Documentation
¶
Overview ¶
Package attachadapter bridges a *agent.Agent onto the attach-mode HTTP/SSE surface (pkg/attach). It is phase 4 of the pkg/agent decomposition (docs/agent-package-split-design.md): the 22 Attach* capability methods and the WithAttach* provider options that used to live on the core Agent now live here, so the frozen agent surface stays narrow and hosts that never serve attach-mode never carry the wiring.
Usage:
a := agent.New(llm, ...) // core options only
ad := attachadapter.New(a,
attachadapter.WithMemoryProvider(f), // formerly agent.WithAttachMemoryProvider
attachadapter.WithPromptBroker(b),
)
reg.Register(ad) // ad satisfies attach.Registrant
The adapter satisfies attach.Registrant plus every optional attach capability interface (ToolsProvider, UsageProvider, PermsController, ...); registering it with a *attach.SessionRegistry makes the agent reachable over HTTP/SSE via attach.NewServer.
Index ¶
- Variables
- type Adapter
- func (ad *Adapter) Agent() *agent.Agent
- func (ad *Adapter) AppName() string
- func (ad *Adapter) AttachAddAllow(patterns []string) error
- func (ad *Adapter) AttachAddDeny(patterns []string) error
- func (ad *Adapter) AttachAgents() []attach.AgentInfo
- func (ad *Adapter) AttachAskSideQuestion(ctx context.Context, question string) (string, error)
- func (ad *Adapter) AttachCapabilities() attach.CapabilityReport
- func (ad *Adapter) AttachCheckpoint(ctx context.Context, note string) (attach.CheckpointResponse, error)
- func (ad *Adapter) AttachCompact(ctx context.Context, focus string) (attach.CompactResponse, error)
- func (ad *Adapter) AttachContext() attach.ContextInfo
- func (ad *Adapter) AttachInterrupt() bool
- func (ad *Adapter) AttachMCP() attach.MCPInfo
- func (ad *Adapter) AttachMemory() []attach.MemorySource
- func (ad *Adapter) AttachPerms() attach.PermsInfo
- func (ad *Adapter) AttachPricing() attach.PricingInfo
- func (ad *Adapter) AttachPromptBroker() *attach.PromptBroker
- func (ad *Adapter) AttachRefreshPricing(ctx context.Context) (attach.PricingRefreshResponse, error)
- func (ad *Adapter) AttachReload(ctx context.Context) attach.ReloadResponse
- func (ad *Adapter) AttachReplan(ctx context.Context, req attach.ReplanRequest) (attach.ReplanResponse, error)
- func (ad *Adapter) AttachSetManualPricing(req attach.PricingSetRequest) error
- func (ad *Adapter) AttachSkills() []attach.SkillInfo
- func (ad *Adapter) AttachSpawnSubagent(ctx context.Context, spec attach.SubagentSpec) (attach.SubagentSpawnResponse, error)
- func (ad *Adapter) AttachStatus() attach.StatusInfo
- func (ad *Adapter) AttachTools() []attach.ToolInfo
- func (ad *Adapter) AttachUsage() attach.UsageInfo
- func (ad *Adapter) Description() string
- func (ad *Adapter) EventLog() *eventlog.Handle
- func (ad *Adapter) Inject(message string) error
- func (ad *Adapter) InjectAs(message string, caller auth.Caller) error
- func (ad *Adapter) RequestWake()
- func (ad *Adapter) SessionID() string
- func (ad *Adapter) SetAttachEmitter(f func(eventType string, payload any))deprecated
- func (ad *Adapter) SetOperatorEventEmitter(f func(eventType string, payload any))
- func (ad *Adapter) UserID() string
- type Option
- func WithMCPProvider(fn func() attach.MCPInfo) Option
- func WithMemoryProvider(fn func() []attach.MemorySource) Option
- func WithPricingProvider(fn func() attach.PricingInfo) Option
- func WithPricingSetter(fn func(req attach.PricingSetRequest) error) Option
- func WithPromptBroker(b *attach.PromptBroker) Option
- func WithRefreshPricer(fn func(ctx context.Context) (attach.PricingRefreshResponse, error)) Option
- func WithReloader(fn func(ctx context.Context) attach.ReloadResponse) Option
- func WithReplanner(...) Option
- func WithSkillsProvider(fn func() []attach.SkillInfo) Option
Constants ¶
This section is empty.
Variables ¶
ErrSubagentSpawnerUnavailable is returned by AttachSpawnSubagent when the agent wasn't constructed with agent.WithBackgroundManager. The attach handler maps this to HTTP 501 so the operator sees "subagent spawn not registered" instead of a 500.
The message string is load-bearing: pkg/attach matches it literally (it can't import this package's sentinel without knowing about the agent side), so change it only in lockstep with isSubagentSpawnerUnavailable in pkg/attach/handlers_slash.go. The prefix says "attachadapter" — the sentinel's home since the #443 split (the stale "agent:" prefix from its old home was fixed under #492 item 5).
Functions ¶
This section is empty.
Types ¶
type Adapter ¶
type Adapter struct {
// contains filtered or unexported fields
}
Adapter wraps a *agent.Agent with the attach-facing capability surface. Construct with New; the zero value is not useful.
The capability methods (Attach*) are safe on a nil receiver — they degrade to the same "capability not registered" responses an unwired closure produces, matching the nil-safety convention of the agent package. The plain Registrant forwards (AppName, Inject, ...) assume a real wrapped agent, same as registering a bare *agent.Agent did before the split.
func New ¶
New wraps a with the attach capability surface.
Contract (one rule, not two): the CAPABILITY methods (Attach*) are nil-safe — on a nil adapter or nil wrapped agent they degrade to the same "capability not registered" / zero-value responses an unwired closure produces. The plain attach.Registrant forwards are NOT nil-safe: they require a real wrapped agent and misbehave otherwise (the identity accessors — AppName, SessionID, EventLog — panic; Inject/InjectAs error; RequestWake no-ops), exactly as registering a bare *agent.Agent did before the #443 split. Passing a nil agent is therefore only useful for constructing a capability-only value in tests — never register one (attach.SessionRegistry would reject its empty identity anyway).
func (*Adapter) Agent ¶
Agent returns the wrapped *agent.Agent. Hosts that thread the adapter through construction seams (session factories, TUI deps) use this to recover the agent for Run/Inject/etc without carrying both values.
func (*Adapter) AttachAddAllow ¶
AttachAddAllow implements attach.PermsController. Delegates to permissions.Gate.AddAllowPatterns. Returns nil if no gate was wired (no-op rather than error — operators shouldn't see an error for an absent gate). Surfaces validation errors from the gate so the operator sees malformed-pattern feedback.
func (*Adapter) AttachAddDeny ¶
AttachAddDeny implements attach.PermsController. Delegates to permissions.Gate.AddDenyPatterns.
func (*Adapter) AttachAgents ¶
AttachAgents implements attach.AgentsProvider. Returns the live background subagents from the agent's SubagentManager, or an empty slice when no manager was wired.
func (*Adapter) AttachAskSideQuestion ¶
AttachAskSideQuestion implements attach.SideQueryProvider. Wraps agent.AskSideQuestion (the /btw side-channel that doesn't persist to the event log).
func (*Adapter) AttachCapabilities ¶
func (ad *Adapter) AttachCapabilities() attach.CapabilityReport
AttachCapabilities implements attach.CapabilityReporter (#490). The adapter satisfies every optional capability interface unconditionally (see the conformance block below), so interface presence stopped signaling wiredness the moment the adapter became the universal registration path — every session advertised mcp/perms_stream/specialists and all five slash commands, and remote UIs rendered dead affordances backed by empty payloads or 501s. This report states what is actually wired:
- perms_stream ⇔ a prompt broker was supplied (WithPromptBroker);
- mcp ⇔ an MCP snapshot fn was supplied (WithMCPProvider);
- specialists / "subagent" ⇔ the agent carries a background manager (agent.WithBackgroundManager);
- interrupt, "btw" ⇔ a live agent is wrapped (both are core agent capabilities — Interrupt and AskSideQuestion need no extra wiring);
- "compact" ⇔ agent.HasCompactor(); "done" ⇔ HasCheckpointer();
- "replan" ⇔ a replanner fn was supplied (WithReplanner).
func (*Adapter) AttachCheckpoint ¶
func (ad *Adapter) AttachCheckpoint(ctx context.Context, note string) (attach.CheckpointResponse, error)
AttachCheckpoint implements attach.CheckpointSlashProvider. Wraps agent.Checkpoint.
func (*Adapter) AttachCompact ¶
AttachCompact implements attach.CompactSlashProvider. Wraps agent.Compact and projects the result into the JSON wire format. Errors propagate; the attach handler turns them into 500s.
func (*Adapter) AttachContext ¶
func (ad *Adapter) AttachContext() attach.ContextInfo
AttachContext implements attach.ContextProvider. Projects the agent's ContextStats (compaction / checkpoint / subtask shape) into the attach wire format. Same cost as ContextStats (one session.Service.Get() + O(events) scan) — operator-driven, infrequent.
func (*Adapter) AttachInterrupt ¶
AttachInterrupt implements attach.InterruptProvider so the attach-mode POST /sessions/<sid>/interrupt handler can dispatch cancel intents from a remote operator. Forwards to agent.Interrupt.
func (*Adapter) AttachMemory ¶
func (ad *Adapter) AttachMemory() []attach.MemorySource
AttachMemory implements attach.MemoryProvider. Returns nil when no provider was wired — the handler emits 200 with an empty `{"sources": []}`.
func (*Adapter) AttachPerms ¶
AttachPerms implements attach.PermsProvider. Returns the gate's current Snapshot (mode + allow + deny pattern lists) projected into the attach wire format, plus the per-session approval log so the remote TUI's /permissions slash can render what was approved this session. Returns zero PermsInfo if no gate was wired via agent.WithGate.
func (*Adapter) AttachPricing ¶
func (ad *Adapter) AttachPricing() attach.PricingInfo
AttachPricing implements attach.PricingProvider.
func (*Adapter) AttachPromptBroker ¶
func (ad *Adapter) AttachPromptBroker() *attach.PromptBroker
AttachPromptBroker implements attach.PromptBrokerProvider.
func (*Adapter) AttachRefreshPricing ¶
AttachRefreshPricing implements attach.PricingController. Returns attach.ErrCapabilityNotRegistered when no func was wired — the handler maps that to HTTP 501.
func (*Adapter) AttachReload ¶
func (ad *Adapter) AttachReload(ctx context.Context) attach.ReloadResponse
AttachReload implements attach.Reloader. Returns a response with Errors populated by ErrCapabilityNotRegistered when no func was wired so the handler emits the same 501 the other unwired controllers do.
func (*Adapter) AttachReplan ¶
func (ad *Adapter) AttachReplan(ctx context.Context, req attach.ReplanRequest) (attach.ReplanResponse, error)
AttachReplan implements attach.ReplanProvider. Routes to the closure wired by WithReplanner; returns ErrCapabilityNotRegistered when no func was wired.
func (*Adapter) AttachSetManualPricing ¶
func (ad *Adapter) AttachSetManualPricing(req attach.PricingSetRequest) error
AttachSetManualPricing implements attach.PricingController.
func (*Adapter) AttachSkills ¶
AttachSkills implements attach.SkillsProvider.
func (*Adapter) AttachSpawnSubagent ¶
func (ad *Adapter) AttachSpawnSubagent(ctx context.Context, spec attach.SubagentSpec) (attach.SubagentSpawnResponse, error)
AttachSpawnSubagent implements attach.SubagentSpawner. Delegates to the agent's wired SubagentManager. Returns ErrSubagentSpawnerUnavailable when no manager is attached.
func (*Adapter) AttachStatus ¶
func (ad *Adapter) AttachStatus() attach.StatusInfo
AttachStatus implements attach.StatusProvider. V1 returns the agent's model name + a coarse "idle" state — finer-grained state (running / deferred / paused) would require run-loop instrumentation that hasn't been wired yet; the design doc captures pause/resume + state mutation as v3 work.
func (*Adapter) AttachTools ¶
AttachTools implements attach.ToolsProvider. Returns the agent's full tool catalog as ToolInfo entries with source classification (builtin vs other) and the gate's pre-flight state per tool when a gate was wired via agent.WithGate. MCP / skill attribution is "other" in v1 — distinguishing them at the slice level needs an upstream metadata pass we haven't done yet.
func (*Adapter) AttachUsage ¶
AttachUsage implements attach.UsageProvider. Returns the agent's usage tracker totals plus a per-model breakdown when more than one model has been used in this session (typical pattern: parent on a frontier model, subtasks on a cheap flash-tier model via --agentic-small-model), plus a per-turn array so operators can answer per-turn cost/cache questions without hand-scraping the eventlog (issue #222). Returns a zero UsageInfo if no usage tracker was wired (agent.WithUsageTracker).
cost_usd_uncached_reference is computed per-turn using the resolved pricing for that turn's model — sessions that mix models (parent + subtask on a flash-tier via --agentic-small-model) get accurate reference numbers instead of averaging one model's rates over the other. Rolled up into Overall / PerModel by summing per-turn contributions.
func (*Adapter) Description ¶
Description implements attach.DescriptionProvider — the /.well-known/agent-card.json handler falls back to this when no explicit AgentCardConfig.Description override is supplied.
func (*Adapter) RequestWake ¶
func (ad *Adapter) RequestWake()
RequestWake implements attach.Registrant.
func (*Adapter) SetAttachEmitter
deprecated
func (*Adapter) SetOperatorEventEmitter ¶
SetOperatorEventEmitter implements attach.OperatorEventTarget. The attach broadcaster calls this on first SSE subscriber (wiring its Emit method) and again with nil when the last subscriber disconnects. Forwards to the agent, which owns the emit machinery — the core run loop is the thing that emits status/turn/usage events.
type Option ¶
type Option func(*Adapter)
Option configures an Adapter under construction.
func WithMCPProvider ¶
WithMCPProvider wires a snapshot func for /sessions/<sid>/mcp (backs /mcp). Formerly agent.WithAttachMCPProvider.
func WithMemoryProvider ¶
func WithMemoryProvider(fn func() []attach.MemorySource) Option
WithMemoryProvider wires a snapshot func that returns the agent's loaded instruction sources for the remote-attach /sessions/<sid>/memory endpoint (backs the remote TUI's /memory slash). The caller usually projects an `instruction.Loaded`'s Sources list into []attach.MemorySource; nil = endpoint returns empty. Formerly agent.WithAttachMemoryProvider.
func WithPricingProvider ¶
func WithPricingProvider(fn func() attach.PricingInfo) Option
WithPricingProvider wires a snapshot func for /sessions/<sid>/pricing (backs the remote TUI's /pricing read). Formerly agent.WithAttachPricingProvider.
func WithPricingSetter ¶
func WithPricingSetter(fn func(req attach.PricingSetRequest) error) Option
WithPricingSetter wires a func that runs on POST /sessions/<sid>/pricing/set — writes a manual per-model rate and rebuilds the catalog. Formerly agent.WithAttachPricingSetter.
func WithPromptBroker ¶
func WithPromptBroker(b *attach.PromptBroker) Option
WithPromptBroker wires the broker that bridges the agent's permissions.Gate prompts to remote operators over GET /sessions/<sid>/perms/stream and POST /perms/respond. The caller is also responsible for wiring this broker into the gate (typically via Gate.SetPrompter(broker)) so prompts the gate generates actually flow through it. Without this option the /perms/stream + /perms/respond routes return 501. Formerly agent.WithAttachPromptBroker.
func WithRefreshPricer ¶
WithRefreshPricer wires a func that runs on POST /sessions/<sid>/pricing/refresh — typically calls into `pkg/pricing.Refresh` and rebuilds the catalog. Returns the outcome the operator sees. Formerly agent.WithAttachRefreshPricer.
func WithReloader ¶
func WithReloader(fn func(ctx context.Context) attach.ReloadResponse) Option
WithReloader wires a func that runs on POST /sessions/<sid>/reload. The closure is expected to re-walk project deps (instruction sources, skills bundles, MCP config) and return per-surface success in the response so the operator sees which parts succeeded and which failed. The adapter doesn't inspect the response shape; what "reload" means is the host's concern. Without this option the operator sees 501 / capability not registered. Formerly agent.WithAttachReloader.
func WithReplanner ¶
func WithReplanner(fn func(ctx context.Context, req attach.ReplanRequest) (attach.ReplanResponse, error)) Option
WithReplanner wires a func that runs on POST /sessions/<sid>/slash/replan and on the in-process TUI's /replan slash dispatch. The closure is expected to clear the gate's planRecorded flag and archive the latest plan artifact (typically `tools.RevokeLatestPlan(gate, agentsDir)`). Without this option the slash returns 501 / "capability not registered".
Wire only when plan-first gating is active (config permissions.require_plan_artifact: true). Wiring it under other configs is a no-op but harmless. Formerly agent.WithAttachReplanner.
func WithSkillsProvider ¶
WithSkillsProvider wires a snapshot func for /sessions/<sid>/skills (backs /skills). Formerly agent.WithAttachSkillsProvider.