Expand description
Tool dispatch: registry, policy, executor, and concurrency configuration.
The executor is the single entry point through which the agent loop
invokes tools. It handles three failure modes uniformly — returning
a tool_result Content with is_error: true so the LLM can observe
and adapt, rather than terminating the loop:
- Policy denial —
ToolPolicy::is_allowedreturned false. - Missing tool — the LLM invoked a name that is not in the registry.
- Tool error — the tool itself returned
Err(ToolError).
Separating dispatch from the agent loop lets sub-agents (and future
orchestration tools) share the same registry via ToolContext.
§Concurrency model
execute_batch partitions the LLM-issued batch into contiguous
runs of the same routing class — ReadOnly, ConcurrentMut,
SerialMut, or ShortCircuit. Each run dispatches concurrently
via futures::stream::FuturesUnordered; runs are sequentialised
against each other. The barrier between class boundaries preserves
the LLM’s intended ordering across mixed batches — [Read A, Write A, Read A] always observes the write before the trailing
read, because the executor cannot know which path each tool
touches and falls back on the LLM’s emitted order.
Admission control is layered: each call acquires permits from at
most two semaphores before invoking the tool — an optional
per-tool semaphore (cap configured by the consumer for that
specific tool name) and a mandatory class semaphore (read pool,
serial-mutator pool, or concurrent-mutator pool, chosen by the
tool’s ToolClass, is_recursive flag, and whether it was
promoted via crate::AgentBuilder::tool_concurrency).
Default-mutating tools that haven’t been promoted route to the
serial-mutator pool of width 1 — preserving the pre-concurrency
“strict serial” semantics as a special case of the new model.
Recursive tools (per crate::Tool::is_recursive) route to the
concurrent-mutator pool regardless of explicit promotion, because
their execute body parks the dispatcher task while running a
nested Agent::run; routing them through the per-level-forked
concurrent_mut pool prevents permit-held-during-nested-execute
deadlock without breaking the global serialisation contract.
Sub-agents do not simply share the parent’s executor. They
call ToolExecutor::fork_for_subagent to obtain a fresh
executor with the same registry / policy / approval but with the
deadlock-prone pools (concurrent_mut, per-tool) forked to fresh
Arc<Semaphore>s — the non-deadlock-prone pools (serial_mut,
read) stay shared so default-Mutating tools still serialise
globally across the agent tree. See
ConcurrencyConfig::fork for the exact policy.
Structs§
- Allow
All - Default policy: every tool is allowed.
- Concurrency
Config - Internal concurrency configuration owned by
ToolExecutor. - Tool
Call - A single tool invocation decoded from an LLM
tool_useblock. - Tool
Concurrency - Per-tool concurrency configuration.
- Tool
Executor - Dispatches tool calls against a registry, gated by a policy and an
approval handler, with admission control via
ConcurrencyConfig. - Tool
Registry - Name-keyed collection of tools. Construction is one-shot from a
Vec<Arc<dyn Tool>>— swap the whole registry if you need to reconfigure.
Traits§
- Tool
Policy - Decides whether the agent may invoke a given tool.