background

package
v2.8.0-dev.3 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

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 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 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 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 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 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

func NewCheckAgentTool(mgr *Manager) tool.Tool

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

func NewListAgentsTool(mgr *Manager) tool.Tool

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

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 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

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

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

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.

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

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) 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) Close

func (m *Manager) Close() error

Close stops every running subagent and prevents new spawns. Blocks until each goroutine has exited so callers don't race with shutdown. 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) 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 check_agent can return final status). Defensive copy of slice.

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) 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) 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 RunAutonomous 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

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

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.

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 string
	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
)

func (Status) String

func (s Status) String() string

String renders the status for tool results and diagnostics.

Jump to

Keyboard shortcuts

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