Documentation
¶
Overview ¶
Package toolset is the catalog of every tool the agent can construct.
Responsibilities split cleanly across the project:
Each internal/tools/<family> package knows how to BUILD its tools and reports the names it owns via Names(). It does NOT know whether those tools are eagerly or lazily loaded — that policy lives one layer up.
toolset.Build is the single name → instance resolver. One switch lists every tool the agent supports; auditing the surface = reading this file.
toolset.ToolState holds per-agent shared state (e.g. *todo.TodoStore) so stateful tool families can be constructed with the right backing data. The agent constructs one ToolState per agent instance, so two agents built from the same profile get isolated state.
The agent (internal/agent) decides WHICH tools to build eagerly (ActiveTools — exposed every turn) vs which to mark as lazy-loadable (DeferredTools — materialized on demand when first invoked).
Index ¶
- func Build(names []tools.ToolName, s *ToolState) ([]tools.Tool, error)
- func Describe(name tools.ToolName) (tools.Descriptor, error)
- func HintFor(name tools.ToolName) string
- func TagsFor(name tools.ToolName) []string
- type Registry
- func (r *Registry) Build(name tools.ToolName, state *ToolState) (tools.Tool, error)
- func (r *Registry) Has(name tools.ToolName) bool
- func (r *Registry) MustRegister(name tools.ToolName, factory ToolFactory)
- func (r *Registry) Names() []tools.ToolName
- func (r *Registry) Register(name tools.ToolName, factory ToolFactory) error
- type ToolFactory
- type ToolState
- func (s *ToolState) AgentGroup() *meta.SpawnGroup
- func (s *ToolState) DeferredLookup() meta.DeferredLookup
- func (s *ToolState) HasAgentGroupPanel() bool
- func (s *ToolState) HasUserPromptQueue() bool
- func (s *ToolState) HasWakeupQueue() bool
- func (s *ToolState) PlanController() mode.PlanModeController
- func (s *ToolState) QuestionBroker() question.Broker
- func (s *ToolState) ReadTracker() *fs.ReadTracker
- func (s *ToolState) RegisterStore(store observable.Store)
- func (s *ToolState) SetDeferredLookup(d meta.DeferredLookup)
- func (s *ToolState) SetPlanController(c mode.PlanModeController)
- func (s *ToolState) SetQuestionBroker(b question.Broker)
- func (s *ToolState) SetSkillRegistry(r *skill.Registry)
- func (s *ToolState) SetSubagentSpawner(sp meta.SubagentSpawner)
- func (s *ToolState) SkillRegistry() *skill.Registry
- func (s *ToolState) SubagentSpawner() meta.SubagentSpawner
- func (s *ToolState) Subscribe(fn observable.Observer)
- func (s *ToolState) TodoStore() *todo.TodoStore
- func (s *ToolState) UserPromptQueue() *UserPromptQueue
- func (s *ToolState) WakeupQueue() *meta.WakeupQueue
- type UserPromptQueue
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Build ¶
Build resolves each name to a tool instance via the default Registry. Stateful tools pull their backing state from s; stateless tools are package-level singletons.
Unknown names return an error — there is no silent fallback.
External hosts that need to register additional tools should call DefaultRegistry().Register at startup before agent construction.
func Describe ¶
func Describe(name tools.ToolName) (tools.Descriptor, error)
Describe returns the metadata (tools.Descriptor) for a tool name without registering the tool with any agent. Internally this constructs a throwaway instance just long enough to read its static metadata fields — for stateful tools the short-lived backing state is immediately garbage-collected.
Use Build when you actually need to call Execute.
Types ¶
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maps tool names to factories. External projects (and Phase 13 MCP support) register their own tools by calling DefaultRegistry().Register at startup before agent.Main constructs the root agent.
Registry is safe for concurrent use. Register fails on duplicate names — silently overwriting would let a typo route an LLM-facing name to the wrong implementation. Use MustRegister at init time when a duplicate is a programming bug.
func DefaultRegistry ¶
func DefaultRegistry() *Registry
DefaultRegistry returns the process-wide registry pre-populated with every built-in tool. Built-ins are registered exactly once on first access. External projects extend the catalog by calling Register on this returned pointer at startup.
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry returns an empty registry. Most callers want DefaultRegistry instead, which is pre-populated with every built-in tool.
func (*Registry) Build ¶
Build instantiates the named tool against state. Returns an error for unregistered names — there is no silent fallback.
func (*Registry) MustRegister ¶
func (r *Registry) MustRegister(name tools.ToolName, factory ToolFactory)
MustRegister wraps Register and panics on error. Use only at init time (registerBuiltins) where a duplicate or nil factory is a programmer bug.
type ToolFactory ¶
ToolFactory builds one tool instance against the supplied ToolState. Stateless tools ignore the state and return a package-level singleton; stateful tools pull their backing data (TaskStore, ReadTracker, etc.) from the supplied *ToolState.
Factories must not retain the *ToolState beyond the call — the tool itself either captures the late-binding closures it needs (e.g. the AGENT tool's spawner lookup) or holds the stateful pointers it received.
type ToolState ¶
type ToolState struct {
// contains filtered or unexported fields
}
ToolState carries the shared backing state for stateful tool families. Accessors are lazy — state is allocated only when the first tool that needs it is built. Pass the same *ToolState to every Build call for an agent so siblings within a family share state.
Cross-cutting consumers (TUI, session persistence) hold this through the agent (via agent.ToolState()) and read state through the typed accessors rather than peeking into tool internals.
func NewToolState ¶
func NewToolState() *ToolState
func (*ToolState) AgentGroup ¶
func (s *ToolState) AgentGroup() *meta.SpawnGroup
func (*ToolState) DeferredLookup ¶
func (s *ToolState) DeferredLookup() meta.DeferredLookup
DeferredLookup returns the currently-installed lookup for TOOL_SEARCH, or nil if none. Used as the late-bound lookup passed into meta.NewToolSearch.
func (*ToolState) HasAgentGroupPanel ¶
HasAgentGroupPanel reports whether a SpawnGroup has already been allocated. The agent loop uses this to skip drain calls in runs that never spawned a subagent (avoids forcing the lazy allocation just to peek at an empty panel).
func (*ToolState) HasUserPromptQueue ¶
HasUserPromptQueue reports whether a UserPromptQueue has already been allocated. The agent loop uses this to skip the drain in runs that never had user input queued (avoids forcing the lazy allocation just to peek at an empty queue).
func (*ToolState) HasWakeupQueue ¶
HasWakeupQueue reports whether a WakeupQueue has already been allocated. The agent loop uses this to skip the drain call in runs that never built the SCHEDULE_WAKEUP tool (avoids the lazy allocation just to peek at an empty queue).
func (*ToolState) PlanController ¶
func (s *ToolState) PlanController() mode.PlanModeController
PlanController returns the currently-installed plan-mode controller, or nil if none. The EnterPlanMode / ExitPlanMode tools read through this lookup at Execute time so the *Agent can register itself after agent.New returns without an init ordering hazard.
func (*ToolState) QuestionBroker ¶
QuestionBroker returns the question back-channel, or nil if not installed. The AskUserQuestion tool reads through this at Execute time.
func (*ToolState) ReadTracker ¶
func (s *ToolState) ReadTracker() *fs.ReadTracker
ReadTracker returns the session read-tracker shared by all fs tools, allocating one on first use.
func (*ToolState) RegisterStore ¶
func (s *ToolState) RegisterStore(store observable.Store)
RegisterStore plugs a TaskGroup into the unified change stream. ToolState subscribes to store and re-publishes every Change to its own observers. Lazy accessors below call this on first allocation, so callers that just use the typed accessors get registration for free.
func (*ToolState) SetDeferredLookup ¶
func (s *ToolState) SetDeferredLookup(d meta.DeferredLookup)
SetDeferredLookup installs the lookup TOOL_SEARCH will read from. The agent layer calls this exactly once (root agent only); the *Agent itself satisfies meta.DeferredLookup.
func (*ToolState) SetPlanController ¶
func (s *ToolState) SetPlanController(c mode.PlanModeController)
SetPlanController installs the controller EnterPlanMode / ExitPlanMode will mutate. The agent layer calls this exactly once after construction; only the root agent satisfies the interface (subagents inherit nil and the tools surface a clear error if invoked there).
func (*ToolState) SetQuestionBroker ¶
SetQuestionBroker installs the broker the AskUserQuestion tool will block on. The agent layer calls this once after construction via WithQuestionBroker.
func (*ToolState) SetSkillRegistry ¶
SetSkillRegistry installs the skill catalog the SKILL tool will read from. The host (cmd/evva) calls this once at startup with the merged home+workdir registry. Subagents inherit the same pointer via agent.WithSkillRegistry.
func (*ToolState) SetSubagentSpawner ¶
func (s *ToolState) SetSubagentSpawner(sp meta.SubagentSpawner)
SetSubagentSpawner installs the spawner the AGENT tool will delegate to. The agent layer calls this exactly once, after constructing its Agent struct, so the *Agent itself satisfies meta.SubagentSpawner.
func (*ToolState) SkillRegistry ¶
SkillRegistry returns the currently-installed skill catalog, or nil if none. The SKILL tool reads through this lookup at Execute time so the host can SetSkillRegistry any time before the model invokes it.
func (*ToolState) SubagentSpawner ¶
func (s *ToolState) SubagentSpawner() meta.SubagentSpawner
SubagentSpawner returns the currently-installed spawner, or nil if none. Used as the lookup closure passed into meta.NewAgent at build time so the AGENT tool sees a late-bound spawner without an init ordering hazard.
func (*ToolState) Subscribe ¶
func (s *ToolState) Subscribe(fn observable.Observer)
Subscribe registers an observer that receives every Change from every registered TaskGroup. The agent uses this to bridge into its event sink.
func (*ToolState) TodoStore ¶
TodoStore returns the todo subsystem's backing store, allocating one on first use. The todo_write tool constructed against the same ToolState shares it. First-use also registers the store on the change stream so the agent's event bridge picks up every todo mutation without per-store wiring.
func (*ToolState) UserPromptQueue ¶
func (s *ToolState) UserPromptQueue() *UserPromptQueue
UserPromptQueue returns the side-channel for prompts the user typed while a Run was in flight, allocating one on first use. The UI's submit handler Enqueue's; the agent loop's drainUserPrompts pulls every entry and folds them into the session as fresh RoleUser messages between iterations.
func (*ToolState) WakeupQueue ¶
func (s *ToolState) WakeupQueue() *meta.WakeupQueue
WakeupQueue returns the SCHEDULE_WAKEUP side-channel, allocating one on first use. WakeupTool Enqueue's the prompt here when its sleep finishes; the agent loop Drain's the queue at the top of every iteration and appends each entry as a RoleUser message.
type UserPromptQueue ¶
type UserPromptQueue struct {
// contains filtered or unexported fields
}
UserPromptQueue is the bridge that lets the UI hand the agent a fresh user message WITHOUT starting a new Run. The agent loop drains the queue at the top of every iteration and folds each entry into the session as a RoleUser message — same pattern as drainAsyncSubagents / drainWakeupPrompts.
Why a side-channel and not just another Run: while a Run is in flight the previous assistant turn's tool_calls may not yet be answered. A second Run that appended RoleUser there would orphan the tool_calls and every provider would 400 (the bug we fixed earlier in this branch). The queue defers the append to a safe point — between iterations, after the previous turn's RoleTool message has landed — so the conversation stays well-formed.
Subagents do not drain this queue; the user has no view into the subagent's loop, so enqueuing there would be invisible. Each agent has its own ToolState (and therefore its own queue), so subagent queues simply stay empty.
func NewUserPromptQueue ¶
func NewUserPromptQueue() *UserPromptQueue
NewUserPromptQueue returns a fresh, empty queue.
func (*UserPromptQueue) Drain ¶
func (q *UserPromptQueue) 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 (*UserPromptQueue) Enqueue ¶
func (q *UserPromptQueue) Enqueue(prompt string)
Enqueue appends a prompt to be delivered on the next loop iteration. Empty / whitespace-only prompts are silently dropped — they'd produce a useless empty RoleUser turn.
func (*UserPromptQueue) Len ¶
func (q *UserPromptQueue) Len() int
Len reports the number of pending prompts without draining. UIs use this to badge a "+N queued" indicator on the status bar without consuming the queue.