Documentation
¶
Overview ¶
Package subagent implements parallel child-agent execution.
A subagent is a lightweight agentic loop that runs in its own goroutine with its own working directory (typically a git worktree), its own tool dispatcher, and its own message history. It communicates with the parent through a typed event channel; no shared Bubble Tea state is touched.
Index ¶
- Constants
- Variables
- func ApplyToolFilter(dispatcher ToolDispatcher, profile AgentProfile)
- func BuiltinProfiles() map[string]AgentProfile
- func Sweep(ctx context.Context, parentWorkDir string) error
- type AgentProfile
- type Dependencies
- type DispatcherFactory
- type EventType
- type GitWorktrees
- type Isolation
- type Manager
- func (m *Manager) Cancel(id string)
- func (m *Manager) CancelAll()
- func (m *Manager) Cleanup(ctx context.Context, id string) error
- func (m *Manager) Events() <-chan SubagentEvent
- func (m *Manager) Get(id string) *Subagent
- func (m *Manager) List() []SubagentInfo
- func (m *Manager) Shutdown(ctx context.Context)
- func (m *Manager) Spawn(parentCtx context.Context, req SpawnRequest) (string, *Subagent, error)
- type SpawnRequest
- type Status
- type Subagent
- type SubagentEvent
- type SubagentInfo
- type ToolCallInput
- type ToolCallOutput
- type ToolDescriptor
- type ToolDispatcher
- type WorktreeOps
Constants ¶
const ( // MaxConcurrent is the maximum number of subagents running at once. MaxConcurrent = 8 // DefaultMaxTools is the per-subagent tool-call budget. DefaultMaxTools = 50 // DefaultMaxTokens is the per-subagent token budget (input + output). DefaultMaxTokens = 50_000 // MaxTotalSubagents is the maximum total subagents that can be spawned // in a single session to prevent runaway resource consumption. MaxTotalSubagents = 50 // MaxSpawnRate is the maximum number of subagents that can be spawned // per minute to prevent rapid resource exhaustion. MaxSpawnRate = 10 )
const ( ModePrimary = "primary" ModeSubagent = "subagent" ModeAll = "all" )
Variables ¶
var ErrMaxConcurrent = errors.New("subagent: max concurrent agents reached")
ErrMaxConcurrent is returned when Spawn cannot acquire a slot.
Functions ¶
func ApplyToolFilter ¶ added in v1.3.0
func ApplyToolFilter(dispatcher ToolDispatcher, profile AgentProfile)
ApplyToolFilter removes tools from a ToolDispatcher based on the profile's AllowedTools and DeniedTools. If AllowedTools is non-empty, only listed tools are kept. DeniedTools are then removed from whatever remains.
func BuiltinProfiles ¶ added in v1.3.0
func BuiltinProfiles() map[string]AgentProfile
BuiltinProfiles returns the 6 built-in agent profiles.
Types ¶
type AgentProfile ¶ added in v1.3.0
type AgentProfile struct {
Name string
Description string
Mode string // "primary", "subagent", "all"
SystemPrompt string
Model string // override model ID; "" = inherit parent
Hidden bool
// Tool scoping: AllowedTools is an allowlist (empty = all tools).
// DeniedTools is a denylist applied after the allowlist.
AllowedTools []string
DeniedTools []string
// Resource budgets (0 = use manager defaults).
MaxTools int
MaxTokens int
MaxTurns int
}
AgentProfile defines a named subagent type with its own system prompt, tool access, model assignment, and resource budgets. Profiles can be built-in or user-defined via config overrides.
func ListSubagentProfiles ¶ added in v1.3.0
func ListSubagentProfiles(overrides map[string]config.SubagentProfileConfig) []AgentProfile
ListSubagentProfiles returns all non-hidden profiles where Mode is "subagent" or "all", sorted by name. Used to populate the Agent tool description so the LLM knows which subagent types are available.
func ResolveProfile ¶ added in v1.3.0
func ResolveProfile(name string, overrides map[string]config.SubagentProfileConfig) (AgentProfile, bool)
ResolveProfile returns the resolved AgentProfile for a given name. It starts with the built-in default (if one exists) and applies user overrides from the config. If the profile is disabled via config, the second return value is false. If no built-in profile exists for the name, a custom profile is created from the overrides alone (Mode defaults to "all").
type Dependencies ¶
type Dependencies struct {
WorkDir string
Registry *provider.Registry
ActiveModel *types.ModelInfo
Logger *slog.Logger
Worktrees WorktreeOps // optional; nil = always use IsolationDefault
NewDispatcher DispatcherFactory // required: builds a dispatcher per workspace
Profiles map[string]config.SubagentProfileConfig // optional: user profile overrides
}
Dependencies are shared resources the Manager needs.
type DispatcherFactory ¶
type DispatcherFactory func(workDir string) (ToolDispatcher, error)
DispatcherFactory builds a fresh ToolDispatcher scoped to a subagent's workspace. This lets the parent wire a real tools.Dispatcher without the subagent package importing internal/tools.
type GitWorktrees ¶
type GitWorktrees struct {
// Root is the directory under which worktree subdirectories are created.
// Defaults to filepath.Join(parentWorkDir, ".m31a-worktrees") when empty.
Root string
}
GitWorktrees is a WorktreeOps implementation backed by the local git binary via internal/git. It creates worktrees under a sibling directory of the main repo so they share the same .git object store.
func (*GitWorktrees) Create ¶
func (g *GitWorktrees) Create(ctx context.Context, parentWorkDir, agentID, branchSuffix string) (string, error)
Create provisions a git worktree at <root>/<agentID> on branch m31a/agent-<agentID>[-<suffix>], starting from the current HEAD.
func (*GitWorktrees) IsRepo ¶
func (g *GitWorktrees) IsRepo(parentWorkDir string) bool
IsRepo reports whether parentWorkDir lives inside a git repository.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager orchestrates subagent lifecycles.
func NewManager ¶
func NewManager(deps Dependencies) *Manager
NewManager creates a Manager ready to accept spawns.
func (*Manager) CancelAll ¶
func (m *Manager) CancelAll()
CancelAll requests cancellation of every running subagent.
func (*Manager) Events ¶
func (m *Manager) Events() <-chan SubagentEvent
Events returns the read-only event stream consumed by the TUI or tests.
func (*Manager) List ¶
func (m *Manager) List() []SubagentInfo
List returns a snapshot of every known subagent (any status).
type SpawnRequest ¶
type SpawnRequest struct {
Description string // short 3-5 word label
Prompt string // full task description
Name string // optional stable name
SubagentType string // agent profile name ("explore", "general", etc.); empty = "general"
Isolation Isolation // worktree or default
Background bool // if true, return immediately; if false, block until done
ModelID string // empty = inherit parent
MaxTools int // 0 = default (50)
MaxTokens int // 0 = default (50_000)
}
SpawnRequest is the input to Manager.Spawn.
type Subagent ¶
type Subagent struct {
Info SubagentInfo
Profile AgentProfile // resolved agent profile
// contains filtered or unexported fields
}
Subagent is the live handle for a running child agent.
type SubagentEvent ¶
type SubagentEvent struct {
Type EventType `json:"type"`
AgentID string `json:"agent_id"`
Name string `json:"name,omitempty"`
SubagentType string `json:"subagent_type,omitempty"`
Timestamp time.Time `json:"ts"`
// Spawned
Worktree string `json:"worktree,omitempty"`
// ToolStart / ToolDone
ToolCallID string `json:"tool_call_id,omitempty"`
ToolName string `json:"tool_name,omitempty"`
ToolInput string `json:"tool_input,omitempty"` // abbreviated for display
ToolOutput string `json:"tool_output,omitempty"`
ToolError string `json:"tool_error,omitempty"`
DurationMs int64 `json:"duration_ms,omitempty"`
// TextDelta / Thinking
Delta string `json:"delta,omitempty"`
// Done
Summary string `json:"summary,omitempty"`
ToolCalls int `json:"tool_calls,omitempty"`
InputToks int `json:"input_tokens,omitempty"`
OutputToks int `json:"output_tokens,omitempty"`
Usage *types.Usage `json:"usage,omitempty"`
// Error
Error string `json:"error,omitempty"`
}
SubagentEvent is emitted by a running subagent. Consumers (the TUI, the parent tool result collector) read these from Manager.Events().
Tagged union — interpret fields based on Type.
func (SubagentEvent) MarshalJSON ¶
func (e SubagentEvent) MarshalJSON() ([]byte, error)
MarshalJSON guards against nil slices and ensures stable output.
type SubagentInfo ¶
type SubagentInfo struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
SubagentType string `json:"subagent_type,omitempty"`
Description string `json:"description"`
Status Status `json:"status"`
Isolation Isolation `json:"isolation"`
Worktree string `json:"worktree,omitempty"`
ModelID string `json:"model_id"`
StartedAt time.Time `json:"started_at"`
FinishedAt time.Time `json:"finished_at,omitempty"`
// Counters
ToolCalls int `json:"tool_calls"`
InputToks int `json:"input_tokens"`
OutputToks int `json:"output_tokens"`
// Last activity for the expanded card
LastToolName string `json:"last_tool_name,omitempty"`
LastToolStatus string `json:"last_tool_status,omitempty"` // "running" | "done"
LastSummary string `json:"last_summary,omitempty"`
LastError string `json:"last_error,omitempty"`
}
SubagentInfo is a snapshot for TUI rendering and /agent listing.
type ToolCallInput ¶
type ToolCallInput struct {
ID string `json:"id"`
Name string `json:"name"`
Input json.RawMessage `json:"input"`
}
ToolCallInput is the minimum shape the subagent loop needs from a tool dispatcher. It mirrors types.ToolCall/types.ToolResult so the subagent package does not import internal/tools (which would create a cycle).
type ToolCallOutput ¶
type ToolDescriptor ¶
type ToolDescriptor struct {
Name string
Description string
// ParameterSchema is the JSON Schema string; empty means "{}".
ParameterSchema string
}
ToolDescriptor describes a single tool for the provider tool-definition payload. The loop uses these to advertise available tools to the LLM.
type ToolDispatcher ¶
type ToolDispatcher interface {
Execute(ctx context.Context, call ToolCallInput) (ToolCallOutput, error)
ListTools() []ToolDescriptor
UnregisterTool(name string)
Stop()
}
ToolDispatcher abstracts the subset of tools.Dispatcher that the subagent loop uses. The concrete Dispatcher satisfies this interface without modification.
type WorktreeOps ¶
type WorktreeOps interface {
Create(ctx context.Context, parentWorkDir, agentID, branchSuffix string) (path string, err error)
Remove(ctx context.Context, path string) error
IsRepo(parentWorkDir string) bool
}
WorktreeOps abstracts git worktree operations so the manager can be tested without a real git repo. The production implementation lives in internal/tools/subagent/worktree.go.