AI agents

A Tool Contract for Failure: Inputs, Outputs and Error Codes

Antonio Evans11 min read

A clean line diagram of a tool call: an input box on the left, a tool block in the middle, and on the right two output paths.
Key points

Key points

  1. 1Treat every tool failure as a written contract: what input triggered it, what the response body contains, whether to retry, escalate or abort, and what the next action is.
  2. 2Classify errors at the tool-definition level (transient, terminal, needs-clarification) so the agent's control loop branches instead of guessing.
  3. 3Carry a machine-readable hint and a correction suggestion in the payload, because a reasoning model that gets a usable signal can self-correct without a human.
  4. 4Test the contract with malformed inputs, simulated failures and side-effect retries before the tool ships, not after the first production incident.

Why the error payload is the contract

An agent that calls a tool receives a response body and must decide what to do next without leaving the execution loop. That makes the error payload a specification, not a message. A classic 400 Bad Request with a body of {"error": "invalid input"} works for a human, who can read the docs, see the red UI field and try again manually, but an agent has none of those fallbacks. It receives the response, interprets it and must act, all inside the loop, as laid out in Error Handling for AI Agents: A Different Standard Than Classic Apps.

The core principle follows from that: every error must tell the agent what to do next. Error design shifts from reactive (describing what went wrong) to prescriptive (describing what to try instead), and the message becomes documentation read at runtime.

A worked example of the same failure redesigned for an agent, from that same analysis:

{
  "error": "Invalid value for field 'status'",
  "received": "done",
  "allowed_values": ["pending", "in_progress", "completed", "cancelled"],
  "hint": "Did you mean 'completed' instead of 'done'?"
}

The first version tells the agent that something failed. The second tells it which field failed, what arrived, what was acceptable and how to fix the call. Only the second supports automatic recovery.

There is a second reason the contract matters. Tool errors compound in multi-step agents: a wrong argument in step two corrupts the inputs to step three, which corrupts step four, until the task collapses. Small improvements at the tool boundary therefore have outsized downstream effects on completion rates, according to Designing Tools for LLM Agents. The contract bounds the blast radius of each failure.

The contract template

Write one block per error code, in the same repository as the tool definition, and treat changes as breaking changes. The template has seven fields.

FieldWhat it recordsExample
CodeStable string the agent branches onINVALID_ENUM_VALUE
TriggerThe exact input condition that produces itstatus not in the allowed set
Response shapeEvery field the agent may readreceived, field, allowed_values, hint
Classtransient, terminal, needs_clarificationterminal
Retry policyWhether, how often, under what conditionRetry only after changing the argument
Side effectsWhether the operation committed before failingNo write occurred
Next actionWhat the agent does if the retry failsEscalate to a human with the payload attached

The fields map to what error-handling guidance recommends for tool execution: errors should be specific about cause, name the violated constraint and suggest the next step, as covered in ApX's module on tool error handling.

Two decisions belong in the template that most teams skip. First, the side-effect column: a failure after a state change is not the same as a failure before it, and the retry policy depends on knowing which happened. Second, the next action must be written in the imperative and be executable with what the agent already has in context. "Handle the error" is not a next action. "Call get_customer_by_email with the value from the received field, then retry with the returned customer ID" is.

An explicit failure-reporting tool is a useful complement. Give the agent a structured exit path, a report_failure function that accepts the failed tool, an error description and a suggested alternative, so that when it cannot recover it reports the failure in a shape your logs can parse instead of looping or inventing a result, as Pristren's design pattern collection recommends.

Every input that can be malformed

The contract starts upstream of the error code. If the schema lets the model supply a plausible-but-invalid value, no error payload will fix the problem, so the first job is to shrink the set of failures the tool can produce.

A tight schema encodes constraints so the model does not have to guess: enums for fields with a small set of valid values, length bounds on short strings, patterns on dates, defaults on optional fields, and typed parameters rather than free strings. A loose schema means the model guesses at constraints, and a tight one encodes them so no guessing is needed, as shown in AI Agent Tool Design: What Works and What Doesn't. Validation failures then surface at the tool boundary rather than as cryptic downstream errors.

Enumerate the malformed inputs explicitly, because each one maps to an error code:

  • Missing required field (the model omitted it).
  • Wrong type (a string where an integer was expected, for example "42" instead of 42).
  • Value outside an enum or range.
  • Structurally valid but semantically wrong (a date in the past, an ID that does not exist).
  • Correct input, unavailable dependency (the record exists but the service is down).
  • Correct input, insufficient permission.

The first four are terminal in the sense that resending the same payload fails identically. The fifth is transient. The sixth needs escalation, since no retry and no correction by the model will grant permission.

Where the provider supports it, push the constraint into generation. Cohere's strict_tools parameter guarantees that tool calls follow the supplied schema, so tool names and parameters are not hallucinated, every required parameter is present and every value carries the requested type, subject to a cap of 200 fields across all tools in one call, according to Cohere's parameter type documentation. When the schema is enforced at generation time, the malformed-input contract only needs to cover what the schema cannot express, such as referential validity and permissions.

Naming is part of input hygiene. A parameter called user could accept a name, an email or an integer ID, and the model will guess; rename it user_id and the ambiguity disappears. The same guide notes that requiring absolute file paths instead of relative ones eliminated a whole class of model errors because the model stopped guessing the working directory, per AI/TLDR.

Every output shape the agent must handle

The contract covers success shapes too, because an agent cannot follow recovery logic against an output it did not expect. Specify, in the tool description and in the contract document, exactly what a successful call returns, including the empty case. Pristren's guide recommends stating the output format inside the description, for example a JSON array whose objects carry title, url and snippet, and noting explicitly that an empty array means no results. When the model knows the output format in advance, it handles empty and partial results correctly instead of hallucinating structure.

The output shapes to document:

ShapeWhat it meansAgent action
Full successAll fields present, non-emptyContinue the plan
Empty resultValid query, zero matchesReformulate or broaden before concluding
Partial successSome records failed validationReport which ones, continue with the rest
TruncatedResult limit hitNarrow the query rather than paginate blindly
Shape mismatchResponse does not parseRepair or reread, do not re-execute the effect

The truncated case deserves attention in agent contexts. A tool that returns 800 rows burns most of the context window on data the agent will never use, while a search that returns the five most relevant rows makes the task possible within budget, per AI/TLDR. The contract should state the row limit and the signal the agent receives when it is reached.

Partial success is the quiet failure. A tool that writes three of five records and returns a generic success corrupts every later step. Make partial success a distinct output shape with its own contract entry, so the runtime validation layer can decide whether to proceed, retry the failed items or escalate. Validation belongs in the runtime, not in the model's reasoning: the runtime checks whether a result arrived, matches the expected format and is non-empty, and can retry or inject a corrective message before the model acts on bad data, as Pristren describes.

Classify each error: retry, escalate or abort

The classification is the heart of the contract and the part that most distinguishes agent error handling from classic backend work. Three classes, three sets of rules.

Retry. Transient failures: rate limits, timeouts, temporary network blips, 5xx responses from a service that is otherwise healthy. These are infrastructure disruptions that the orchestration layer should absorb, with exponential backoff and jitter, typically capped at three to five attempts, as AgentWorks' recovery pattern guide lays out and as n8n's tool-calling error guide also recommends so the model never needs to see the transport error. Cap retries at three in most cases: if it has not worked in three attempts, it is unlikely to work in thirty, per this practitioner writeup on agent error patterns.

Escalate. Failures the model cannot fix alone or must not fix unilaterally: permission errors, terminal validation failures on high-risk actions, and any action above a defined risk threshold such as financial transactions, external communications or irreversible deletes. The agent pauses and requests approval rather than continuing to guess, per AgentWorks. Escalation is also correct when a bounded retry budget is exhausted on a side-effecting call, because the outcome is unknown and a human should reconcile it.

Abort. Permanent, structural failures that make the current task impossible: a missing record the plan depends on, a revoked credential, a context window exceeded. Retrying a validation error or a 403 wastes the call budget and produces the same failure every time, as AgentWorks notes. Temporal's non-retryable error pattern formalizes the same distinction: mark the error as non-retryable at the throw site or list its type in the retry policy, so the activity fails fast instead of waiting through backoff delays for a failure that will never resolve, according to Temporal's documentation.

A decision rule that fits most tools: if the same call with the same arguments could succeed on the next attempt, it is retryable; if changing the arguments could fix it and the model has enough context to make the change, it is self-correctable; otherwise it escalates or aborts. The contract states which, per code, and the control loop reads it:

ERROR_CONTRACT = {
    "INVALID_ENUM_VALUE": {"class": "terminal", "retry": "after_argument_change"},
    "RATE_LIMITED":       {"class": "transient", "retry": "backoff", "max_attempts": 3},
    "PERMISSION_DENIED":  {"class": "terminal", "retry": "never", "next": "escalate"},
    "DEPENDENCY_DOWN":    {"class": "transient", "retry": "backoff", "max_attempts": 3},
    "RECORD_NOT_FOUND":   {"class": "terminal", "retry": "never", "next": "abort"},
}

One more discipline: decide these thresholds up front rather than after the first incident. Most teams bolt on error handling after production breaks, but idempotency and escalation logic are expensive to retrofit into a live agent because they change the parts customers already depend on, per AgentWorks.

Carry a machine-readable hint

The hint field is what turns an error from a report into an instruction. Give the agent, in the payload, the field that failed, the value it sent, the allowed values and a one-line suggested correction, like the "Did you mean 'completed' instead of 'done'?" example. An LLM given "invalid date format, expected ISO 8601 (YYYY-MM-DD)" can self-correct on the next attempt, while an LLM given a raw stack trace usually cannot. AgentWorks identifies this as the single highest-leverage pattern for cutting failed runs, because most tool-call errors are recoverable by the model itself given a clear enough signal, with no human, no fallback model and no escalation.

Keep the hint machine-readable as well as human-readable. A retry_after_ms integer for throttled calls lets the runtime wait exactly as long as the service asked, which is why parsing standard Retry-After headers beats hardcoding an interval, per n8n. A corrected_arguments object lets the agent resubmit without re-reasoning from scratch. A recovery enum (retry, reformulate, escalate, abort) lets a non-LLM control loop branch without parsing prose, which matters once the agent runs on a deterministic orchestrator.

Two cautions. Do not put secrets, internal stack traces or raw provider errors in the hint, since the payload enters the model's context and usually the logs. And do not let the hint smuggle instructions: a dependent service's error text is untrusted input, so parse the fields you own and regenerate the hint on your side.

Test the contract before the tool ships

A contract is only a contract if something checks it. Four tests, run before release.

Malformed-input matrix. For every input constraint, send a violating value and assert the exact error code and payload shape. This is the same discipline as testing a tool with valid and invalid inputs and converting failures into actionable feedback for the model, as demonstrated in this LangChain tool configuration walkthrough.

Simulated failures. Stand up a failing dependency (a mock that returns 429 for the first two calls and then succeeds) and assert that the agent retries within budget, respects backoff and stops at three. Jitter matters here: it prevents a thundering herd when several agents hit the same limit, per the pattern writeup.

Side-effect retry test. Call a state-changing tool, drop the response, call it again with the same idempotency key, and assert the effect happened once. A timeout after dispatch does not establish whether the side effect committed, so idempotency must reach the service that owns the effect, as Formation's guide to tool call retries explains. The orchestration layer should generate the key from the durable execution step, never from model output, because a newly sampled key identifies the retry as a different operation and defeats deduplication.

Escalation threshold test. Assert that the agent stops and asks a human after the configured number of failed attempts or before any action above the risk threshold, rather than looping. Teams working on Cloudflare's agents project found that a recognized transient error became terminal because the recovery path only routed one specific error class into bounded recovery, letting the turn stop with a partial result, as documented in this issue report. A contract test for "this error must route to recovery" would have caught it.

For teams running many tools across providers, a single gateway that normalizes error payloads and routes every model through one API key removes a whole class of contract drift; Quanta Inference (https://quantaintelligence.ai/inference) provides text, image, video, audio and embedding models through one OpenAI-compatible, prepaid API.

When to revisit the contract

Revisit the contract whenever a production error arrives that the document does not name, whenever a retry policy changes, and whenever a tool gains a side effect. A useful metric: the share of production errors that map to a documented code. If it falls below your threshold (start at 90 percent), the contract is behind the tool.

A second trigger is a change in how errors are surfaced to the model. Providers differ on how strictly they enforce your schema, and a switch from a permissive setup to strict tool calling changes which malformed inputs can even reach your code, per Cohere's documentation. Re-read the malformed-input section after any such change.

One last design rule. Every error the tool returns should be reproducible from the contract by a reader who has never seen the code. If a new engineer or a new agent cannot determine the next action from the document alone, the contract is not finished.

FAQ

FAQ

What is the difference between an error message and a tool failure contract?

A message describes what went wrong. A contract states, for every error code, what input triggered it, what the response body contains, whether the agent should retry and under what condition, whether a human must take over, or whether the task must stop. The contract is the spec the agent's recovery logic is written against, and it is the difference between a model that self-corrects and one that loops.

How many times should an agent retry a failed tool call?

Three is a common default for transient failures, with exponential backoff and jitter, because if it has not worked in three attempts it is unlikely to work in thirty. Failures where the same arguments fail identically, such as validation errors, permission denials or missing records, should be marked non-retryable and routed to correction or escalation. Never retry a side-effecting call without an idempotency key.

Is it safe to retry a tool that writes data?

Only if the write is idempotent at the service that owns it. A timeout after dispatch does not tell you whether the effect committed, so attach an idempotency key generated by the orchestration layer before dispatch and reuse it across every retry of the same logical operation. If the downstream system has no idempotency support, carry the unresolved outcome to a human for reconciliation rather than retrying blindly.

When should the agent escalate to a human instead of retrying?

Escalate when the retry budget on a side-effecting call is exhausted, on any permission or authorization failure, and before any action above a defined risk threshold such as payments, external messages or irreversible deletes. The threshold is a configuration value, not a judgment made at runtime, so behavior stays predictable and auditable.

How do I know the contract covers the failures that actually occur?

Compare production error codes against the document. When a failure arrives that the contract does not name, add it with its class, retry rule and next action, and add a test that reproduces it. Pair that with the four pre-release tests (malformed-input matrix, simulated failures, side-effect retry, escalation threshold) so the contract is verified, not just written.

Sources

Sources

  1. Error Handling for AI Agents: A Different Standard Than Classic Apps
  2. Designing Tools for LLM Agents: Best Practices | AI/TLDR
  3. Error Handling for LLM Agent Tools - ApX Machine Learning
  4. Tool Use in LLMs: Design Patterns for Reliable Agent Actions - Pristren
  5. AI Agent Tool Design: What Works and What Doesn't
  6. Parameter types for tool use (function calling) - Cohere Documentation
  7. Error Handling and Recovery Patterns for AI Agents
  8. LLM Tool Calling Error Handling: Retries and Fallbacks
  9. Patterns AI Agent Error Handling That Actually Works
  10. Non-Retryable Errors
  11. Langchain v1 Agents 5 - Tool Configuration & Error Handling Best ...
  12. Agent Tool Call Retries: Idempotency and Recovery
  13. AIChatAgent terminalizes platform-transient response ...
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