background

package
v2.9.0-dev.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 19, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Index

Constants

View Source
const (
	SpawnAgentToolName = "spawn_agent"
	StopAgentToolName  = "stop_agent"
)

The model-facing names of the two delegation tools. Exported because the delegation surface is something a caller has to be able to reason about by name: the CLI carves these out of what a subagent inherits so a subagent doesn't get a delegation surface it never asked for (#748).

Variables

View Source
var ErrAdhocDisabled = errors.New("background: ad-hoc subagents are disabled; reference a configured subagent by name")

ErrAdhocDisabled is returned when the model attempts an ad-hoc (inline-persona) spawn while allow_adhoc is off. The remedy is to reference a configured subagent by name.

View Source
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.

View Source
var ErrManagerClosed = errors.New("background: closed")

ErrManagerClosed is returned by Spawn after Close has been called.

View Source
var ErrModelNotOverridable = errors.New("background: model override must be \"small\" or omitted")

ErrModelNotOverridable is returned when a spawn requests a model override other than "small" (or inherit). Per D2, a specific model requires its own predefined spec — a reference may only downshift to the small tier or inherit the spec's configured model.

View Source
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).

View Source
var ErrNoSmallModel = errors.New("background: no small-tier model configured")

ErrNoSmallModel is returned when a spawn requests model "small" but the manager was constructed without a small-tier model id (WithSmallModelID).

View Source
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.

View Source
var ErrSelfSpawn = errors.New("background: a subagent may not spawn itself")

ErrSelfSpawn is returned by Spawn when a subagent tries to spawn a subagent it is itself an instance of. Recursion at depth 1 sits inside any sensible depth cap, so the cap can't see it: what stops it is the declared-name lineage the spawn context carries (#732).

View Source
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.

View Source
var ErrTooManyConcurrent = errors.New("background: max concurrent subagents reached")

ErrTooManyConcurrent is returned by Spawn when the manager already has MaxConcurrent running subagents.

View Source
var ErrToolNotGranted = errors.New("background: tool not granted by predefined spec")

ErrToolNotGranted is returned when a reference spawn's tools override lists a tool the predefined spec does not grant. Overrides may only narrow the spec's tool set, never widen it.

View Source
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.

View Source
var ErrUnknownSubagent = errors.New("background: unknown predefined subagent")

ErrUnknownSubagent is returned when a spawn_agent reference names a predefined spec the manager doesn't have registered.

View Source
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 NewSpawnAgentTool

func NewSpawnAgentTool(mgr *Manager) tool.Tool

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 stop_agent + operator-surface 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

func NewSpawnTools(mgr *Manager) []tool.Tool

NewSpawnTools is a convenience that returns both model-facing background-agent tools (spawn_agent + stop_agent) in one slice, ready to pass through agent.WithTools. The bundled CLI uses this to wire the suite atomically. Introspection (list/check) is intentionally NOT a model tool: completed subagents push their results back to the parent (the [Background reports] channel) and spawn_agent {wait:true} covers blocking needs, so a poll loop is redundant. Operators inspect live instances out-of-band via the attach hub / TUI.

func NewStopAgentTool

func NewStopAgentTool(mgr *Manager) tool.Tool

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.

func SpawnToolNames added in v2.9.0

func SpawnToolNames() []string

SpawnToolNames returns the names NewSpawnTools registers, for callers that need to filter a tool slice rather than build one — chiefly the CLI, which withholds the delegation surface from subagents that inherit the parent's registry wholesale (#748).

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 operator surfaces (attach hub, TUI) and the stop_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

func (h *Handle) Err() error

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.

func (*Handle) Status

func (h *Handle) Status() Status

Status returns the current status (safe for concurrent callers).

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

func ManagerOf(a *agent.Agent) *Manager

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

func (m *Manager) Alerts() <-chan Alert

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) AllowAdhoc added in v2.9.0

func (m *Manager) AllowAdhoc() bool

AllowAdhoc reports whether ad-hoc (inline-persona) spawns are permitted. False (the daemon default) means the model may only spawn by reference to a predefined spec.

func (*Manager) AttachParent

func (m *Manager) AttachParent(a *agent.Agent)

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) Catalog added in v2.9.0

func (m *Manager) Catalog() []attach.SubagentCatalogInfo

Catalog returns the configured-subagent roster (#627) — declarative templates first, then predefined catalog specs (async-only), each sorted by name. This is what the daemon LOADED, as opposed to Manager.List / ListSubagents (live spawned instances). Backs the operator-facing surfaces: GET .../subagents (via the SubagentManager interface's ListSubagentCatalog), the /subagent listing, and the boot dump. Never nil.

Modes records how each subagent can actually be invoked HERE, on this manager's parent. Every declarative template is "async" (spawn_agent {agent}); it is additionally "sync" only when the attached parent exposes it as a tool, which is a property of that parent's WithSubagents set, not of the template. Predefined catalog specs are always "async" only (spawn-by-reference, no synchronous tool).

func (*Manager) Close

func (m *Manager) Close() error

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

func (m *Manager) Get(name string) (*Handle, bool)

Get returns the handle for the named subagent. ok=false when the name isn't registered.

func (*Manager) GrantableToolNames added in v2.9.0

func (m *Manager) GrantableToolNames() []string

GrantableToolNames returns the sorted names an ad-hoc subagent's `tools` list may draw from — exactly the set resolveTools accepts, minus the auto-wired names it silently drops.

It exists so spawn_agent's own parameter description can name the real catalog instead of a hard-coded example. The shipped example listed `bash`, which on a distroless build is a name the model can pass and resolveTools will then reject with ErrUnknownTool — a wasted spawn to learn something the description could have said.

func (*Manager) List

func (m *Manager) List() []*Handle

List returns all currently-tracked handles, sorted by start time. Terminal handles remain in the list until Close (so operator surfaces can still report final status). Defensive copy of slice.

func (*Manager) ListSubagentCatalog added in v2.9.0

func (m *Manager) ListSubagentCatalog() []attach.SubagentCatalogInfo

ListSubagentCatalog implements the agent.SubagentManager seam's operator-catalog method (#627): the configured roster in attach types, so the adapter can serve GET .../subagents without importing this package. Identical data to Catalog (which callers holding the concrete *Manager may use directly).

func (*Manager) ListSubagents

func (m *Manager) ListSubagents() []attach.AgentInfo

ListSubagents implements agent.SubagentManager. Returns attach-facing metadata for the manager's live subagents; backs Agent.AttachAgents.

func (*Manager) OnAlert

func (m *Manager) OnAlert(h func(Alert))

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

func (m *Manager) Parent() *agent.Agent

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) Predefined added in v2.9.0

func (m *Manager) Predefined(name string) (Spec, bool)

Predefined returns the registered spec for name (a copy) and whether it exists.

func (*Manager) PredefinedNames added in v2.9.0

func (m *Manager) PredefinedNames() []string

PredefinedNames returns the registered predefined-spec names, sorted. Backs the operator catalog surfaces (#627) and diagnostics.

func (*Manager) PrependPendingAlerts

func (m *Manager) PrependPendingAlerts(prompt string) string

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) ReferenceNames added in v2.9.0

func (m *Manager) ReferenceNames() []string

ReferenceNames returns every subagent name spawnable by reference — declarative templates plus catalog predefined specs — sorted. Used by operator surfaces (the /subagent command) to list what can be spawned.

func (*Manager) SetSubagentTemplates added in v2.9.0

func (m *Manager) SetSubagentTemplates(ts []SubagentTemplate) error

SetSubagentTemplates installs (or replaces) the declarative-subagent roster after construction. Call it once, before the parent agent starts running — the declarative builder produces the templates only after the manager (and its spawn tools) exist, so they can't be passed to NewManager. Names must be non-empty, branch-safe, carry a non-nil ModelFactory, be unique among themselves, and not collide with a predefined (catalog) spec name. Returns an error without mutating state when any check fails.

Each template's Tools are rebound to THIS manager (see rebindSpawnTools) as they are installed, so one shared roster can be registered on many managers — which is exactly what a multi-session daemon does.

func (*Manager) Spawn

func (m *Manager) Spawn(ctx context.Context, parentBranch string, spec Spec) (*Handle, error)

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) SpawnRef added in v2.9.0

func (m *Manager) SpawnRef(ctx context.Context, parentBranch, name, goal string, ov RefOverrides, explicitName string) (*Handle, error)

SpawnRef spawns a configured subagent by name — routing a declarative template to SpawnTemplate and a catalog predefined spec to Spawn — with the given goal and narrowing-only overrides. It is the operator-facing twin of the model's spawn_agent reference path (the /subagent TUI command uses it to reach the same roster the model can), and never opens the ad-hoc inline-persona path. Unknown names return ErrUnknownSubagent.

func (*Manager) SpawnSubagent

SpawnSubagent implements agent.SubagentManager. Translates an attach spec into a background Spec and delegates to Spawn; backs attachadapter.AttachSpawnSubagent.

func (*Manager) SpawnTemplate added in v2.9.0

func (m *Manager) SpawnTemplate(ctx context.Context, parentBranch, name string, ov RefOverrides, explicitName string) (*Handle, error)

SpawnTemplate launches a registered declarative subagent asynchronously (spawn_agent {agent: name}, #626). The template's persona, tools, toolsets, and model are pre-resolved; the caller supplies the goal via ov and may narrow the model (to "small") and budgets. explicitName, when non-empty, names the instance; otherwise the runtime auto-derives "<name>-<n>".

Tool narrowing is intentionally NOT supported for templates: a rooted subagent's grant spans built-ins, MCP toolset tools, and skills, so a name-subset can't be applied coherently here — configure a dedicated, narrower subagent instead. Model overrides other than inherit/"small" are rejected (D2), matching the catalog reference path.

func (*Manager) Stop

func (m *Manager) Stop(name string) error

Stop cancels the named subagent's context. The goroutine exits at the next ctx-aware checkpoint inside RunAutonomous. Returns nil even when the subagent is already terminal; surfaces "not found" when the name isn't registered.

func (*Manager) StopSubagent added in v2.9.0

func (m *Manager) StopSubagent(name string) (bool, error)

StopSubagent implements agent.SubagentManager. Thin wrapper over Stop that separates "no such subagent" (false, nil — a 404 for the operator, who aimed at a name that isn't running) from a real failure (an error). Stopping an already-stopped subagent is a no-op that still reports true: the handle is still registered, so the operator's intent was satisfied.

func (*Manager) TemplateNames added in v2.9.0

func (m *Manager) TemplateNames() []string

TemplateNames returns the registered declarative-subagent template names, sorted. Backs the operator catalog surfaces (#627).

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 WithAllowAdhoc added in v2.9.0

func WithAllowAdhoc(allow bool) ManagerOption

WithAllowAdhoc permits inline-persona (ad-hoc) spawns — the parent's model authoring a fresh system_prompt at spawn time rather than referencing a predefined spec. Off by default (the daemon posture): an unattended daemon should only spawn operator-vetted specs. Turn it on for interactive/dev sessions where a human is steering.

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 WithPredefinedSpecs added in v2.9.0

func WithPredefinedSpecs(specs []Spec) ManagerOption

WithPredefinedSpecs registers the operator-curated subagent roster the parent's model can spawn by reference (spawn_agent {agent: "<name>"}, #626). Each spec is a template: its SystemPrompt, tool grant, model (Spec.ModelID), and budgets are what a reference spawn inherits and may only narrow. Names must be unique and non-empty; each spec needs a SystemPrompt (its persona). Goal may be empty — the parent supplies the task per spawn. Duplicate or invalid specs make NewManager return an error.

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.

func WithSmallModelID added in v2.9.0

func WithSmallModelID(id string) ManagerOption

WithSmallModelID sets the model id the "small" per-spawn model override resolves to (D2). Without it, spawns requesting model: "small" are rejected with ErrNoSmallModel.

func WithSubagentTemplates added in v2.9.0

func WithSubagentTemplates(ts []SubagentTemplate) ManagerOption

WithSubagentTemplates registers the declarative-subagent roster at construction time. Most callers instead use SetSubagentTemplates, because the templates are built (cmd/core-agent/subagents.go) after the manager is constructed but the spawn tools reference the manager — a construction-ordering cycle the post-construction setter breaks.

func WithSyncWaitTimeout added in v2.9.0

func WithSyncWaitTimeout(d time.Duration) ManagerOption

WithSyncWaitTimeout bounds how long a synchronous spawn (spawn_agent {wait: true}, #626) holds the parent turn open before returning a partial/timeout result. The subagent keeps running in the background past the timeout (its result later pushed via [Background reports]); only the parent's blocking wait is capped. Zero (the default) waits until the subagent finishes on its own budget or the parent context is canceled.

type Mode added in v2.9.0

type Mode string

Mode distinguishes the two things "background subagent" has always meant, which want opposite termination rules (#730).

A BOUNDED delegation is handed one task and is done when it stops working: the first turn that ends without the model asking for another tool ends the run, and its last message is the deliverable. The return tool is registered too and ranked above that (#745), so a model that calls it hands back a curated result instead — but a model that forgets still ends, which is the property that matters.

A STANDING worker is a loop that watches something. A turn that produces only text is a status report, so the driver feeds it the continuation prompt and keeps going until a budget fires, the scheduler defers it, or it calls the return tool. This is the pre-#730 behavior, unchanged.

const (
	// ModeAuto derives the mode: standing when a scheduler is
	// installed (an agent that asks to be re-run later is by
	// definition not finished when it stops talking), bounded
	// otherwise. This is the zero value, and the default for every
	// spawn that doesn't say.
	ModeAuto Mode = ""
	// ModeBounded forces the one-task delegation contract.
	ModeBounded Mode = "bounded"
	// ModeStanding forces the watch-loop contract.
	ModeStanding Mode = "standing"
)

type RefOverrides added in v2.9.0

type RefOverrides struct {
	Goal    string
	Model   string
	Tools   []string
	Budgets Budgets
}

RefOverrides are the narrowing-only adjustments a caller may layer on top of a referenced predefined spec (#626). Every field is optional; the zero value means "take the spec's value unchanged".

Narrowing semantics (D2/D5):

  • Goal replaces the spec's goal outright (a template spec typically carries no goal; the parent supplies the task per spawn).
  • Model may only be "" / "inherit" (keep the spec's model) or "small" (downshift to the manager's small tier). A specific model is rejected.
  • Tools, when non-empty, must be a SUBSET of the spec's granted tools+extras — it can only drop, never add.
  • Budgets tighten only: a smaller positive cap wins; a larger or zero override is ignored.

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
	// Description is a one-line summary of what this subagent is for.
	// Surfaced to the operator catalog (#627) and — for a predefined
	// spec, which the parent can reference by name — into the
	// spawn_agent schema the model routes from (#640). Optional, but a
	// spec without one is harder for the parent to route to.
	Description 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
	// ModelID selects the model this subagent runs on. Empty means
	// "inherit the manager's model" (m.modelID — typically the parent's).
	// A predefined spec carries its operator-configured model here; a
	// per-spawn "small" override rewrites it to the manager's small-tier
	// model id (see resolvePredefinedSpec / WithSmallModelID, #626).
	ModelID string
	// 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
	// Mode selects how the subagent's loop terminates. Empty derives
	// it from Scheduler; see Mode.
	Mode Mode
	// Ref is the predefined-spec name this spec was resolved from, set
	// by the reference path (resolvePredefinedSpec) and empty for an
	// ad-hoc, inline-authored one. Name is rewritten to a per-instance
	// name before the spawn ("triage-2"), so Ref is what survives to
	// say WHICH configured subagent is running — the self-spawn guard
	// matches on it (#732).
	Ref 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
)

func (Status) String

func (s Status) String() string

String renders the status for tool results and diagnostics.

type StopClass added in v2.9.0

type StopClass string

StopClass is the machine-readable answer to the one question a delegating agent has to answer about a returned result: is this finished, or is it a partial (#730)?

Removing the "continue" re-drive from a bounded delegation means a subagent that runs out of room hands back what it has. That is the right contract — the parent holds the goal and can re-ask with specifics, which a blind "continue" injected inside the subagent cannot — but only if the parent can tell the two apart. Prose in a text blob is not sufficient.

const (
	// StopNatural: the subagent finished — it stopped asking for tools
	// (bounded) or signalled completion (standing). The output is the
	// deliverable.
	StopNatural StopClass = "natural"
	// StopMaxSteps: the turn cap fired. A partial; re-ask with what is
	// still missing, or raise MaxTurns.
	StopMaxSteps StopClass = "max_steps"
	// StopBudget: a cost, token, or wall-clock bound fired. A partial.
	StopBudget StopClass = "budget"
	// StopDeferred: the subagent scheduled its own next turn and will
	// resume. Not a partial to re-ask — it isn't done with the loop.
	StopDeferred StopClass = "deferred"
	// StopStopped: the parent (or operator) stopped it. Whatever text
	// exists was cut mid-thought.
	StopStopped StopClass = "stopped"
	// StopError: the run failed, was cancelled, or exhausted its retry
	// policy.
	StopError StopClass = "error"
)

type SubagentTemplate added in v2.9.0

type SubagentTemplate struct {
	// Name is the reference key (spawn_agent {agent: Name}) and seeds
	// auto-derived instance names ("cluster-1"). Must be branch-safe.
	Name string
	// Description is the operator-facing summary (backs the #627 catalog).
	Description string
	// Root is the subagent's content root (its own AGENTS.md + mcp.json +
	// skills/ tree), relative to the recipe as authored in config. Empty
	// for an inline (non-rooted) declarative subagent. Display-only —
	// surfaced in the #627 operator catalog so operators can see which
	// subagents carry their own content bundle.
	Root string
	// ModelFactory builds a fresh LLM for each spawn — session isolation,
	// same as the catalog path's provider.Model call. Required.
	ModelFactory func(context.Context) (adkmodel.LLM, error)
	// ModelID labels the template's model for /usage pricing attribution.
	ModelID string
	// Instruction is the fully-resolved persona, installed as layer 4
	// (user memory) exactly like the synchronous declarative path.
	Instruction string
	// Tools are the built-in tools (already resolved to instances) the
	// subagent runs with. Shared, stateless — safe across instances.
	Tools []tool.Tool
	// Toolsets are the MCP + skills groups. Process-long-lived, stateless
	// handles — shared across concurrent instances of the template.
	Toolsets []tool.Toolset
	// MaxDepth caps the subagent's OWN nesting (0 = substrate default).
	MaxDepth int
	// Budgets bound each async run; per-spawn overrides may only tighten.
	Budgets Budgets
	// Scheduler is the between-turn scheduler choice ("" = manager
	// default); see resolveScheduler for the accepted values.
	Scheduler string
	// Mode selects how the run terminates. Empty derives it from the
	// resolved Scheduler; see Mode.
	Mode Mode
}

SubagentTemplate is a predefined declarative subagent, pre-resolved for async-by-reference spawning. Unlike a catalog Spec (persona + tool NAMES resolved against the manager's catalog at spawn time), a template already carries built tool instances, MCP + skills toolsets, and a model factory — because a declarative subagent may be rooted (its own content root, mcp.json, and skills/ tree) and can't be reconstructed from the parent's catalog.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL