symfonic.agent.backend.tools¶
tools ¶
Tool normalization and execution behind the kernel's tool port (FAC-6, RES-3).
NormalizedTools
dataclass
¶
The construction-time-resolved tool set.
Frozen and resolved once, at construction: FAC-6 fixes the tool set for the life of the agent. There is no per-invocation override and no mutable registry, because the invocation plan is compiled per call and a tool set that can change underneath it is precisely the drift the plan model exists to remove.
ToolAdapter ¶
The kernel's ToolPort, bound to this agent's fixed tool set.
It is built from the same normalized set that produced plan group G4, which is why the loop performs no second allowlist check: one manifest, one answer to "may this call run" (IPL-5).
The kernel owns run-unique call IDs; this port handles one call at a time.
Source code in src/symfonic/agent/backend/tools.py
refuse ¶
The outcome of a call a precondition would not let run.
The port builds it rather than the kernel, for the reason the kernel carries outcomes opaquely at all: this is the facade's type, and a kernel that constructed one would be deciding what a tool result looks like. Same shape as an unknown tool and a raising tool -- the error text is what the model is shown as the observation, which is how a tool-using loop is told no and can try something else.
duration_ms is zero because nothing ran. Not "very fast": a reader
totalling tool time must not count a call that never happened.
Source code in src/symfonic/agent/backend/tools.py
execute_tool_call
async
¶
execute_tool_call(tools: NormalizedTools, call_id: str, name: str, arguments: dict[str, Any], *, context: RunToolContext | None = None, run_aware: Mapping[str, RunAwareExecutor] | None = None) -> ToolCall
Run one tool call and return its completed :class:ToolCall record.
A tool that raises is reported, never hidden: the exception text lands on
ToolCall.error and is fed back to the model as the tool's observation,
which is how a tool-using loop recovers. BaseException (and therefore
asyncio.CancelledError) is deliberately not caught — cancellation
must propagate unchanged (EVT-9).
Source code in src/symfonic/agent/backend/tools.py
merge_capability_tools ¶
merge_capability_tools(adopter: Sequence[Any], contributed: Sequence[tuple[str, Any]]) -> NormalizedTools
Normalise the adopter's tools and the capabilities' into one set.
One set, because the tool path is read in four places — bind_tools, the
G4 manifest, ToolAdapter, and the tool_call grant — and a second
collection that only some of them consult is exactly how a tool becomes
bindable and not callable.
contributed is (capability, tool) pairs, which is why this exists
rather than a bare normalize_tools(list(a) + list(b)). Every refusal
below names the capability. A collision reported as "tools[4] resolves to
the name 'run_agent', which is already registered by an earlier tool" is
true and tells the adopter nothing about which capability to configure —
and unlike tools=[...], the adopter did not write the offending list
and cannot see it.
The adopter's tools go first and keep the plain index in their messages, so an error in a hand-written list reads exactly as it did before capabilities could contribute anything.
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
for a non-tool object, or for any name collision — capability against adopter, or capability against capability. |
Source code in src/symfonic/agent/backend/tools.py
normalize_tools ¶
Accept the three documented input forms; reject everything else.
Accepted (FAC-6): an object produced by @symfonic_tool(...), any
LangChain BaseTool, or a plain Python callable with annotated
parameters and a docstring — adapted through symfonic_tool() with its
documented defaults.
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
for a non-tool object (naming it and its index), or for two tools resolving to the same name. Last-one-wins would silently drop a tool the adopter believes is registered, so the collision is a hard error rather than a warning. |
Source code in src/symfonic/agent/backend/tools.py
remap_tool_call_ids ¶
Return an assistant wire message whose tool ids match facade ids.