subagent

package
v0.37.3 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrUnknownJob = errors.New("unknown job ID")
	ErrNotSync    = errors.New("subagent is already async")
	ErrNotRunning = errors.New("subagent already finished")
)

Sentinel errors returned by promote/Promote, distinguishable by callers (e.g. pkg/serve maps them to specific HTTP statuses).

Functions

func WithReadOnlyFiles added in v0.35.0

func WithReadOnlyFiles(ctx context.Context, files []string) context.Context

WithReadOnlyFiles passes file-specific read capability to a child launch. It is deliberately context-bound rather than a subagent tool parameter: the model must not be able to request arbitrary paths for its child.

Types

type Config

type Config struct {
	DefaultModel           core.Model
	CurrentModel           func() core.Model
	CurrentThinkingLevel   func() string
	CurrentPermissionCheck func() func(ctx context.Context, name string, args map[string]any) *core.ToolCallDecision
	ProviderFactory        func(core.Model) (core.Provider, error)
	// CompactSummarizer optionally routes compaction summaries to the globally
	// configured model instead of the child's own. nil = the child's model.
	CompactSummarizer func(sessionModel core.Model) (core.Provider, core.Model, string)
	AgentsMD          string
	PromptBuilder     func(opts agentcontext.SystemPromptOptions) string
	ParentTools       *core.Registry
	AppCtx            context.Context
	WorkspaceRoot     string // CWD passed to system prompt builder
	SkillsIndex       string // pre-formatted skills index for system prompt
	MemoryIndex       string // pre-formatted memory index (one line per fact)
	// Sources, when set, supersedes AgentsMD/SkillsIndex/MemoryIndex: children
	// spawned after a /reload must inherit what the parent knows now, not the
	// prompt inputs captured when the session was built.
	Sources *agentcontext.Sources

	// PromptCacheKey is the PARENT's cache-routing key. Children of the same
	// session share one derived key: they reuse the same instructions+tools
	// prefix (measured: ~half of gpt-5.6-terra first requests already hit),
	// so splitting by job id would send siblings to different machines and
	// miss that prefix. Still distinct from the parent, whose tool set differs.
	// Empty = no key.
	PromptCacheKey string
	// StreamRepairBackoff optionally overrides the waits before stream repair
	// attempts for child agents. Nil uses the agent production defaults.
	StreamRepairBackoff []time.Duration

	// BashState, when non-nil, is the per-agent persistent shell state. The
	// subagent seeds an isolated copy for the child (subshell semantics) and
	// drops it when the child finishes. nil = no shell-state isolation.
	BashState *tool.BashState

	// AttachmentScope is the PARENT session's attachment capability, handed to
	// every child agent through its own AgentConfig. It cannot be inherited from
	// the context: a child's context is derived from AppCtx, not from the tool
	// call that spawned it, so anything the parent's run installed is simply not
	// there. Children persist their sidecar transcript, so they do externalize —
	// always owned by the PARENT session ID, never the job ID: the store's GC
	// only knows main sessions, so a job-owned blob would be collected on
	// restart. nil = children work inline.
	AttachmentScope *attachment.Scope

	// OnAsyncComplete is called when an async subagent finishes (completed, failed, or cancelled).
	// truncated is true when resultTail is only the last N lines of the full output.
	OnAsyncComplete func(jobID, task, status, resultTail string, truncated bool)

	// OnAsyncJobChange is called when an async job starts or finishes.
	// count is the current number of running jobs.
	OnAsyncJobChange func(count int)

	// OnChildStart is called right before a child agent (sync or async) begins
	// running, with its jobID/task/model/thinking level, whether it's async, its start
	// time (so live UIs can compute elapsed and reconcile it after a reconnect),
	// and its stable per-session creation ordinal (accentIndex) for a
	// deterministic accent color that survives reconnects.
	OnChildStart func(jobID, task, model, thinking, originToolCallID string, async bool, startedAt time.Time, accentIndex int)

	// OnChildEvent is called for each typed bus event produced by translating
	// the child's core.AgentEvent stream (via bus.TranslateAgentEvent). inner
	// is already a concrete bus.* type (e.g. bus.TextDelta), never a raw
	// core.AgentEvent — pkg/subagent imports pkg/bus directly (no import
	// cycle: pkg/bus does not import pkg/subagent), so translation happens
	// here rather than at the call site.
	OnChildEvent func(jobID string, inner any)

	// OnChildUsage is called each time a child closes a message (its
	// message_end), with the child's accumulated usage/cost so far (cost using
	// the CHILD's model pricing) and how full its own context window is
	// (0-100, or -1 when its model has no known window). It lets live UIs show
	// running tokens/cost/context before the terminal OnChildEnd. Same
	// aggregation as OnChildEnd, so the live value stays consistent with the
	// final total.
	OnChildUsage func(jobID string, usage *core.Usage, costUSD float64, contextPct int)

	// OnChildEnd is called once when a child agent (sync or async) finishes.
	// Result/Error are the terminal child outcome, not the one-time model
	// delivery claim used by subagent_wait and async notifications.
	OnChildEnd func(jobID, task string, async bool, status, result, resultErr string, finishedAt time.Time, usage *core.Usage, costUSD float64)

	// Title generation is deliberately asynchronous: starting work must never
	// wait on a convenience model call. Resumed children keep their saved title.
	TitleModel   core.Model
	TitleEnabled bool
	OnChildTitle func(jobID, title string)

	// ChildMaxTurns caps the number of turns a child agent may take. 0 (or
	// negative) falls back to defaultChildMaxTurns.
	ChildMaxTurns int

	// ChildMaxRunDuration caps how long a child agent may run. 0 (or
	// negative) falls back to defaultChildMaxRunDuration.
	ChildMaxRunDuration time.Duration

	// MaxConcurrentAsync caps how many async subagent jobs may run at once.
	// 0 (or negative) falls back to defaultMaxConcurrentAsync.
	MaxConcurrentAsync int

	// AllowedModels restricts, by model ID, which models a subagent may be
	// launched with. Empty (the default) means no restriction: the allowlist
	// is opt-in and must not change behaviour for anyone who never set one.
	// It also filters what the tool advertises — a model the agent cannot use
	// must not be named in the schema or in an error, or the agent will keep
	// trying it.
	AllowedModels []string

	// LoadAllowedModels reads the current global delegation policy. It keeps
	// already-open sessions subject to policy changes without recreating them.
	LoadAllowedModels func() []string

	// InheritedCompactAt is the soft compaction threshold (tokens) a child
	// should run under: the PARENT session's own threshold when it set one,
	// otherwise the global default. Resolved by the caller, which is the only
	// side that can see both. nil or 0 = compact at the child's model window.
	//
	// A child is a separate agent with its own history, so nothing reaches it
	// implicitly: without this a long delegated task would keep filling context
	// to the brim while the parent that spawned it compacts at 60%.
	InheritedCompactAt func() int

	// TranscriptLoader loads a persisted subagent transcript by job ID,
	// enabling the "resume" parameter to continue a finished subagent's
	// conversation instead of starting fresh. nil = resume unsupported (the
	// tool reports a clear error when resume is requested). The caller wires
	// this to its session-scoped transcript store (see pkg/serve).
	TranscriptLoader func(jobID string) (ResumedTranscript, error)
}

type JobInfo

type JobInfo struct {
	JobID            string
	OriginToolCallID string
	Task             string
	Title            string
	Model            string
	Thinking         string
	Status           string
	Async            bool
	StartedAt        time.Time
	FinishedAt       time.Time
	// Usage/CostUSD carry the child's accumulated usage/cost so far (nil Usage
	// until the child has closed at least one message), so a reconnect snapshot
	// can restore live cost without resetting it.
	Usage   *core.Usage
	CostUSD float64
	// ContextPercent is how full the CHILD's own context window is (0-100),
	// or -1 when unknown — see job.contextPct.
	ContextPercent int
	// AccentIndex is the job's stable creation ordinal (see job.accentIndex),
	// used by clients to pick a deterministic accent color that survives
	// reconnects.
	AccentIndex int
}

JobInfo describes a live (or recently finished) subagent job.

type Jobs

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

Jobs is a handle onto the subagent job store, returned by RegisterAll.

func RegisterAll

func RegisterAll(reg *core.Registry, cfg Config) (*Jobs, error)

RegisterAll registers the subagent tools on reg and returns a handle onto the job store (for external consumers: init snapshot, tray UI, cancellation).

func (*Jobs) Cancel

func (j *Jobs) Cancel(jobID string) bool

Cancel requests cancellation of a running job. Returns false if no job with that ID is tracked (so callers can surface a 404); returns true if the job exists, whether or not it was still running (idempotent for finished jobs).

func (*Jobs) Has

func (j *Jobs) Has(jobID string) bool

Has reports whether a job with jobID is currently tracked.

func (*Jobs) Messages

func (j *Jobs) Messages(jobID string) []core.AgentMessage

Messages returns a defensive deep copy of the stored transcript for jobID.

func (*Jobs) Promote

func (j *Jobs) Promote(jobID string) error

Promote flips a running sync subagent job to async, unblocking its parent's blocking tool call while the child keeps running in the background. Propagates ErrUnknownJob, ErrNotSync, ErrNotRunning from the underlying store so callers (e.g. pkg/serve) can map them to specific responses.

func (*Jobs) Snapshot

func (j *Jobs) Snapshot() []JobInfo

Snapshot lists all jobs currently tracked (live and recently finished, subject to the store's TTL cleanup).

func (*Jobs) Steer

func (j *Jobs) Steer(jobID string, text string) bool

Steer queues a message for inter-step delivery to the running child agent of jobID. Returns false if no job with that ID is tracked, if the job is no longer running (finished, cancelled or still initializing), or if the child itself refused the message. Non-blocking; safe to call concurrently.

type ResumedTranscript added in v0.28.0

type ResumedTranscript struct {
	Messages []core.AgentMessage
	Model    string
	Thinking string
}

ResumedTranscript is what a TranscriptLoader hands back: the persisted child conversation plus the identity it ran under. Model/Thinking let a resume continue with the SAME model and thinking level the subagent already used instead of silently adopting the parent's; both are optional (empty on transcripts written before they were recorded, which fall back to the parent's settings, i.e. the historical behaviour).

Jump to

Keyboard shortcuts

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