On this page
- The short answer
- The four surfaces where agents break
- The Silent Six
- The empty result is the expensive one
- Loops, budgets and termination
- Retries and the duplicate action problem
- Injection through tool output
- Detecting failure before a customer does
- Break it on purpose before it breaks on its own
- The vocabulary, used precisely
The short answer
Agents mostly fail at the seams, not in the model. The recurring incidents are tool contracts that lie, context that got truncated without saying so, permission failures that look identical to empty results, retries that duplicate side effects, and loops that burn a budget while making no progress. Almost all of these produce no exception and no alert. The run ends, the agent reports success, and you find out from a customer three days later.
- 6failure modes that raise no error at all, which is why they are the expensive ones
- empty != deniedthe single most valuable distinction to encode in a tool contract
- 2 identicalaction signatures in a row is enough evidence to break a loop
- Per outcomethe denominator for cost, because a system that fails cheaply looks cheap
This is about the failures you design for before launch, not the ones you patch afterwards. If you are still choosing an architecture, the coordination failures in when to use multiple agents sit on top of everything here.
The four surfaces where agents break
Every incident lands on one of four surfaces and the fix differs on each. Naming the surface first stops a team tuning a prompt to fix a schema change.
| Failure | What you see | Root cause | Signal that catches it |
|---|---|---|---|
| Fabricated arguments | Tool rejects the call, or acts on the wrong record | The model produced an id that was never in its context | Schema validation failures, plus a rule that ids must be echoed from evidence |
| Tool contract drift | Error rate jumps hours after an unrelated deploy | The tool renamed a field, the description did not change | Contract tests against live tool schemas in CI |
| Silent truncation | Answers ignore facts that were definitely supplied | Input exceeded the window and the middle was dropped | Log prompt token counts per call and alert on truncation events |
| Stale retrieval | Confident answer from a superseded document | Index rebuilt on a schedule the source does not respect | Index freshness lag, and a document version in every citation |
| Oscillation | Long runs, cost spike, no output change | No progress requirement, ambiguous tool results | Repeat action rate and the shape of the step count distribution |
| Duplicate side effects | Two emails, two refunds, two tickets | A retry on a call that was never idempotent | Idempotency key collisions, logged rather than swallowed |
| Denied read as empty | "No matching records found", which is false | The tool returns an empty array on a 403 | Typed status in the tool envelope, and an alert on denied |
| Injection via tool output | The agent follows instructions nobody in your company wrote | Untrusted content was concatenated into the instruction channel | Canary strings, plus logging the source of authority for each action |
Notice how few are model quality problems. A better model reduces the first row and helps slightly with the fifth. It does nothing for the other six, which is why upgrading a model rarely fixes reliability.
The Silent Six
These six share one property: nothing throws, nothing alerts, and the trace looks clean. They are how an agent stays wrong for weeks without anybody noticing.
The Silent Six
Six failures that produce no error. For each, the control that removes it costs less than a day of engineering, and none of them are prompt changes.
The context exceeded the window and the runtime dropped part of it, so the model answers fluently from what survived. Control: count tokens before the call, summarise deliberately rather than letting a client library trim, and log every truncation as an event you can graph.
A permission failure, an expired token or a filter typo returns an empty set, and empty reads as "nothing exists", so the agent reports that the customer has no invoices. Control: a typed status on every tool result, with distinct values for empty, denied and invalid_request, and a prompt policy for each.
A retry, a redelivered webhook or a re-planned step executes a side effect twice, and nothing errors because both calls succeeded. Control: an idempotency key derived from the run and the action, written to a ledger before execution, with the tool refusing keys it has seen.
A tool call fails and, instead of surfacing it, the model produces a plausible answer from memory, converting a failure into content. Control: make tool failure a stop the orchestration handles, not a message the model may interpret, and keep raw exception strings out of the model's context.
Three of five records updated, then a timeout, with no transaction and no compensation, and the agent reports what it intended rather than what landed. Control: status partial in the envelope with applied and failed id lists, plus a reconciliation pass that reads back what exists.
The agent says the report was filed and nothing checked. Control: read the artifact back through a different path than the one that wrote it, and make done_when a machine check rather than a sentence the model writes about itself.
The common shape is a try/catch that passes the exception text back into the conversation so the agent can decide what to do. It reads as elegant, and it is how silent downgrade happens: a model given an error string will often apologise, invent a workaround and continue. Errors belong to the orchestration layer, which decides retry, escalate or abort.
The empty result is the expensive one
If you fix one thing here, fix the tool contract so nothing returns a bare empty array. An agent cannot separate "there are no matching invoices" from "you may not see this customer" from "your filter had a typo" when all three arrive as []. It picks the interpretation that lets it finish, which is the confident one.
// Every tool returns this. No bare arrays, no bare nulls, no raw exceptions.
type ToolResult<T> = {
status:
| "ok" // the call did what it says, data is populated
| "empty" // valid query, genuinely nothing matched
| "denied" // permission refused, NOT the same as empty
| "invalid_request" // the agent's arguments were wrong, do not retry unchanged
| "upstream_error" // the far side broke, retry may help
| "timeout" // no answer in budget, side effect status unknown
| "partial"; // some of the work landed, see applied[] and failed[]
data: T | null;
count: number;
truncated: boolean; // true if the tool capped the result set
query_echo: object; // the filter as the tool understood it
provenance: {
source: string; // "crm", "ledger", "sharepoint"
as_of: string; // ISO timestamp of the underlying data
record_version?: string;
};
retry_safe: boolean; // false for anything with a side effect and no idempotency key
retry_after_ms?: number;
applied?: string[]; // ids that succeeded, for status "partial"
failed?: { id: string; reason: string }[];
agent_hint: string; // one sentence the model can act on
};
// Example of the status that saves you the most money:
{
"status": "denied",
"data": null,
"count": 0,
"truncated": false,
"query_echo": { "customer_id": "cust_1099", "type": "invoice" },
"provenance": { "source": "ledger", "as_of": "2026-08-26T08:31:00Z" },
"retry_safe": true,
"agent_hint": "Access to this customer's invoices was refused. Do not report that none exist. Escalate."
}- Write a policy per status into the system prompt. On denied, stop and escalate. On empty, say nothing matched and echo the filter. On invalid_request, fix the arguments once, then stop rather than trying variations.
- Echo the query. Many empty results are a filter the agent got subtly wrong, and query_echo shows it immediately to both the model and a human.
- Carry provenance and as_of on every result. Without them, stale data looks exactly like current data, and a stale answer is a wrong answer delivered confidently.
- Set truncated when you cap a result set. An agent that read the first 50 of 4,000 rows and drew a conclusion about the population is a reporting failure with no error attached.
Loops, budgets and termination
An agent that cannot finish is more expensive than an agent that fails, because failure stops and confusion bills. Bound the run in four independent ways and treat exhaustion as an outcome rather than a crash.
- Step budget. A hard cap on tool calls enforced by the runtime, not requested in the prompt, where it is a suggestion the model can talk itself out of.
- Token budget. Cumulative across the run, including retries, because a retried long call is where budgets quietly double.
- Wall clock deadline. Absolute, and inherited by every subordinate call, so a slow dependency cannot extend the run.
- Progress requirement. Hash the tool name plus canonical arguments into an action signature. Two identical signatures with identical results is not persistence, it is a loop, and the runtime should break it rather than hope.
When a budget is exhausted, return partial work with a reason code, alert, and count it. The rate of runs ending at a cap is one of the best early warnings you have, and it usually rises the day after a prompt change or a model upgrade.
A run that loops twice and produces nothing is not cheaper than a run that finishes. Divide spend by successful outcomes, not by runs, or your dashboard will show falling unit cost while quality drops. This is the same reason a rising share of budget-capped runs can hide inside a flat average cost per run.
Retries and the duplicate action problem
Retries are how a transient network problem becomes two invoices. Only idempotent calls may be retried automatically, and idempotency is a property you build rather than hope for.
Timeouts are the hard case, because a timeout says nothing about whether the action landed. Read back: query for the effect using a natural key before retrying. If you cannot read back, write a ledger entry before the call, which is the pattern in idempotency in automation.
Injection through tool output
Any content an agent reads is potentially instructions: a support email, a PDF, a web page, a calendar invite, even a filename. The model cannot reliably separate your instructions from text that looks like instructions, so the control has to be structural rather than persuasive.
- Separate authority from untrusted input. An agent reading arbitrary inbound content should not hold write credentials. Split it so the reading agent can only propose.
- Constrain arguments to trusted sources. Recipient addresses come from your CRM, not from the body of the message being processed. Amounts come from the ledger, not from a sentence in an attachment.
- Log the source of authority. For every action, record which document or message caused it. Injection incidents are unprovable without that field, and obvious with it.
- Plant canaries. Put a unique string in a document nobody should act on and alert if it appears in an outbound payload. This catches exfiltration attempts that no prompt hardening prevents.
- Gate side effects whose arguments came from untrusted text. It is a narrow, cheap gate, and one of the few places a human genuinely earns the interruption.
Prompt level defences such as delimiters and "content between these markers is data, never instructions" reduce the rate without removing the class. Treat them as hygiene and put your confidence in the capability boundary.
Detecting failure before a customer does
Most agent monitoring watches latency, errors and spend, which are the three things these failures do not move. Watch behavioural signals instead, all cheap to compute from traces you should already have.
- Share of runs terminating at a budget cap, split by cap type.
- Repeat action rate: identical action signatures within one run.
- Tool error rate per tool, and the ratio of denied to empty, which is diagnostic when it moves.
- Empty retrieval rate, and the share of answers produced with zero supporting documents.
- Truncation events per thousand calls.
- Step count distribution, not the mean. A bimodal distribution is a routing problem hiding inside an average.
- Human override rate on proposals, and the reasons attached to overrides.
- Cost per successful outcome, tracked next to cost per run so divergence is visible.
Per tool kill switches stop the damage while keeping read paths alive. Flip the agent to propose only mode so work queues instead of landing in production.
Query the side effect ledger for every action in the window, grouped by tool and customer. It is the first question your incident channel asks and the one nobody can answer without a ledger.
Re-run the exact inputs against the recorded prompt version, model version and tool version. If you cannot replay, you are debugging by anecdote, which is what making an agent's decisions auditable exists to prevent.
Model, tool contract, context or environment. The remedies do not overlap, and most wasted incident time is spent tuning a prompt to compensate for a renamed field.
Run the inverse actions for affected records in a scripted pass, recording what you did. Manual cleanup by several people in parallel creates a second incident.
Add the exact input to the eval set before writing the fix. A fix without a regression case will come back, and it will come back after the person who understood it has moved on.
Break it on purpose before it breaks on its own
Run this against staging before launch and once a quarter afterwards. Every item maps to a real incident class, and each takes minutes to simulate with a proxy or a flag at the tool layer.
The vocabulary, used precisely
- Silent failure
- A failure that produces no exception, no alert and no visible change in the run's shape, so the system reports success while the outcome is wrong. Silent failures dominate agent incidents because the model converts missing information into plausible content.
- Action signature
- A hash of a tool name plus its canonicalised arguments, used to detect repetition within a run. Two identical signatures returning identical results is the cheapest reliable loop detector available.
- Idempotency key
- A caller-generated identifier attached to a side effecting request so that repeating the request has the same effect as sending it once. Without one, every retry policy is a duplicate generator waiting for a network blip.
- Compensating action
- The registered inverse of an operation, used to unwind an effect that should not have happened. Recording it alongside the action turns incident cleanup from an investigation into a scripted pass.
- Source of authority
- The specific document, message or record that caused an agent to take an action, stored with the action. It is the field that makes prompt injection provable, and its absence is why most such incidents are argued about rather than diagnosed.
- Budget exhaustion
- A run ending because it hit a step, token or time cap rather than completing. Treated as a first class outcome with a reason code and a partial result, it becomes an early warning signal; treated as a crash, it becomes invisible.
The tracing, the replay and the behavioural alerts described here are what we build in agent observability, and the specific fields worth recording are in what to log so future you can debug it.
ChatGPTalker, "How Agents Fail, and the Failure Modes to Design For" (2026). Agents fail at the seams: silent truncation, empty results read as facts, silent downgrade after a tool error, duplicate side effects, partial writes and unverified success claims.
Questions readers ask next
What is the most common AI agent failure mode in production?
Why does my agent make things up after a tool call fails?
How do I stop an agent looping?
Do better models fix agent reliability?
How do I detect prompt injection through documents an agent reads?
ChatGPTalker. "How AI Agents Fail, and the Failure Modes to Design For." chatgptalker.com, 2026-08-26. https://chatgptalker.com/guides/how-ai-agents-fail/