Help us shape the future of Go! Take the 2026 Go Developer Survey

subagents

package
v2.0.0 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package subagents provides foreground and background subagent spawning for task delegation. A subagent is a fully independent agent loop with its own LLM client, tool registry, and message history. It inherits the parent's config, persona, grimoire, and permissions but runs in isolation. Subagents may run in the foreground (blocking) or transition to background after a configurable threshold (#30), delivering their result via a channel.

Index

Constants

This section is empty.

Variables

View Source
var StaggerDelay = 500 * time.Millisecond

StaggerDelay is the configurable pause between concurrent subagent launches to avoid hitting provider rate limits. Zero means no delay. Default 500ms — safe for xAI's grok-build-0.1 at 6 concurrent agents.

Functions

func MergeWorktree

func MergeWorktree(repo string, wt *Worktree) error

MergeWorktree merges the worktree's branch back into the repo's current branch. Returns an error on conflict so the caller can surface it.

func RemoveWorktree

func RemoveWorktree(repo string, wt *Worktree) error

RemoveWorktree force-removes the worktree dir and deletes its branch.

Types

type DAGEntry

type DAGEntry struct {
	Run             *SubagentRun
	Goal            string // the full goal including persona overrides
	Workspace       string
	TurnCb          TurnCallback
	MaxTurns        int
	IsolateWorktree bool
	ResultCh        chan *SubagentRun // result sent here when complete
	// contains filtered or unexported fields
}

DAGEntry is a queued subagent waiting for dependencies to clear.

type Finding

type Finding struct {
	Title    string `json:"title"`
	Detail   string `json:"detail,omitempty"`
	Severity string `json:"severity,omitempty"`
	File     string `json:"file,omitempty"`
	Line     int    `json:"line,omitempty"`
}

Finding is one thing a typed subagent found.

type Mailbox

type Mailbox struct {
	// contains filtered or unexported fields
}

Mailbox is a concurrency-safe per-address message store keyed by an agent's address (its element name). Drain is destructive.

func NewMailbox

func NewMailbox() *Mailbox

NewMailbox creates a new empty Mailbox.

func (*Mailbox) Drain

func (m *Mailbox) Drain(to string) []Message

Drain returns and clears all messages for an address.

func (*Mailbox) Post

func (m *Mailbox) Post(to, from, body string)

Post appends a message to the recipient's mailbox.

type Manager

type Manager struct {

	// OnBackgroundComplete is an optional callback invoked when a backgrounded
	// subagent finishes. The TUI may set this to surface a completion notice.
	// It is called from a goroutine; implementations must be goroutine-safe.
	// nil = no notification (default; non-TUI callers are unaffected).
	OnBackgroundComplete func(*SubagentRun)
	// contains filtered or unexported fields
}

Manager handles subagent lifecycle and execution.

func NewManager

func NewManager(cfg *config.Config, workspace string, isChild bool) *Manager

NewManager creates a subagent manager. Pass isChild=true when the manager itself is running inside a subagent to block recursive spawning.

func (*Manager) Close

func (m *Manager) Close()

Close releases the subagents' shared environment (one UseParent supplied stays open: its owner closes it). Warnings stop first, so none reaches the sink after Close, even from the environment's own shutdown. Every in-flight run — running, background, or waiting in the DAG queue — is cancelled next: a background run is detached (context.Background(), so only /agents kill or Close stops it) and otherwise keeps a reference on the shared Env forever, so the owner's own Close (MCP.Stop, code graph Close) never runs (F2e M1). Close waits up to closeRunWait for cancelled runs to finish and release that reference before returning, so a caller that closes its own Env right after Close sees the shared resources actually freed. A spawn after Close fails cleanly.

func (*Manager) GetRun

func (m *Manager) GetRun(id string) (*SubagentRun, bool)

GetRun returns a subagent run by ID.

func (*Manager) Kill

func (m *Manager) Kill(selector string) bool

Kill cancels a specific in-flight subagent (task 6ffb5a7c). The selector may be the run id, the task id, OR the element/display name the user sees in /agents (e.g. "mizu" or "water") — users refer to agents by the name on screen, not the internal id (#d15ac448). Returns true if a cancellable run was found. The run's context is cancelled (which the runtime honors — task 349f1f14) and its status is marked failed so ListRuns reflects the kill immediately. A run whose cancel is gone (it finished and was merged or discarded) is left untouched and returns false.

func (*Manager) ListRuns

func (m *Manager) ListRuns() []*SubagentRun

ListRuns returns all subagent runs, most recent first.

func (*Manager) Resume

func (m *Manager) Resume(ctx context.Context, checkpointID string, turnCb TurnCallback) (*SubagentRun, error)

Resume continues a previously-failed subagent from its last checkpoint. checkpointID is the RunID returned by RunGoal and stored in SubagentRun.CheckpointID. It constructs a runner with the same options as executeSubagent so the resumed run shares the same workspace, turn limits, and callback wiring.

func (*Manager) Spawn

func (m *Manager) Spawn(ctx context.Context, goal string, workspace string, turnCb ...TurnCallback) (*SubagentRun, error)

Spawn creates and runs a subagent with the given goal. It blocks until the subagent completes or the context is cancelled.

func (*Manager) SpawnAsync

func (m *Manager) SpawnAsync(ctx context.Context, goal, workspace string, opts SpawnOptions) (<-chan *SubagentRun, *SubagentRun)

SpawnAsync starts a subagent in a goroutine and returns a buffered channel that receives the final *SubagentRun when it completes, plus the live run handle (status "running"). The channel has capacity 1 so the exec goroutine never blocks or leaks if the caller abandons the channel. The caller may wait on the channel or ignore it (e.g., after transitioning to background).

SpawnAsync does NOT handle DAG dependencies; use SpawnWithOptions for that.

func (*Manager) SpawnWithOptions

func (m *Manager) SpawnWithOptions(ctx context.Context, goal string, workspace string, opts SpawnOptions) (*SubagentRun, error)

SpawnWithOptions creates and runs a subagent with full options including DAG dependencies. If depends_on contains task IDs that haven't completed yet, the subagent blocks in "waiting" state until all dependencies clear, then auto-starts.

When opts.BackgroundAfter > 0, the call blocks up to that duration waiting for the subagent to finish. If the subagent is still running at the threshold, it transitions to "background" status and SpawnWithOptions returns the live (background) run immediately so the parent can resume. The result is later delivered via a goroutine that updates the run state and invokes m.OnBackgroundComplete (if set).

Default behavior (BackgroundAfter == 0) is unchanged: fully blocking.

func (*Manager) UseParent

func (m *Manager) UseParent(p loop.Nester, warn func(string))

UseParent makes every subagent nest under p and report its own setup and hook warnings to warn. The chat passes its Env (2.0 F2e): subagents share its MCP clients, hooks (and its session_id) and code graph instead of starting their own. The manager never closes p; its owner does. Call it before the first spawn.

type Message

type Message struct {
	From string
	To   string
	Body string
}

Message is a single inter-agent message.

type PostMessageTool

type PostMessageTool struct {
	// contains filtered or unexported fields
}

PostMessageTool is a built-in tool that allows a subagent to post a message to another subagent's mailbox. The recipient is identified by its element name (e.g. "fire", "water"). Messages are delivered at spawn time when the recipient agent next starts (spawn-time injection; live mid-run injection is a follow-up, see issue #31).

func NewPostMessageTool

func NewPostMessageTool(manager *Manager, from string) *PostMessageTool

NewPostMessageTool creates a post_message tool backed by the given manager. from should be the element name of the agent that holds this tool instance, or "parent" when the tool is held by the top-level orchestrator.

func (*PostMessageTool) Description

func (t *PostMessageTool) Description() string

func (*PostMessageTool) Execute

func (t *PostMessageTool) Execute(_ context.Context, input map[string]any, _ chan<- tools.ProgressEvent) (tools.ToolResult, error)

func (*PostMessageTool) InterruptBehavior

func (t *PostMessageTool) InterruptBehavior() tools.InterruptBehavior

func (*PostMessageTool) IsConcurrencySafe

func (t *PostMessageTool) IsConcurrencySafe(input map[string]any) bool

func (*PostMessageTool) IsReadOnly

func (t *PostMessageTool) IsReadOnly() bool

func (*PostMessageTool) Name

func (t *PostMessageTool) Name() string

func (*PostMessageTool) Parameters

func (t *PostMessageTool) Parameters() json.RawMessage

func (*PostMessageTool) ValidateInput

func (t *PostMessageTool) ValidateInput(input map[string]any) error

type Profile

type Profile struct {
	Allow        func(tools.Tool) bool // nil: every tool
	PersonaLevel prompts.PersonaLevel
	Model        string
}

Profile is what a Type sets on a subagent's run.

type Result

type Result struct {
	Summary  string    `json:"summary"`
	Findings []Finding `json:"findings"`
	Files    []string  `json:"files"`
	Warning  string    `json:"warning,omitempty"`
}

Result is what a typed subagent hands its parent: {summary, findings[], files[]}, plus a warning when the run never called submit_result.

func ValidateResult

func ValidateResult(raw map[string]any) (Result, []string)

ValidateResult checks raw against the result schema and returns the result, or every problem found, each naming its field. Unknown keys are rejected. Findings and Files are never nil in a valid result.

type SpawnAgentTool

type SpawnAgentTool struct {
	// contains filtered or unexported fields
}

SpawnAgentTool is a built-in tool that allows the model to spawn subagents for task delegation. The subagent runs in the foreground (blocks until complete) and returns its final result.

func NewSpawnAgentTool

func NewSpawnAgentTool(manager *Manager) *SpawnAgentTool

NewSpawnAgentTool creates a new spawn_agent tool backed by the given manager.

func (*SpawnAgentTool) Description

func (t *SpawnAgentTool) Description() string

func (*SpawnAgentTool) Execute

func (t *SpawnAgentTool) Execute(ctx context.Context, input map[string]any, progress chan<- tools.ProgressEvent) (tools.ToolResult, error)

func (*SpawnAgentTool) InterruptBehavior

func (t *SpawnAgentTool) InterruptBehavior() tools.InterruptBehavior

func (*SpawnAgentTool) IsConcurrencySafe

func (t *SpawnAgentTool) IsConcurrencySafe(input map[string]any) bool

func (*SpawnAgentTool) IsReadOnly

func (t *SpawnAgentTool) IsReadOnly() bool

func (*SpawnAgentTool) Name

func (t *SpawnAgentTool) Name() string

func (*SpawnAgentTool) Parameters

func (t *SpawnAgentTool) Parameters() json.RawMessage

func (*SpawnAgentTool) Timeout

func (t *SpawnAgentTool) Timeout() time.Duration

Timeout gives a subagent 10 minutes; it manages its own turn limits.

func (*SpawnAgentTool) ValidateInput

func (t *SpawnAgentTool) ValidateInput(input map[string]any) error

type SpawnOptions

type SpawnOptions struct {
	TaskID          string               // user-assigned task ID for DAG references
	DependsOn       []string             // task IDs that must complete before this starts
	TurnCb          TurnCallback         // nested progress callback
	MaxTurns        int                  // 0 = default (20)
	IsolateWorktree bool                 // run this subagent in its own git worktree (#32)
	BackgroundAfter time.Duration        // >0: auto-transition to background if the subagent runs longer than this (#30). 0 = always foreground (unchanged).
	Sliders         *config.SliderConfig // persona override for the subagent's voice modulation; nil = slider.json
	Type            Type                 // subagent type (2.0 W4e); "" = untyped
}

SpawnOptions holds optional parameters for Spawn.

type SubagentRun

type SubagentRun struct {
	ID           string    `json:"id"`
	TaskID       string    `json:"task_id,omitempty"` // user-assigned ID for DAG dependencies
	Name         string    `json:"name"`              // display name (e.g., "火 hi")
	Element      string    `json:"element"`           // english element (e.g., "fire")
	Goal         string    `json:"goal"`
	Workspace    string    `json:"workspace"`
	Status       string    `json:"status"`               // "waiting", "running", "background", "completed", "failed"
	DependsOn    []string  `json:"depends_on,omitempty"` // task_ids that must complete first
	Result       string    `json:"result"`
	Error        string    `json:"error,omitempty"`
	StartedAt    time.Time `json:"started_at"`
	EndedAt      time.Time `json:"ended_at,omitempty"`
	Turns        int       `json:"turns"`
	CheckpointID string    `json:"checkpoint_id,omitempty"` // run id to resume from on failure
	// Type is the subagent type spawn_agent asked for (2.0 W4e); "" for a
	// run spawned without one (Manager.Spawn), which keeps the untyped
	// behaviour.
	Type Type `json:"type,omitempty"`
	// Summary is a typed run's result summary (Result holds its JSON), for
	// /agents. Empty for an untyped run.
	Summary string `json:"summary,omitempty"`
	// contains filtered or unexported fields
}

SubagentRun tracks the state and result of a spawned subagent.

type SubmitResultTool

type SubmitResultTool struct {
	// contains filtered or unexported fields
}

SubmitResultTool records a typed subagent's result (2.0 W4e). It only records, so it is read-only: every type keeps it and it never prompts.

func NewSubmitResultTool

func NewSubmitResultTool(h *resultHolder) *SubmitResultTool

NewSubmitResultTool returns a submit_result tool that stores into h.

func (*SubmitResultTool) Description

func (t *SubmitResultTool) Description() string

func (*SubmitResultTool) Execute

func (t *SubmitResultTool) Execute(_ context.Context, input map[string]any, _ chan<- tools.ProgressEvent) (tools.ToolResult, error)

func (*SubmitResultTool) InterruptBehavior

func (t *SubmitResultTool) InterruptBehavior() tools.InterruptBehavior

func (*SubmitResultTool) IsConcurrencySafe

func (t *SubmitResultTool) IsConcurrencySafe(map[string]any) bool

func (*SubmitResultTool) IsReadOnly

func (t *SubmitResultTool) IsReadOnly() bool

func (*SubmitResultTool) Name

func (t *SubmitResultTool) Name() string

func (*SubmitResultTool) Parameters

func (t *SubmitResultTool) Parameters() json.RawMessage

func (*SubmitResultTool) ValidateInput

func (t *SubmitResultTool) ValidateInput(map[string]any) error

ValidateInput accepts everything: Execute validates, so the model gets every problem back as a tool result it can fix.

type TurnCallback

type TurnCallback func(turn int, maxTurns int, toolName string)

TurnCallback is called on each subagent turn so the parent can display nested progress. turn is 1-indexed, toolName is the tool being called on this turn (empty if the turn is a text response).

type Type

type Type string

Type is a subagent type (2.0 W4e): it picks the subagent's tools, model and whether its persona is composed.

const (
	// TypeExplore investigates: read-only tools, the off persona, the small
	// model.
	TypeExplore Type = "explore"
	// TypeGeneral does the work: every tool, the persona, the agent model.
	TypeGeneral Type = "general"
	// TypeReview reviews code: read and code-graph tools, the off persona,
	// the agent model.
	TypeReview Type = "review"
)

func ParseType

func ParseType(s string) (Type, error)

ParseType reads spawn_agent's type argument. An empty one is general.

type Worktree

type Worktree struct {
	Path   string
	Branch string
}

Worktree is an isolated git worktree for a subagent.

func AddWorktree

func AddWorktree(repo, name string) (*Worktree, error)

AddWorktree creates a worktree for `name` on a new branch under <repo>/.celeste/worktrees/<name>.

Jump to

Keyboard shortcuts

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