subagent

package
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 18, 2026 License: MIT Imports: 18 Imported by: 0

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

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

Variables

View Source
var ErrMaxConcurrent = errors.New("subagent: max concurrent agents reached")

ErrMaxConcurrent is returned when Spawn cannot acquire a slot.

Functions

func Sweep

func Sweep(ctx context.Context, parentWorkDir string) error

Sweep removes every stale m31a/agent-* branch that no longer has a worktree. Call this at startup to recover from crashes.

Types

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
}

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 EventType

type EventType string

EventType discriminates the SubagentEvent variants below.

const (
	EventSpawned   EventType = "spawned"
	EventToolStart EventType = "tool_start"
	EventToolDone  EventType = "tool_done"
	EventTextDelta EventType = "text_delta"
	EventThinking  EventType = "thinking"
	EventDone      EventType = "done"
	EventError     EventType = "error"
	EventCancelled EventType = "cancelled"
)

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.

func (*GitWorktrees) Remove

func (g *GitWorktrees) Remove(ctx context.Context, path string) error

Remove deletes the worktree at path and its branch. Idempotent.

type Isolation

type Isolation string

Isolation controls workspace isolation for a subagent.

const (
	// IsolationWorktree creates a git worktree + branch for the subagent.
	IsolationWorktree Isolation = "worktree"
	// IsolationDefault shares the parent working directory.
	IsolationDefault Isolation = "default"
)

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

func (m *Manager) Cancel(id string)

Cancel requests cancellation of a specific subagent.

func (*Manager) CancelAll

func (m *Manager) CancelAll()

CancelAll requests cancellation of every running subagent.

func (*Manager) Cleanup

func (m *Manager) Cleanup(ctx context.Context, id string) error

Cleanup removes a subagent's workspace (best-effort) and forgets it.

func (*Manager) Events

func (m *Manager) Events() <-chan SubagentEvent

Events returns the read-only event stream consumed by the TUI or tests.

func (*Manager) Get

func (m *Manager) Get(id string) *Subagent

Get returns a subagent by ID, or nil if unknown.

func (*Manager) List

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

List returns a snapshot of every known subagent (any status).

func (*Manager) Shutdown

func (m *Manager) Shutdown(ctx context.Context)

Shutdown cancels all subagents, waits for completion, and closes the event channel. Safe to call once at application exit.

func (*Manager) Spawn

func (m *Manager) Spawn(parentCtx context.Context, req SpawnRequest) (string, *Subagent, error)

Spawn starts a new subagent and returns its ID. If req.Background is false, Spawn blocks until the subagent finishes.

type SpawnRequest

type SpawnRequest struct {
	Description string    // short 3-5 word label
	Prompt      string    // full task description
	Name        string    // optional stable name
	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 Status

type Status string

Status describes a subagent's lifecycle state.

const (
	StatusPending Status = "pending"
	StatusRunning Status = "running"
	StatusDone    Status = "done"
	StatusError   Status = "error"
	StatusCancel  Status = "cancelled"
)

type Subagent

type Subagent struct {
	Info SubagentInfo
	// 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"`
	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"`
	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 ToolCallOutput struct {
	ToolCallID string `json:"tool_call_id"`
	Output     string `json:"output"`
	Error      string `json:"error,omitempty"`
	DurationMs int64  `json:"duration_ms"`
}

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

Jump to

Keyboard shortcuts

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