Senior · Core path · 7 chapters
Tool design — semantics
Idempotency and safe-retry for a non-deterministic caller; error messages written to be *actionable by a model*
If these contracts are vague, transient failures become duplicate side effects and recoverable input mistakes become operator incidents.
Read these first
What you will know
Make a tool safe to retry when the model calls it twice
A request ID or idempotency key must survive timeout retries while side effects are committed at most once.
Write an error message that tells the model what to do differently
The response should identify the violated precondition and expose permitted values or the correct next call shape, rather than merely report failure.
Replace a stack trace with a model-actionable tool error and verify the resulting retry path
Raw frames consume context and often reveal server internals, while the model needs a stable error code plus corrected input or sequence guidance.
Design a destructive tool so a mistaken call is recoverable
Soft deletion, preview, and explicit confirmation preserve an undo path when the model infers the wrong target or parameters.
Show a model recovering from a tool error without human help
The test should include a plausible malformed call, a bounded retry, and a successful terminal result without hidden application code.
Chapters
- 1
Idempotency and actionable errors
A call that times out after the payment has committed and a call refused in 40 ms because the amount exceeds an account limit sit at opposite corners of one grid: did the attempt leave state behind, and could an identical repeat ever succeed. Only the first is an idempotency problem and only the second is an error-message problem, and the tempting single fix — make everything idempotent and let the agent retry freely — turns the second into a burned turn budget and a false report that the system is down.
- 2
Deriving an idempotency key
A key generated fresh on every retry protects nothing, because the retry is exactly where it needs to match. Derive it from the intent — the charge id, the amount in minor units, the currency — record it against the committed refund inside the same transaction, and return the original result when it reappears. The key is computed application-side from a chosen stable subset of the model's arguments, never asked of the model, and choosing that subset decides which two legitimately distinct requests can never both succeed.
- 3
Model-actionable error responses
Compare a bare failure string with a response carrying error invalid_status, field status, message 'must be one of: open, pending, closed', received 'OPEN'. The second names the violated precondition, the offending value and the permitted set, so the next call is a correction rather than a guess. What each field is for, what to do when the permitted set has 249 members, why every independent violation should arrive in one response, and why 'it eventually recovered' is not evidence that the bare version worked.
- 4
Retryable and terminal error classes
An agent retries book_appointment four times on a slot taken two seconds earlier, then reports an outage; another retries a transfer rejected for insufficient funds with the balance right there in the response. Neither error said that repetition was futile for these arguments or what to send instead, so retrying was the only move left. Three classes — transient, invalid_request, terminal — plus retry_after and adjacent alternatives, and why a machine-readable field beats an HTTP status code or a lookup table in the harness.
- 5
Stack traces in a tool result
Returning frames to a model is worse than merely expensive: they publish deployment paths, SQL and library versions to a component that paraphrases things for users, and the text changes whenever a dependency is upgraded, so there is no stable code to alert on or branch on. Catching the exception and returning str(exc) fixes tokens and most of the leakage while leaving recovery untouched — the mapped error object is what changes the model's next move.
- 6
Recoverability on a destructive tool
A confirm: true argument is the same model confirming its own belief in the same generation, and restating the target's name makes the error more confident rather than less. Soft deletion with a retention window, a result naming what was actually affected, and the restoring call returned alongside it give a real undo path — then the harder case, send_customer_email, where nothing can be un-sent and the window has to be manufactured.
- 7
Testing unattended recovery
Drive a realistic malformed call end to end and assert a correct terminal result within a bounded number of attempts, with no hidden application code doing the repair. Whether four attempts is a pass depends on what an attempt costs in tokens, seconds and commit opportunities. This is also the instrument that proves an error-path rewrite worked, by comparing the distribution of attempts to recovery before and after — and the thing to do when a new model version needs nine.
External resources
- Writing effective tools for AI agents — using AI agentsanthropic.com
Canonical guidance on error responses written to be actionable by a model.
- To Call or Not to Call: A Framework to Assess and Optimize LLM Tool Callingarxiv.org
Framework for deciding when a model should retry, skip, or escalate a call.
- LLM Tool Schema Design: Inputs, Outputs & Error Handlingaiquinta.ai
Structured error shapes and retry semantics you can implement directly.
- Effective harnesses for long-running agentsanthropic.com
Why safe-retry and resumability matter when the caller is non-deterministic.
- AI Agents with Function Calling:Tool UseToronto Machine Learning Series (TMLS)
Shows the retry path a model takes after a failed or ambiguous tool result.