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).
Run-unique call ids are not minted here. The kernel owns them, because uniqueness is a property of the run and this object only ever sees 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]) -> 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
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | |
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.