- Published on
Only offer tools that belong to the current workflow
- Authors

- Name
- Mehdi Akiki
Article · Through the layers
The assistant is helping a support agent find an invoice. Its tool list also contains delete-account, issue-refund, and rebuild-index.
Those tools may belong to the same product. They do not all belong to this task.
A smaller offered set makes the workflow easier to understand and gives the model fewer irrelevant actions to consider. The backend still has to enforce access independently.
Build the manifest from trusted workflow state
Choose the workflow on the server, using the authenticated user's current context. A support lookup and an approved refund flow can offer different tools.
This Rust example expresses the selection without depending on a model SDK:
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Workflow {
SupportLookup,
RefundReview,
RefundExecute,
Notifications,
}
pub fn offered_tools(workflow: Workflow) -> &'static [&'static str] {
match workflow {
Workflow::SupportLookup => &["find_invoice", "read_invoice"],
Workflow::RefundReview => &["read_invoice", "preview_refund"],
Workflow::RefundExecute => &["read_invoice", "execute_refund"],
Workflow::Notifications => &["send_mail"],
}
}
The refund-review workflow above does not expose execution. A separate state transition can provide an execution capability after the required checks and approval.
Do not let a model-provided workflow name widen the manifest. The generated request can ask to enter another flow; the server decides whether the transition is allowed.
The secure internal-tool article explains the service boundary. OWASP's excessive-agency guidance also identifies tools unrelated to the intended operation as unnecessary functionality.
A hidden name is not an access check
A client can fabricate a call to a tool that was not offered. A model may emit an old name from previous context. An internal bug can route a call through the wrong manifest.
The execution gateway must verify that the requested tool is enabled for this workflow and that the actor may perform the exact action on the exact target. The authorization article covers carrying that context through execution.
Keep checks close to the effect. A tool being present in a manifest is evidence of availability at one point, not a durable grant after the workflow or permissions change.
Make workflow changes visible
When the task moves from lookup to a write, show the user the proposed action and target. Resolve the arguments before approval if the action needs approval. Do not reuse an earlier broad “continue” as permission for a different write.
Invalidate or revalidate capabilities when the workflow changes. A queued call from the previous state should not inherit whatever authority the new state happens to have.
Test both selection and execution
Assert the exact manifest for each supported workflow. Then directly call a tool that is absent and confirm denial before any external effect.
Add a role change, a stale workflow state, and a fabricated tool name. Test that allowed actions still work so an empty manifest cannot satisfy the whole suite.
Some workflows need a broad tool set. Make that breadth a reviewed requirement and use narrow argument and resource policies. For a simple lookup, a small relevant set is usually easier for the user, the model, and the engineer reviewing its behaviour.
The manifest and dispatcher share one workflow definition
The example gateway has four explicit workflows. Support lookup offers find and read. Refund review offers read and preview. Refund execution offers read and execute. Notifications offers only captured mail. The function shown earlier is the actual selection function used by the gateway.
Selection is checked twice for different purposes. The manifest tells the model which proposals fit the current step. Execution checks the same allowed-name set before a proposal can reach a credential or effect. Omitting a tool from a manifest does not make its public name impossible to guess.
The test gives a support context an execute-refund proposal directly. It is denied even though the proposal names a real tool. A valid read succeeds under the same actor and account. That positive control matters: the implementation is enforcing a workflow boundary, not refusing every call indiscriminately.
The write workflow also requires an eligible actor and an exact approval. Creating a refund-execution context is not itself permission for any refund payload. Approval binds the actor, tenant, account, workflow, policy revision, target, and amount. The target is checked again at execution.
The source and tests use fixed workflow states supplied by the trusted request layer. A real product needs a reviewed state transition when a user moves from preview to execution. A model argument must not be allowed to switch the workflow simply by naming the more powerful state.
There is no model-quality measurement here. Synthetic proposals exercise the application's enforcement independent of which model produced them. Keep a separate evaluation for whether the smaller manifest helps the model choose the right allowed operation. Both questions matter, but a better selection rate is not evidence that a hidden write is inaccessible. The dispatcher supplies that evidence by checking the call where it would otherwise acquire an effect capability.