Concept · No. 020 · November 4, 2026
A tool error is the agent's next prompt, so write it like one
When a tool call fails, the error text is the only thing the model has to decide what to do next. Most tool errors are written for a human with a debugger, and agents respond to them by guessing.
The idea
In an agent loop, a tool result goes straight back into the context window and shapes the next action. That includes failures. An error string is not a log line once an agent reads it: it is an instruction, whether you meant it as one or not.
Most backends return errors designed for a developer: a status code, a terse message, sometimes a stack trace. A human reads 404 Not Found, opens the docs and works out the fix. A model reads the same thing and has to infer the cause. It may retry with small variations, switch to an unrelated tool, or decide the operation succeeded and move on.
Designing errors as recovery instructions means each failure says what went wrong, whether retrying can help, and what the safe next step is. The cost is a little more care in the tool layer. The payoff is fewer loops, fewer invented values, and failures that end in a clear state.
An example
An agent handles a request to bill a customer. It calls create_invoice with customer_id: "Acme Ltd".
- The naive tool forwards this to the billing API, which returns
404 customer not found. - The agent tries
"Acme", then"ACME LTD", then constructs something that looks like an ID. Each attempt costs a round trip, and the last one could match a real, wrong customer.
Now the same call with a designed error:
code: INVALID_CUSTOMER_ID,retryable: false.message:customer_idexpects an ID such ascus_..., not a name.hint: callsearch_customerswith the name to resolve it.
The agent calls search_customers("Acme Ltd"), which returns two matches. That tool's response says the match is ambiguous and the user should confirm. The agent asks, gets an answer, and creates the invoice once. Same model, same tools, very different behaviour.
Where it breaks
Helpful errors can leak. Listing candidate records inside an error is only safe if the caller is authorised to see them. Returning SQL, internal hostnames or stack traces gives the model detail it cannot use and an attacker detail they can.
Errors that echo upstream content become an injection channel. If a third-party API's message is passed through verbatim, whoever controls that text can steer the agent.
Hints can overreach. An error that suggests force: true or a broader scope turns a guardrail into a speed bump the model will happily step over.
A wrong retryable flag is worse than none. Marking a validation error as retryable produces a tight loop that burns tokens and rate limits.
Finally, if the fix is mechanical, explaining it is the wrong tool. A date in the wrong format should be normalised by the tool, not described back to the model.
In practice
I would start by giving every tool a single error shape and enforcing it in the tool layer rather than per handler.
- Return stable fields:
code,message,retryable, and an optionalhintnaming the next tool or the valid values. - Keep codes stable and messages short, so evals and dashboards can group failures.
- Split audiences: a model-safe message in the result, full detail in logs under a correlation ID.
- Fix deterministic problems inside the tool and only surface what needs judgment.
- Never suggest escalating permissions or bypassing a check. Point to a human instead.
- Report partial success explicitly, listing which items succeeded and which did not.
- Cap retries per error code in the harness, independent of what the model decides.
- Test failure paths in evals by injecting errors and checking that the agent reaches a correct end state within a bounded number of steps.
Treat every tool error as a prompt the agent will act on: say what failed, whether retrying helps, and the one safe next step.