meta

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: May 21, 2026 License: MIT Imports: 13 Imported by: 0

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

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

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

func Names

func Names() []tools.ToolName

Names lists every tool name this package contributes.

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 (t *AgentTool) Description() string

func (*AgentTool) Execute

func (t *AgentTool) Execute(ctx context.Context, logger *slog.Logger, input json.RawMessage) (tools.Result, error)

func (*AgentTool) Name

func (t *AgentTool) Name() string

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

Jump to

Keyboard shortcuts

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