Documentation
¶
Index ¶
- Variables
- func NewCheckAgentTool(mgr *Manager) tool.Tool
- func NewListAgentsTool(mgr *Manager) tool.Tool
- func NewSpawnAgentTool(mgr *Manager) tool.Tool
- func NewSpawnRemoteAgentTool(spawner RemoteAgentSpawner, mgr *Manager) (tool.Tool, error)
- func NewSpawnTools(mgr *Manager) []tool.Tool
- func NewStopAgentTool(mgr *Manager) tool.Tool
- type Alert
- type Budgets
- type Handle
- type Manager
- func (m *Manager) Alerts() <-chan Alert
- func (m *Manager) AttachParent(a *agent.Agent)
- func (m *Manager) Close() error
- func (m *Manager) Get(name string) (*Handle, bool)
- func (m *Manager) List() []*Handle
- func (m *Manager) ListSubagents() []attach.AgentInfo
- func (m *Manager) OnAlert(h func(Alert))
- func (m *Manager) Parent() *agent.Agent
- func (m *Manager) PrependPendingAlerts(prompt string) string
- func (m *Manager) Spawn(ctx context.Context, parentBranch string, spec Spec) (*Handle, error)
- func (m *Manager) SpawnSubagent(ctx context.Context, spec attach.SubagentSpec) (attach.SubagentSpawnResponse, error)
- func (m *Manager) Stop(name string) error
- type ManagerOption
- func WithAlertBuffer(n int) ManagerOption
- func WithCatalog(tools []tool.Tool) ManagerOption
- func WithDefaultBudgets(b Budgets) ManagerOption
- func WithDefaultScheduler(s coretools.Scheduler) ManagerOption
- func WithGate(g *permissions.Gate) ManagerOption
- func WithMaxConcurrent(n int) ManagerOption
- func WithMaxDepth(n int) ManagerOption
- func WithProvider(p models.Provider, modelID string) ManagerOption
- type RemoteAgentEvent
- type RemoteAgentHandle
- type RemoteAgentSpawner
- type RemoteAgentSpec
- type RemoteAgentStatus
- type Spec
- type Status
Constants ¶
This section is empty.
Variables ¶
var ErrDepthExceeded = errors.New("background: max subagent depth exceeded")
ErrDepthExceeded is returned by Spawn when the calling context is already at the max subagent depth.
var ErrManagerClosed = errors.New("background: closed")
ErrManagerClosed is returned by Spawn after Close has been called.
var ErrNoParent = errors.New("background: parent agent not wired (use agent.WithBackgroundManager)")
ErrNoParent is returned by Spawn when the manager hasn't been attached to an agent yet (i.e. agent.New(... WithBackgroundManager ...) hasn't run).
var ErrNoSpawner = errors.New("background: NewSpawnRemoteAgentTool: spawner is required (use RefuseRemoteAgentSpawner for the headless / unattended case)")
ErrNoSpawner is returned by NewSpawnRemoteAgentTool when nil is passed for the spawner. Use RefuseRemoteAgentSpawner instead of nil when you want the tool registered but no-op.
var ErrSubagentExists = errors.New("background: subagent with this name already exists")
ErrSubagentExists is returned by Spawn when a RUNNING subagent with the requested name is already registered. Names must be unique among live subagents within a manager; a handle in a terminal state (completed / failed / stopped / deferred) is evicted by the next Spawn of the same name, so names become reusable once their previous run has finished.
var ErrTooManyConcurrent = errors.New("background: max concurrent subagents reached")
ErrTooManyConcurrent is returned by Spawn when the manager already has MaxConcurrent running subagents.
var ErrUnknownScheduler = errors.New("background: unknown scheduler choice")
ErrUnknownScheduler is wrapped and returned by Spawn when a spec.Scheduler value isn't one of the recognized choices.
var ErrUnknownTool = errors.New("background: unknown tool")
ErrUnknownTool is wrapped and returned by Spawn when a spec.Tools or spec.Extras entry isn't present in the catalog.
Functions ¶
func NewCheckAgentTool ¶
NewCheckAgentTool returns a tool the parent's model can call to inspect one subagent's detailed status — including its terminal result (final text, stop reason, totals) once it's finished.
func NewListAgentsTool ¶
NewListAgentsTool returns a tool the parent's model can call to see every subagent the manager has tracked (running + terminal). Empty list when none have been spawned.
func NewSpawnAgentTool ¶
NewSpawnAgentTool returns a tool the parent's model can call to launch a new in-process background subagent. The tool's name in the model's view is "spawn_agent"; the registered handler defers to mgr.Spawn after reading the calling tool.Context's branch so the new subagent's events land in the right hierarchical branch.
Spawn errors (invalid spec, depth/concurrency cap, unknown tool) are returned as the tool's result text rather than as Go errors, so the model sees them in conversation context and can adapt (e.g. by stopping a sibling first). Provider/model construction errors propagate normally since those are typically caller-fixable configuration problems.
func NewSpawnRemoteAgentTool ¶
func NewSpawnRemoteAgentTool(spawner RemoteAgentSpawner, mgr *Manager) (tool.Tool, error)
NewSpawnRemoteAgentTool returns a tool the parent's model can call to launch an out-of-process subagent via the consumer-supplied spawner. The handle's Events() channel is drained by a goroutine the manager starts inside Spawn; events of Kind="alert" land on the manager's alert channel under the subagent's name, and the terminal handle status is recorded for list/check/stop uniformity alongside in-process subagents.
Pass nil for mgr to skip the alert + registry fan-in (alerts will be dropped); typically you want both wired, especially for the bundled CLI.
func NewSpawnTools ¶
NewSpawnTools is a convenience that returns all four model-facing background-agent tools in one slice, ready to pass through agent.WithTools. The bundled CLI uses this to wire the full suite atomically.
func NewStopAgentTool ¶
NewStopAgentTool returns a tool the parent's model can call to cancel a running subagent. No-op if the subagent already terminal. Returns an error result (not a tool failure) when the name is unknown so the model can adapt.
Types ¶
type Alert ¶
type Alert struct {
From string
Text string
Timestamp time.Time
Kind string // "alert" (default) | "completed" | "failed" | "stopped"
}
Alert is one report message a spawned subagent (or the manager itself on completion) emitted upward to the parent.
type Budgets ¶
type Budgets struct {
MaxTurns int
MaxCost float64
MaxWallclock time.Duration
PerTurnTimeout time.Duration
}
Budgets bounds a single spawned subagent's run. Zero values mean no cap for that dimension. The manager's WithDefaultBudgets supplies the defaults; per-spawn overrides come from the spawn_agent tool args.
type Handle ¶
type Handle struct {
Name string
Branch string
StartedAt time.Time
// contains filtered or unexported fields
}
Handle is the lifecycle record for one spawned subagent. Exposed read-only via Manager.List / Manager.Get so the parent model's check_agent tool can introspect status without reaching into internal state.
func (*Handle) Done ¶
func (h *Handle) Done() <-chan struct{}
Done returns a channel that closes when the subagent's goroutine exits. Use to wait for completion from the parent without polling.
func (*Handle) Err ¶
Err returns the terminal error if the subagent's RunAutonomous returned one. Nil while running or on clean completion.
func (*Handle) Result ¶
func (h *Handle) Result() *autonomous.RunResult
Result returns the terminal RunResult if the subagent has finished, or nil if it's still running.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager owns the lifecycle of in-process background subagents that the parent agent's model decides to spawn at runtime via the spawn_agent tool family (see background_tools.go).
One manager backs one parent agent. The manager:
constructs each spawned subagent against the parent's session service (with branch isolation), the parent's permissions gate (inherited wholesale), and a fresh model.LLM (one client per spawn, see docs/background-subagents-design.md);
runs each subagent in its own goroutine via RunAutonomous with per-subagent budgets;
multiplexes alert + completion messages from every running subagent onto a single channel the parent's run loop drains before each turn (see Agent.Run);
enforces a configurable max-concurrent cap on top of the subagent depth cap (the existing CurrentSubagentDepth check from subagent.go) so a runaway model can't spawn unboundedly.
Construction order is intentional: the manager is built first (without a parent reference), the spawn-related tools are built against the manager, the parent agent.New is called with those tools registered and the manager wired via WithBackgroundManager. agent.New stamps the parent back-reference onto the manager during construction so Spawn can read parent.SessionService / AppName / UserID / SessionID without the consumer plumbing them twice.
func ManagerOf ¶
ManagerOf recovers the concrete *Manager from an agent's SubagentManager seam, or nil when the agent has no manager wired (or a different implementation). Rich callers (the runner's REPL, the embedded TUI) use this to reach Manager methods beyond the agent.SubagentManager interface (List, Get, Stop, Alerts, OnAlert).
func NewManager ¶
func NewManager(opts ...ManagerOption) (*Manager, error)
NewManager builds a manager from the supplied options. Required: provider + modelID (WithProvider). The parent agent reference is established later by WithBackgroundManager when the parent is constructed via agent.New — until that wiring happens, Spawn returns ErrNoParent.
func (*Manager) Alerts ¶
Alerts returns the channel external consumers (the runner's REPL alert display goroutine, library consumers building their own UIs) drain to surface alerts as they arrive. The pre-turn drain inside Agent.Run uses PrependPendingAlerts instead — that path uses a non-blocking drain so it doesn't compete with this channel.
Note: a single alert lands on this channel exactly once. Consumers must agree on who drains it; today the runner.WriteEvents alert- display goroutine drains for REPL display, and Agent.Run drains for pre-turn injection. They're separated by which path is active (REPL vs headless vs autonomous).
func (*Manager) AttachParent ¶
AttachParent records the parent agent on the manager. Called by agent.New when WithBackgroundManager is set (via the agent.SubagentManager seam). Safe to call once; subsequent calls overwrite (last-writer-wins so re-construction in tests works cleanly).
func (*Manager) Close ¶
Close stops every running subagent and prevents new spawns. Blocks until each goroutine has exited or closeDrainTimeout elapses — stragglers are abandoned (their contexts stay cancelled) and reported in the returned error so the caller can log them. Idempotent.
func (*Manager) Get ¶
Get returns the handle for the named subagent. ok=false when the name isn't registered.
func (*Manager) List ¶
List returns all currently-tracked handles, sorted by start time. Terminal handles remain in the list until Close (so check_agent can return final status). Defensive copy of slice.
func (*Manager) ListSubagents ¶
ListSubagents implements agent.SubagentManager. Returns attach-facing metadata for the manager's live subagents; backs Agent.AttachAgents.
func (*Manager) OnAlert ¶
OnAlert installs a synchronous hook called from pushAlert before the channel send. Useful for surfacing alerts to side channels (e.g. the REPL's inline display) without competing with the model- context drain on Alerts() / PrependPendingAlerts. Pass nil to clear.
The hook runs in whichever goroutine triggered the alert (typically a subagent's goroutine for report_alert, the Spawn goroutine for completion). Hooks should not block.
func (*Manager) Parent ¶
Parent returns the agent the manager is attached to, or nil if no agent.New has wired it yet. Exposed for tests + diagnostics.
func (*Manager) PrependPendingAlerts ¶
PrependPendingAlerts drains every pending alert from the manager's channel (non-blocking) and, when non-empty, returns prompt with a "[Background reports]" header prepended. Empty channel returns prompt unchanged.
Called by Agent.Run before each turn so the parent's model sees what its subagents have reported since the last turn.
func (*Manager) Spawn ¶
Spawn launches a new background subagent under spec. parentBranch is the branch the calling tool's context carries (typically empty for the top-level parent, "bg.<name>" when nested); the subagent's own branch becomes "<parentBranch>.bg.<spec.Name>" via composeBranch so the eventlog audit trail remains hierarchical.
Returns the handle immediately; the subagent's goroutine runs autonomous.Run against spec.Goal until budgets fire, the model signals done via report_completed, the parent calls Stop, or the goroutine's context is cancelled.
Returned errors are pre-flight: invalid spec, depth or concurrency cap exceeded, unknown tool name, or manager not yet attached to a parent. Once the goroutine is running, terminal errors land on the handle (h.Err()) and a corresponding Alert is pushed.
func (*Manager) SpawnSubagent ¶
func (m *Manager) SpawnSubagent(ctx context.Context, spec attach.SubagentSpec) (attach.SubagentSpawnResponse, error)
SpawnSubagent implements agent.SubagentManager. Translates an attach spec into a background Spec and delegates to Spawn; backs attachadapter.AttachSpawnSubagent.
type ManagerOption ¶
type ManagerOption func(*bgMgrConfig)
ManagerOption configures NewManager.
func WithAlertBuffer ¶
func WithAlertBuffer(n int) ManagerOption
WithAlertBuffer sets the alert channel buffer. When full, the oldest pending alert is dropped to make room (with a warning logged). Default 256.
func WithCatalog ¶
func WithCatalog(tools []tool.Tool) ManagerOption
WithCatalog registers the tool instances spawn_agent arguments can refer to by name. Pass the parent's already-gated tool list (typically tools.Default() plus any MCP/skill tools flattened to a single slice); the manager looks up each requested tool by Tool.Name(). Tools not listed here can't be requested.
func WithDefaultBudgets ¶
func WithDefaultBudgets(b Budgets) ManagerOption
WithDefaultBudgets sets the budgets a spawn request inherits when its own per-call args don't override. Default: 50 turns / $1.00 / 10 minutes, no per-turn timeout.
func WithDefaultScheduler ¶
func WithDefaultScheduler(s coretools.Scheduler) ManagerOption
WithDefaultScheduler sets the tools.Scheduler that spawned subagents inherit when the per-spawn Spec.Scheduler is empty or "default". Pass tools.SleepScheduler() for the canonical in-process supervisor topology where the parent runs as a long-lived daemon and children sleep between scans. Pass tools.ExitOnDeferScheduler() for orchestrator-managed deployments. Pass nil (or leave unset) to run subagents without between-turn pacing — the schedule_next_turn tool is then unavailable to those subagents.
Per-spawn overrides via Spec.Scheduler win when supplied; see Spawn / NewSpawnAgentTool.
func WithGate ¶
func WithGate(g *permissions.Gate) ManagerOption
WithGate wires the permissions gate that spawned subagents inherit (by reference; same instance). Required when running in ask/allow mode; the manager rejects spawn requests when the gate is in ask-mode without a prompter (same deadlock guard as RunAutonomous).
func WithMaxConcurrent ¶
func WithMaxConcurrent(n int) ManagerOption
WithMaxConcurrent caps how many subagents can be Running at once. Spawn calls that would exceed this return a clean tool- result error the model can adapt to. Default 8.
func WithMaxDepth ¶
func WithMaxDepth(n int) ManagerOption
WithMaxDepth caps how deep the subagent tree can go. A spawn from a context already at depth>=N returns an error result instead of nesting further. Default 2.
func WithProvider ¶
func WithProvider(p models.Provider, modelID string) ManagerOption
WithProvider wires the model provider + model ID used to build a fresh LLM client per spawn. Required.
type RemoteAgentEvent ¶
type RemoteAgentEvent struct {
Kind string // "alert" | "log" | "completed" | "failed" | "stopped"
Text string
Timestamp time.Time
}
RemoteAgentEvent is what the consumer's transport delivers back from the remote subagent. The Kind field is the manager's hook for classifying — "alert" gets fanned into the parent's alert channel as a normal Alert; terminal kinds ("completed" / "failed" / "stopped") trigger the terminal Alert + handle close.
type RemoteAgentHandle ¶
type RemoteAgentHandle interface {
// ID returns the spawner-assigned identifier for diagnostics.
// Stable across the handle's lifetime.
ID() string
// Status returns the current lifecycle state of the remote
// subagent. Implementations are expected to be cheap (cached
// status from the last received event is fine).
Status(ctx context.Context) (RemoteAgentStatus, error)
// Stop signals the remote subagent to terminate. The
// implementation decides whether this is best-effort (e.g.
// async cancel of a K8s Job) or synchronous.
Stop(ctx context.Context) error
// Events returns a channel of events streamed from the remote
// subagent. The consumer's implementation closes the channel
// once the remote has terminated; the manager's fan-in
// goroutine exits on close.
Events() <-chan RemoteAgentEvent
}
RemoteAgentHandle is the contract for a running remote subagent. Implementations live in the consumer's package (the substrate adapter); the manager treats it opaquely apart from draining Events() for the alert pipeline.
type RemoteAgentSpawner ¶
type RemoteAgentSpawner interface {
// Spawn launches a remote subagent under spec and returns a
// handle the manager uses for status checks, stop, and event
// fan-in. Errors at this point propagate as Go errors (caller
// is wiring the spawner directly); errors that surface later
// flow through RemoteAgentHandle.Events() as Kind="failed".
Spawn(ctx context.Context, spec RemoteAgentSpec) (RemoteAgentHandle, error)
}
RemoteAgentSpawner is implemented by consumers who want the parent agent's model to be able to spawn out-of-process subagents — gRPC to a remote agent server, K8s Jobs, Cloud Run, NATS-dispatched workers, anything that runs an agent somewhere other than this process. core-agent stays agnostic about transport: the consumer's Spawn implementation is responsible for whatever IPC and lifecycle the substrate requires.
Mirrors the consumer-pluggability shape of tools.NewAskUserTool + tools.Prompter: a small interface the host implements, wired into a tool the model can call uniformly.
func RefuseRemoteAgentSpawner ¶
func RefuseRemoteAgentSpawner(reason string) RemoteAgentSpawner
RefuseRemoteAgentSpawner returns a spawner whose Spawn always errors with reason. Use it as the default spawner when running headless / unattended so the model sees a clean tool result it can adapt to, rather than the bundled CLI crashing on a nil dereference. Analog of tools.RefusePrompter.
type RemoteAgentSpec ¶
type RemoteAgentSpec struct {
ID string
Name string
SystemPrompt string
Goal string
Tools []string
Extras []string
Budgets Budgets
}
RemoteAgentSpec is what the manager hands to the consumer's spawner — the same shape as the in-process Spec, plus a stable opaque ID the consumer can use as a primary key in their substrate (e.g. the K8s Job name). Spec.Name is the human-facing identifier the parent's model chose; Spec.ID is the manager's invariant under the manager's registry. They're usually the same string but kept distinct so consumers don't have to guess.
type RemoteAgentStatus ¶
type RemoteAgentStatus int
RemoteAgentStatus mirrors Status but lives in the remote space. Implementations map their native states (K8s Job phase, HTTP response, etc.) onto these.
const ( RemoteStatusPending RemoteAgentStatus = iota RemoteStatusRunning RemoteStatusCompleted RemoteStatusFailed RemoteStatusStopped )
func (RemoteAgentStatus) String ¶
func (s RemoteAgentStatus) String() string
type Spec ¶
type Spec struct {
Name string
// SystemPrompt is the subagent's task-specific instruction.
// Since #459 it COMPOSES: the built agent gets the layered
// baseline (agent.CoreInstruction + provider quirks +
// agent.AutonomousOverlay) with this text appended as a layer-5
// block — so spawned subagents keep the compaction contract and
// edit-safety rules they previously lost to the full replace.
// Set ReplaceSystemPrompt for the old bare-prompt behavior.
SystemPrompt string
// ReplaceSystemPrompt, when true, restores the pre-#459
// semantics: SystemPrompt fully replaces the layered baseline
// (agent.WithInstruction). The subagent then carries NO harness
// contract — compaction summaries arrive unexplained — so use
// only when you are supplying your own complete prompt.
ReplaceSystemPrompt bool
Goal string
Tools []string
Extras []string
Budgets Budgets
// Scheduler selects the between-turn scheduler the subagent's
// RunAutonomous loop honors. Valid values: "" or "default" (use
// the manager's WithDefaultScheduler — may itself be
// nil), "sleep" (in-process goroutine sleep), "exit_on_defer"
// (orchestrator-managed exit), "none" (no scheduler — the
// schedule_next_turn tool won't be registered for this subagent).
Scheduler string
}
Spec is the request shape a single Spawn call expects. Built from the spawn_agent tool args by the tool handler.
type Status ¶
type Status int
Status is the lifecycle state of a background subagent.
const ( // StatusRunning — goroutine alive, RunAutonomous loop active. StatusRunning Status = iota // StatusCompleted — RunAutonomous returned with Reason==Completed. StatusCompleted // StatusFailed — RunAutonomous returned with a non-Completed // terminal reason (MaxTurns, MaxCost, error, etc.). StatusFailed // StatusStopped — explicit Stop() canceled the run. StatusStopped // StatusDeferred — RunAutonomous cleanly deferred or hit a budget cap. StatusDeferred )