AI agents

Classifying Tool Errors So an Agent Can Act on Them

Antonio Evans10 min read

A cutaway of a tool server (gears, a small database, a keyhole) on the left, joined by a thick cable to an agent loop diagram on the right (a rounded rectangle with an arrow running round it).
Key points

Key points

  1. 1Every tool failure should be classified into one of five classes (transient, validation, permission, business rule, fatal) before the string reaches the model.
  2. 2The tool returns a fixed JSON envelope with a stable code, the class, a retryable boolean, and a retry_after_ms or offending field when they apply.
  3. 3The model maps class to action, so transient means retry with backoff, validation means rewrite the arguments, and business rule or fatal means stop.
  4. 4Tool-side discipline (retries inside the tool, structured error results, stop criteria) does more for reliability than any wording change in the system prompt.

Why the tool side of the contract is where recovery is won

Most teams tune the prompt when an agent loops on a failing tool, but the model can only choose a recovery action that the observation permits. If a calendar conflict arrives as a generic 500 string, the reasonable move is to retry, and the agent will retry until its budget dies. If the same conflict arrives as a business rule failure with the conflicting slot named, the reasonable move is to explain the rule to the user. The prompt did not change; the observation did.

The evidence for this framing is strong. LangChain's guidance states that when agents fail it is usually because the LLM took the wrong action, and that this is caused far more often by the wrong context than by an insufficient model (LangChain docs). Anthropic frames the same shift as context engineering, the curation of everything that enters the model call, including tool results, rather than prompt wording alone (Anthropic engineering). An error envelope is the cheapest place to apply that idea: you control every byte of it and it repeats at every call, so a small improvement compounds.

There is also a cost argument. Manus reports that with Claude Sonnet cached input tokens cost 0.30 USD per million tokens against 3 USD uncached, a tenfold gap, and that a stable prompt prefix is what preserves the cache (Manus). An envelope whose key order and shape vary from call to call quietly breaks that prefix. Fixed schema, stable keys, deterministic serialization.

Finally, the failures are real and numerous. One production write-up cites Datadog data showing about 1 in 20 AI model requests fail in production, and puts individual tool call failure rates at 3 to 15 percent in well-engineered systems (Scalekit). At 99 percent per-step reliability a 10-step workflow lands at 90.4 percent overall; at 95 percent per step across 20 steps it falls to 35.8 percent. Classification is how you spend the reliability budget where it can be recovered.

The five classes and the action each one licenses

The taxonomy below gives the model exactly one recovery action per class. Anything that does not fit is a signal that the tool is lying about what happened.

ClassMeaningretryableExtra fieldsModel action
transientThe same request may succeed if repeated latertrueretry_after_msRetry once within budget, then report the delay
validationThe arguments are wrong or incompletefalsefieldRewrite the arguments and call once more
permissionThe credential cannot perform this actionfalsehintStop calling; escalate to a human or request a token
business ruleThe world refuses this action under a stated rulefalsehintDo not retry; explain the rule and offer alternatives
fatalThe tool cannot complete this kind of taskfalsenoneAbandon the plan and report partial results

Two boundary decisions matter more than the labels.

First, transient versus business rule. A transient failure is about the transport or a temporary overload: a 503, a timeout, a 429 rate limit. A business rule failure is about the state of the world: the slot is taken, the invoice is already paid, the customer is on hold. The distinction is the spine of a well-known argument that retry-with-backoff was imported from stateless HTTP clients into stateful planning loops where it is wrong, and that the right default for a semantic error is replan, not retry (tianpan.co). A framework that retries a 409 burns a 200ms, 400ms, 800ms backoff sequence, 1.4 seconds of guaranteed latency, proving what the first response already said.

Second, permission versus validation. Both are 4xx-like and neither is retryable, but they diverge on who fixes them. An expired access token and a revoked refresh token can look identical at the execution layer while requiring completely different responses, and treating them the same produces retry storms and cascading failures (Scalekit). Validation is a model fix. Permission is a human fix.

A useful secondary test: if the same call with byte-identical arguments could ever succeed without any external change, it is transient. If it could only succeed after the arguments change, it is validation. If it could only succeed after the credential changes, it is permission. If it could only succeed because the world changed, it is business rule.

The error envelope, field by field

Every tool returns the same shape on failure. No exceptions, no free text at the top level.

{
  "ok": false,
  "error": {
    "code": "RESERVATION_SLOT_TAKEN",
    "class": "business_rule",
    "retryable": false,
    "retry_after_ms": null,
    "field": null,
    "hint": "Slot 14:00 on 2026-05-04 is held by another reservation for room B2. Free slots that day: 11:30, 15:15, 16:00.",
    "message": "The requested slot is not available."
  }
}

Field rules:

  • code is a stable, uppercase, machine-readable identifier that never changes meaning. Adding a code is a minor change; reusing one for a new meaning is a breaking change.
  • class is one of the five strings. The model branches on this, not on the code.
  • retryable is a boolean that must agree with class. Only transient returns true. If you find yourself wanting true on a business rule, the tool is not done.
  • retry_after_ms is populated for transient failures only, derived from a Retry-After header where the upstream provides one, or from the orchestration layer's backoff schedule otherwise.
  • field is populated for validation failures and names the exact argument, using the JSON path into the call the model made ("guest_count", "date", "items[2].sku").
  • hint is one or two sentences of plain language aimed at the model's next decision, not a stack trace. For business rules it states the rule and the nearest legal alternatives.
  • message is for humans reading logs. The model should be instructed to ignore it for decision purposes, so a chatty upstream message cannot smuggle instructions into the context.

On success, return ok true with the payload, and keep the same top-level key so the serialization stays stable.

{ "ok": true, "data": { "reservation_id": "rsv_8213", "slot": "2026-05-04T15:15" } }

The envelope size matters. A hint that runs to six sentences gets truncated in practice and costs tokens in every later turn. Write hints the way you would write a good API error: what failed, why, what to do. Anthropic's guidance on tool specifications in system prompts makes the same point from the other side, that explaining when to use a tool and what it returns reduces guessing (Redpanda docs).

The decision rule that keeps retries inside the tool

Here is the rule that most teams get wrong, and it is the one this whole article exists to deliver: retries happen inside the tool, not in the model's loop. The tool retries its own transport failures with jittered backoff, and only returns an error to the agent when it has exhausted that local budget. This is the explicit recommendation in guidance on system prompts for agents (Redpanda docs), and it has a clean justification: the orchestration layer owns silent infrastructure retries while the model owns reasoning-based recovery (n8n blog).

A worked example. Suppose a payments tool gets a timeout on a call that may or may not have committed. Retry from the model is dangerous: a payment-create that timed out may have succeeded server-side, and the retry creates a duplicate charge, as noted in the replan argument. So the tool itself should, on timeout, read back the payment by idempotency key before deciding. If the read-back shows a committed payment, the tool returns success. If not, it returns transient with retry_after_ms. The model never sees an ambiguous state, which is the whole point.

This also protects the latency budget. If a tool call costs 200ms on the happy path, three model-driven retries with 200, 400 and 800ms backoff add at least 1.4 seconds before any useful work happens, and that cost lands on the user. A tool that retries internally can do so in parallel or with a tighter schedule and a hard deadline.

There is one exception worth naming: a transient error that the tool cannot resolve within its own deadline should surface as transient with a real retry_after_ms, and the model may retry once at the agent level, not five times. Cap agent-level retries at one per tool per plan step. Without that cap you get the loop behavior that production teams report as the most common class of bug, with stop criteria (max iterations, cost cap, repeated-state detection) listed among the indispensable patterns (Blck Alpaca).

Mapping class to action in the system prompt

The prompt only needs six lines, because the envelope carries the information.

Tool error handling. Every tool returns {"ok": false, "error": {...}}.
- transient: retry the same call once. If it fails again, tell the user
  the service is slow and quote retry_after_ms.
- validation: fix the argument named in "field" using "hint", then call
  once more. Never repeat the identical call.
- permission: stop calling this tool. Tell the user what credential or
  approval is missing and escalate to a human.
- business_rule: do not retry. Explain the rule in "hint" to the user and
  offer the alternatives it lists.
- fatal: abandon the current plan. Report what succeeded so far.
Never invent a successful result. If a required tool failed, say so.

Notice what this does not do. It does not ask the model to infer from HTTP status codes, which is unreliable because the same 401 can mean three structurally different things (Scalekit). It does not ask the model to read stack traces, which invites the error text to act as an injection vector, a risk flagged in prompt security guidance (LaunchDarkly). And it does not leave the model to invent a fill-in when data is missing, which is the failure mode behind a great deal of confident nonsense: guidance on multi-agent workflows specifically warns against making up information to fill gaps left by a failed tool (Redpanda docs).

A worked example of the mapping. A travel booking agent calls search_flights with a malformed date. The tool returns class validation, field "date", hint "ISO 8601 date required, for example 2026-05-04". The model rewrites the argument and calls again. The next response is class business_rule, code NO_SEATS_IN_FARE, hint "Fare class W is sold out on this route; the lowest available fare is M". The model does not retry, it tells the user the fare is gone and quotes the alternative. Total wasted calls: one rewrite, zero retries.

Contrast that with the same scenario under a single error bucket: two retries on the malformed date, then a retry on the sold-out fare, then a likely hallucinated confirmation. Same model, same prompt length, different envelope.

A tool-side checklist

Run this list against each tool before blaming the model.

#CheckFails if
1One envelope, always the same keys, including on successVariations break prompt caching and confuse parsers
2Every error path returns a code from a closed, versioned listFree-text codes make metrics impossible
3class is one of the five strings and matches retryableA retryable business rule causes loops
4retry_after_ms present on every transient errorThe model guesses a delay and hammers the service
5field present on every validation error and names the argumentThe model rewrites the wrong thing
6hint states the rule and the nearest legal alternativeBusiness rules become dead ends
7Retries and idempotency live inside the toolDuplicate charges and double sends
8Error text is passed as a tool result tied to the call id, never raisedAn exception kills the turn and loses history
9No upstream message string can be read as an instructionInjection through error text
10Codes are asserted in tests for each fault-injected pathThe taxonomy rots

Item 10 is the one teams skip. Fault injection is now a studied technique for exactly this purpose: one benchmark framework intercepts agent-to-model traffic and injects format, logic and tool call faults to measure how well agents recover, and reports a model-size-dependent threshold in logic error recovery across the models it tested (Agent Error Simulator). A second line of work argues that timing is what decides whether a fault is even refutable: of 44 runs where counterevidence eventually became visible, only 17 saw it before first use and 27 saw it afterward (SSCBench). Translated: an envelope that names the problem on the failing call is worth more than any later correction.

Measuring whether any of this works

Three numbers tell you if the taxonomy is earning its keep. Track them per tool, not per agent.

First, retries per resolved task. Before classification, expect a heavy tail from business rules masquerading as transient. The target is under one agent-level retry per plan step, backed by the local retry budget inside the tool.

Second, validation recovery rate: the share of validation failures that produce a successful call within one rewrite. A low rate means your field names or hints are too vague.

Third, permission escalations reaching a human without an intervening retry. Any retry before escalation is wasted spend.

For the observability loop itself, the five context patterns (offload, isolate, retrieve, compact, cache) are widely used to keep the window lean, and production guidance pairs them with session-level goals and step-level tool usage tracking (Splunk). Error class belongs in that step-level record, because it lets you see whether failures cluster in one class for one tool, which is almost always an envelope problem rather than a prompt problem.

If the taxonomy work is the part you would rather hand off, the natural next step is to build the agent services around a single interface: Quanta takes a described site, app or plan and pairs you with a specialist who builds it with you, which is a reasonable route when you want the envelope, the retry budget and the observability wired in from the first commit rather than retrofitted.

FAQ

FAQ

What if a tool has several failures at once, for example a bad argument and a permission error?

Return the class that blocks the most. Permission outranks validation, because rewriting the argument will not help if the credential cannot perform the action. Report the blocking class and put the secondary issue in the hint so the next attempt, if any, can address it.

Should the model ever see the raw HTTP status code?

No, keep it in logs and out of the observation. Status codes are ambiguous at the execution layer, where the same 401 can mean an expired token, a revoked refresh token or a scope mismatch (Scalekit). The envelope's class and code are the contract; the status code is diagnostics.

How short should the hint be?

One or two sentences: the rule, plus the nearest legal alternative. Hints repeat in the context on every turn, and context is a finite resource with diminishing returns, a point Anthropic makes about attention budgets (Anthropic engineering). Long hints also risk being read as instructions.

Is a fixed error schema worth the engineering time?

Yes, for two reasons. It lets the model branch on a class instead of guessing from prose, and its stable prefix preserves prompt caching, which Manus reports as a tenfold cost difference between cached and uncached input tokens (Manus). Fixed schema, stable key order, deterministic serialization.

Do I need few-shot examples of each error class in the prompt?

Often not, but it depends on the model. Prompt guide material is clear that examples set expectations for format and behavior (Prompt Engineering Guide), and general prompt advice holds that every pattern should be verified against an eval set rather than assumed (Blck Alpaca). Start with the six-line mapping above and add a worked example per class only if the eval shows a class being mishandled.

Sources

Sources

  1. Context engineering in agents - Docs by LangChain
  2. Effective context engineering for AI agents - Anthropic
  3. Context Engineering for AI Agents: Lessons from Building Manus
  4. Tool Call Failures in Production: Debug and Prevent Guide
  5. Replan, Don't Retry: Why Most Agent Errors Aren't Transient
  6. Write Effective System Prompts | Redpanda Agentic Data ...
  7. LLM Tool Calling Error Handling: Retries and Fallbacks
  8. System Prompts for Agents: 12 Patterns - Blck Alpaca
  9. Prompt Engineering Best Practices: Tutorial & Examples | LaunchDarkly
  10. Agent Error Simulator: Fault Injection for Testing Recovery ...
  11. SSCBench: Evaluating the Evidential Validity of Fault- ...
  12. Context Engineering for AI Agents in Production: Architecture, Failure ...
  13. Basics of Prompting - Prompt Engineering Guide
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles