The Five Failure Modes of Production Agents (and Postmortems That Actually Fix Them)

Carson Rodrigues

Carson Rodrigues / January 07, 2026

8 min read––– views

fdeai-agentsevals

After enough production agents, you stop being surprised by how they fail. The details vary — this customer's booking flow, that customer's refund policy — but the shapes repeat. I now believe there are about five fundamental failure modes, and every agent incident I've debugged across voice systems and LLM pipelines has been one of them wearing a different costume.

That's actually good news. A short taxonomy means you can build detection for each shape before launch, and it means postmortems converge instead of every incident feeling novel. Here's the taxonomy, then the postmortem process that turns incidents into immunity.


1. Loops: the agent that won't stop trying

The classic. A tool call fails or returns something unexpected, the model retries with the same arguments, gets the same result, retries again. Or subtler: two tools that each "fix" the other's output, ping-ponging. On a metered API this is a money fire; on a voice call it's dead air while the user wonders if anyone's home.

The naive fix is a step cap, and you need one — but a step cap is a crash barrier, not steering. What actually reduces loop rate:

  • Repeat detection. Same tool, same (or trivially varied) arguments, within a session — break the loop and force a different strategy or an escalation, don't just count to twelve.
  • Instructive tool errors. Loops are usually the model failing to understand why a call failed. "Date must be in the future" gets self-corrected; a generic failure gets retried verbatim.
  • An explicit give-up path. Models loop partly because we never tell them what quitting looks like. A prompt that says "after two failed attempts, tell the user what you tried and offer a human handoff" converts loops into graceful exits.

Detection: steps-per-session distribution and step-cap-hit rate on your dashboard. Loops appear as a fattening tail days before anyone complains.


2. Stale context: confidently living in the past

The agent summarizes older turns to fit the context budget, and the summary drops the one detail that mattered. Or working memory says the user wants Tuesday when they changed to Thursday four turns ago. Or a cached retrieval result outlives the fact it described. The agent isn't hallucinating — it's faithfully reasoning over expired truth, which is why this mode is so hard to spot in transcripts. Every individual step looks correct.

Mitigations that have worked for me:

  • Rewrite, don't append. A working-memory scratchpad the agent overwrites each turn ("current goal, current constraints, decisions so far") beats an ever-growing log, because corrections replace stale facts instead of coexisting with them.
  • Re-verify before writes. Anything about to trigger a state change gets re-fetched, not trusted from memory. Fresh reads are cheap; acting on a stale one isn't.
  • Summarize with a checklist. When compressing turns, prompt the summarizer explicitly for corrections and changes-of-mind — the exact items naive summaries lose.

Detection is the hard part — nothing errors. Turn-level groundedness checks in your eval suite catch it offline; in production, watch for user corrections ("no, I said Thursday") as a countable signal. A rising correction rate is stale context announcing itself.


3. Silent tool failures: the empty list that became a fact

A tool times out and returns []. The agent reads "no results" and cheerfully tells the user they have no orders. I've written about this before and I'll keep writing about it, because it remains the most common agent bug in the wild: the tool layer conflating "nothing found" with "lookup failed," and the model turning that ambiguity into a confident falsehood.

The fix is almost boring: tool responses carry explicit status. {status: "ok", results: []} and {status: "error", reason: "timeout"} are different objects, and the prompt tells the model how to behave on each — apologize and retry or escalate on error, never assert absence from a failure. Then your tracing records tool status per span, so the trace shows three timeouts where the transcript shows three confident answers.

This mode is also why I insist tool-call error rate is a product metric, not an infra metric. Every silent failure is a user who was told something false by a system that had no idea it was lying.


4. Over-eager writes: the agent that helped too hard

The agent books the appointment before confirming the time. Issues the refund while the user was still asking whether they could get one. Sends the email draft that was supposed to be reviewed. No error anywhere — the model just crossed the line from "prepare the action" to "take the action" a turn early, because helpfulness is what it's optimized to perform.

This one is a design failure, not a model failure, and the fixes are structural:

  • Consequential writes require explicit confirmation — enforced in the tool layer, not requested in the prompt. The book_appointment tool itself demands a confirmation token minted by an actual user "yes."
  • Distinguish intent from instruction in the prompt, with examples: "can I get a refund?" is a question about policy, not an authorization.
  • Prefer reversible designs. Drafts before sends, holds before bookings, pending states before commits. When the agent jumps the gun on a reversible action, it's an apology; on an irreversible one, it's an incident.

Detection: confirmation-bypass attempts (the model calling a gated tool without a token) are countable and should be near zero. Any rise means a prompt or model change moved the eagerness threshold.


5. Hallucinated success: "Done!" (nothing happened)

The inverse of over-eager writes, and the most trust-corrosive of the five. The agent says it completed the task and didn't — the API call was never made, or failed after the model had already begun narrating success. Streaming makes this worse: the model starts saying "I've booked that for you" while the booking call is still in flight, and if the call fails, the words are already out.

Rules I now build in:

  • Never narrate success before the tool returns success. Sequence the generation so completion claims come after confirmed results — for voice agents this means fillers like "one moment while I book that" are fine, but the confirmation sentence waits for the API.
  • Close the loop with evidence. The success message should carry proof from the tool result: confirmation number, booked time, refund ID. A model forced to cite specifics can't as easily invent the outcome, and your output checks can verify the claim against the span.
  • End-to-end task verification in evals. Assert against the database, not the transcript. An eval that trusts the agent's self-report will happily pass this failure mode forever.

Postmortems: blameless, and aimed at the eval suite

Now the second half: what to do when one of these bites you in production, because it will. I ran postmortems on conventional systems long before agents — the Siemens testing culture leaves a mark — and the discipline transfers, with one big adaptation.

Blameless still means blameless. With agents there's a new person to scapegoat: whoever last touched the prompt. Resist it exactly as you'd resist blaming whoever last deployed. Prompt changes fail for the same reason code changes fail — the system made the failure possible and no test caught it. The interesting question is never "who wrote that line," it's "why did our gates let it through?"

The adaptation: for agent incidents, "root cause" is usually a distribution, not a bug. The agent didn't fail; it failed on 4% of a certain input shape. So the postmortem has to answer distribution-shaped questions:

  1. Which failure mode is this? Name it from the taxonomy. If it's genuinely a sixth thing, that's significant — extend the taxonomy.
  2. What's the blast radius? Query the traces: how many sessions hit this pattern before we noticed? The answer is regularly 10x the reported cases, and it recalibrates severity honestly.
  3. Why didn't detection fire? Not "why did the agent fail" — models fail, that's the operating assumption — but why did no alert, guardrail metric, or canary catch it first. A user should never be your monitoring.
  4. What eval case does this become? This is the non-negotiable exit criterion. Every postmortem produces at least one regression case built from the real failing trace — redacted, minimized, added to the suite with the fix shown passing. An incident that doesn't end as an eval case is an incident you've scheduled to repeat.

That last step is the whole game. Fixes decay — prompts get rewritten, models get swapped, the engineer who remembers the incident leaves. The eval case doesn't decay. Eighteen months later, a model upgrade that would silently reintroduce failure mode number three hits a red test with the incident ID in its name, and someone who's never heard the story is protected by it.

Run the loop long enough and something satisfying happens: the eval suite becomes the institutional memory of every way the system has ever failed, postmortems get shorter because detection fires first, and the five failure modes stop being incidents and become dashboard lines that occasionally twitch.

That's what mature agent operations look like. Not an agent that never fails — an organization that never fails the same way twice.


Related reading

Available for senior AI / contract / FDE work

Building something with AI?

Voice agents, MCP servers, LLM pipelines, agentic workflows — pick a slot, drop a message, or send your email and I'll reply within a day.

or leave your email

Replies within ~24 hours · Remote-first · global · open to relocation