symfonic.capabilities.tools.execution.steering¶
steering ¶
Steering — what the model is told about a call that did not run (T3.1.3).
A guard that vetoes a call is not an error path: because the graph routes
the tool node back to the model, the synthesised answer is the steer.
Getting it wrong in either direction is expensive — a veto with no answer
leaves an unanswered tool_use block, which providers reject on every
later turn, and an answer with no veto runs the call the guard forbade.
The gate therefore has one invariant: calls in == results out. Every call is either admitted or answered, never neither and never both.
SteeringGate ¶
Consult a guard per call and partition into admitted vs answered.
The consultant is injected rather than imported: the legacy seam
builds a BeforeToolCallEvent and calls a callback manager, a test
passes a lambda, and the capability needs to know about neither.
The verdict contract is structural and deliberately narrow — a
mapping with action == "skip", plus content and an optional
is_error. Anything else admits the call, including a consultant
that raises: a broken guard must not stall dispatch.