Documentation
¶
Overview ¶
Package meta hosts agent-meta tools: Agent (spawn sub-agent), ToolSearch (load deferred-tool schemas), and ScheduleWakeup (self-pace /loop iterations). Each needs an agent-side hook supplied via constructor injection from the toolset Builders.
SKILL lives in its own package (internal/tools/skill) because it owns a registry of user-installed skill files; co-locating it there keeps the loader and the tool adjacent.
Index ¶
- Constants
- Variables
- func Names() []tools.ToolName
- type AgentTool
- type DeferredLookup
- type DeferredLookupFn
- type SpawnGroup
- func (g *SpawnGroup) Add(name, id, agentType, jobDesc string, async bool)
- func (g *SpawnGroup) Crush(id string, summary string, err error)
- func (g *SpawnGroup) Domain() string
- func (g *SpawnGroup) DrainCompleted() []SubagentSnapshot
- func (g *SpawnGroup) Remove(id string)
- func (g *SpawnGroup) Report(id, summary string)
- func (g *SpawnGroup) Snapshot() []SubagentSnapshot
- func (g *SpawnGroup) Status(id string, status constant.AgentStatus)
- type SpawnRequest
- type SpawnerLookup
- type SubagentSnapshot
- type SubagentSpawner
- type ToolSearchTool
- type WakeupQueue
- type WakeupTool
Constants ¶
const SpawnGroupDomain = "subagent"
SpawnGroupDomain is the observable.Change.Domain value carried by every SpawnGroup change. Subscribers switch on this string and type-assert Change.Payload to SubagentSnapshot.
Variables ¶
var ErrSubagentForbidden = errors.New("meta: subagents cannot spawn subagents")
ErrSubagentForbidden is returned by Spawn when the calling agent is itself a subagent — only the root agent may spawn subagents. The AGENT tool surfaces this as a recoverable Result.IsError so the model can adjust its plan instead of aborting the loop.
Functions ¶
Types ¶
type AgentTool ¶
type AgentTool struct {
// contains filtered or unexported fields
}
AgentTool is the LLM-facing handle for spawning subagents. The actual work is delegated to a SubagentSpawner installed by the agent layer.
func NewAgent ¶
func NewAgent(lookup SpawnerLookup, spawnGroup *SpawnGroup) *AgentTool
NewAgent constructs an AgentTool that reads its spawner via lookup at Execute time. lookup may be nil (yields a clear runtime error if the model invokes the tool); it may also return nil (same outcome).
func (*AgentTool) Description ¶
func (*AgentTool) Schema ¶
func (t *AgentTool) Schema() json.RawMessage
type DeferredLookup ¶
type DeferredLookup interface {
// DeferredNames returns the tool names the profile allows the model to
// lazy-load. Order is implementation-defined and may vary between calls
// (e.g. a map iteration); TOOL_SEARCH sorts what it returns to the model.
DeferredNames() []tools.ToolName
// Describe returns the LLM-facing metadata for a deferred tool name.
// Returns an error if the name is unknown to the underlying catalog.
Describe(name tools.ToolName) (tools.Descriptor, error)
}
DeferredLookup is the agent-layer dependency the TOOL_SEARCH tool reads to enumerate and describe the deferred tools the current profile permits.
Like SubagentSpawner, this interface lives in meta so the agent layer can implement it without causing the cycle that would arise from meta importing agent. ToolSearchTool resolves the lookup at Execute time (late binding) so the agent can install itself after construction.
type DeferredLookupFn ¶
type DeferredLookupFn func() DeferredLookup
DeferredLookupFn is the late-binding shape NewToolSearch accepts; pass a method value bound to whatever owns the lookup (typically toolset.ToolState).
type SpawnGroup ¶
type SpawnGroup struct {
observable.Observable
// contains filtered or unexported fields
}
SpawnGroup is the per-agent panel of in-flight subagents. It is an observable.Store: every mutation fans through the framework so the TUI (and any other subscriber) can re-render without per-store wiring.
Lifecycle: Add → optional Status updates → Report | Crush → Remove (sync) / DrainCompleted (async). Sync subagents are short-lived in the panel — the spawner calls Remove right after the child returns. Async subagents stay in the panel until the parent loop drains them between turns.
func NewSpawnGroup ¶
func NewSpawnGroup() *SpawnGroup
func (*SpawnGroup) Add ¶
func (g *SpawnGroup) Add(name, id, agentType, jobDesc string, async bool)
Add records a new subagent in the init phase. async marks subagents whose result will be delivered through DrainCompleted instead of the usual tool-return path.
func (*SpawnGroup) Crush ¶
func (g *SpawnGroup) Crush(id string, summary string, err error)
Crush marks a subagent as failed.
func (*SpawnGroup) Domain ¶
func (g *SpawnGroup) Domain() string
Domain identifies this store on the change stream.
func (*SpawnGroup) DrainCompleted ¶
func (g *SpawnGroup) DrainCompleted() []SubagentSnapshot
DrainCompleted atomically extracts and removes every async subagent that has reached a terminal phase (done or crushed). Returned snapshots are in insertion order. Each removal emits an Op:"removed" change so the TUI clears its row.
Sync subagents are never returned here — the spawner removes them directly via Remove as soon as their tool-return path completes.
func (*SpawnGroup) Remove ¶
func (g *SpawnGroup) Remove(id string)
Remove deletes an entry from the group and notifies observers. Used by the spawner for sync subagents (their result is delivered through the tool return channel, not through DrainCompleted).
func (*SpawnGroup) Report ¶
func (g *SpawnGroup) Report(id, summary string)
Report marks a subagent as completed and records its result summary. Async subagents in this state are picked up by DrainCompleted; sync subagents are immediately Remove'd by the spawner.
func (*SpawnGroup) Snapshot ¶
func (g *SpawnGroup) Snapshot() []SubagentSnapshot
Snapshot returns a stable copy of every tracked subagent in insertion order. Read-only; the panel's drain queue is untouched. UIs poll this to render without racing against in-flight goroutines.
func (*SpawnGroup) Status ¶
func (g *SpawnGroup) Status(id string, status constant.AgentStatus)
Status updates the lifecycle phase of an in-flight subagent and notifies observers. No-op when the id is unknown.
type SpawnRequest ¶
type SpawnRequest struct {
Name string
// Kind selects a preset profile: "explore" or "general-purpose".
// Empty/unknown values are the spawner's responsibility (return an error).
Kind string
// 3~5 words desc
Desc string
// Prompt is the task the subagent should accomplish. Must be non-empty.
Prompt string
// Level selects the model tier within the parent's provider:
// 1 = small (smaller model — Sonnet, DeepSeek-Flash, ...).
// 2 = medium (larger model — Opus, DeepSeek-Pro, ...).
// 3 = Large (larger model — Opus + hard effort, DeepSeek-Pro + hard effort, ...).
// Zero defaults to 1; out-of-range values clamp via
// constant.LLMProvider.ModelForLevel.
Level int
AsyncMode bool // default = false
}
SpawnRequest is the parsed AGENT-tool input the spawner needs to actually run a subagent. Passed as a struct so future fields (model overrides, timeout, isolation mode) don't churn the SubagentSpawner interface.
type SpawnerLookup ¶
type SpawnerLookup func() SubagentSpawner
SpawnerLookup is the function shape a ToolState method (or any closure) satisfies to provide late-bound access to a SubagentSpawner. AgentTool keeps the lookup, not the spawner, so the order in which the agent and the tool are constructed doesn't matter — the spawner can be installed after the tool already exists.
type SubagentSnapshot ¶
type SubagentSnapshot struct {
Name string
ID string
Type string // "explore", "general-purpose", ...
Status string
Async bool
JobDesc string // prompt summary
Summary string // result summary (set on Report)
Err string // error message (set on Crush)
}
SubagentSnapshot is the typed payload carried in observable.Change.Payload for every "subagent" domain change. Each notification ships a full snapshot so consumers can render the row without keeping their own state.
Async marks subagents whose results must be picked up via SpawnGroup.DrainCompleted (the main agent loop does this between turns). Sync subagents deliver their result through the tool return channel and are Remove'd as soon as the spawner finishes, so they never sit in DrainCompleted's queue.
type SubagentSpawner ¶
type SubagentSpawner interface {
// Spawn creates a subagent per the request, runs it, and returns the
// child's final assistant text on success.
Spawn(ctx context.Context, req SpawnRequest) (string, error)
// SubagentTypes returns the sorted list of agent names that may appear
// as the AGENT tool's subagent_type value. The agent layer typically
// returns AgentRegistry.ListSubagent().Name. Empty / nil falls back to
// the built-in pair so degenerate setups (no registry, tests with a
// stub spawner) still produce a valid schema.
SubagentTypes() []string
}
SubagentSpawner is the agent-layer dependency that the AGENT tool calls to construct and run a child agent.
The interface lives in meta (not in the agent package) so the agent package can implement it without causing the import cycle that would otherwise arise from meta importing agent. AgentTool reads the spawner through a late-binding lookup (see NewAgent) so the agent can install itself as the spawner after its own struct exists.
type ToolSearchTool ¶
type ToolSearchTool struct {
// contains filtered or unexported fields
}
ToolSearchTool exposes deferred-tool metadata to the model.
Deferred tools appear by name in the system prompt; their full JSON Schemas are pre-injected into the same prompt at session start (see sysprompt.mainDeferredToolsSection). TOOL_SEARCH itself returns a compact JSON envelope — the model has already seen the schemas and uses TOOL_SEARCH for discovery / "is this one available", not schema fetching.
This output shape mirrors ref/src/tools/ToolSearchTool/ToolSearchTool.ts. The divergence from ref: ref pre-injects only names and uses Anthropic tool_reference blocks to expand schemas on the wire; evva pre-injects full schemas because not every provider supports tool_reference expansion.
func NewToolSearch ¶
func NewToolSearch(lookup DeferredLookupFn) *ToolSearchTool
NewToolSearch constructs a ToolSearchTool that reads its lookup at Execute time. lookup may be nil (yields a clear runtime error); it may also return nil (same outcome).
func (*ToolSearchTool) Description ¶
func (t *ToolSearchTool) Description() string
func (*ToolSearchTool) Execute ¶
func (t *ToolSearchTool) Execute(_ context.Context, logger *slog.Logger, input json.RawMessage) (tools.Result, error)
func (*ToolSearchTool) Name ¶
func (t *ToolSearchTool) Name() string
func (*ToolSearchTool) Schema ¶
func (t *ToolSearchTool) Schema() json.RawMessage
type WakeupQueue ¶
type WakeupQueue struct {
// contains filtered or unexported fields
}
WakeupQueue is the side-channel between WakeupTool and the agent loop.
The tool sleeps for the requested delay, then Enqueue's its prompt; the loop calls Drain at the top of each iteration and appends every drained entry as a fresh RoleUser message before the next LLM call. Same pattern as drainAsyncSubagents — the model sees the prompt as if the user just typed it.
func NewWakeupQueue ¶
func NewWakeupQueue() *WakeupQueue
NewWakeupQueue returns a fresh, empty queue.
func (*WakeupQueue) Drain ¶
func (q *WakeupQueue) Drain() []string
Drain returns every queued prompt and clears the queue. Returns nil (not an empty slice) when nothing is queued so callers can short-circuit with a single nil-check.
func (*WakeupQueue) Enqueue ¶
func (q *WakeupQueue) Enqueue(prompt string)
Enqueue appends a prompt to be delivered on the next loop iteration. Empty prompts are silently dropped — the tool validates non-empty at the public boundary, so a zero-arg push here would be a bug.
type WakeupTool ¶
type WakeupTool struct {
// contains filtered or unexported fields
}
WakeupTool implements SCHEDULE_WAKEUP. Execute blocks for delaySeconds (cancellable via ctx) and then enqueues prompt for delivery as a fresh user message on the next loop iteration.
The tool exists so the model can self-pace polling loops — e.g. "spawn async subagents, wakeup in 60s, then check on them". When the wakeup fires the queued prompt re-enters the conversation as if the user just typed it, giving the model a clean re-entry point.
func NewWakeup ¶
func NewWakeup(queue *WakeupQueue) *WakeupTool
NewWakeup constructs a WakeupTool bound to the given queue. The queue must be the same instance the agent loop drains from — typically obtained via ToolState.WakeupQueue().
func (*WakeupTool) Description ¶
func (t *WakeupTool) Description() string
func (*WakeupTool) Execute ¶
func (t *WakeupTool) Execute(ctx context.Context, logger *slog.Logger, input json.RawMessage) (tools.Result, error)
func (*WakeupTool) Name ¶
func (t *WakeupTool) Name() string
func (*WakeupTool) Schema ¶
func (t *WakeupTool) Schema() json.RawMessage