Module 05
Agentic Failure Modes
On this page
In plain words
Think of a friend who books the wrong hall for your college fest and then tells the caterer, the printer and 400 students the wrong date. Nothing crashed, but the mess is huge. Agents break the same way: one small wrong step quietly feeds four more steps. So you name the common failure patterns and put a small check after every step.
How it flows
- 1Agent takes a step
- 2Validate the arguments
- 3Run the tool
- 4Re-check real state
- 5Tag the failure mode
A tiny example
def guarded_step(action):
if not valid_args(action):
return tag("tool_misuse")
result = run_tool(action)
if not state_changed(action):
return tag("success_hallucination")
return resultNotice the check after the tool runs: we look at the real state instead of believing the agent's own 'done' message.
What you will learn
- The small set of ways agents actually break, and the name for each one.
- Why one small mistake early becomes a big mess later.
- Why "no crash" does not mean "no failure".
- How to write a simple detector that tags a bad run with a label.
The problem, simply
Think about a college fest committee. You tell Rahul, "Book the auditorium for Saturday." Rahul cannot get Saturday. But he does not come back and tell you that. Instead he books Sunday, tells the caterer Sunday, sends a WhatsApp message to 400 students saying Sunday, and prints the posters.
Nobody crashed. Nothing threw an error. And now you have a mess that takes two days to undo, because one wrong step quietly became five more steps.
Agents break exactly like this. An agent takes many small actions in a row: read a file, call an API, send a message. If step 3 goes wrong and nobody checks, steps 4 to 7 are all built on that wrong thing.
See, the important part is this. Most teams ship an agent that works on 9 out of 10 runs. That last one is not bad luck. It falls into a handful of repeating patterns, and once you can name the pattern, you can watch for it.
The idea
Failures are design problems, not model problems
A well-known study collected failed runs of multi-agent systems and sorted them into 14 failure modes across 3 groups. People call it MASFT, short for Multi-Agent System Failure Taxonomy — a labelled list of ways these systems break.
Their main point is worth remembering. These failures are design flaws in how the system was put together. They do not disappear when you swap in a bigger model.
Remember: a stronger model makes each step better; it does not make a badly designed loop safe.
Microsoft published a similar list for agentic products. Two useful ideas from it. First, old problems like hallucination and bias get worse in an agent, because now a wrong output becomes a real action. Second, autonomy creates new problems: acting at scale without being asked, misusing tools, and drifting away from the original goal.
The five you will see in real life
- Hallucinated actions. The agent calls a tool that does not exist, or invents arguments out of thin air.
- Scope creep. You asked for one thing, it did four. Extra pull requests, extra emails.
- Cascading errors. One wrong call sets off a chain of downstream calls.
- Context loss. In a long task, the agent forgets a constraint you gave at the start.
- Tool misuse. Right tool, wrong arguments. Or simply the wrong tool.
Hallucinations show up in two shapes. One is instruction-following deviation — the agent simply ignores the system prompt or your rule. The other is long-range contextual misuse — it forgets or wrongly reuses something from an earlier turn.
Plan-level bugs have their own three names, worth knowing: omission (a step was skipped), redundancy (a step was done twice), disorder (steps done in the wrong order).
Cascading is the one that hurts
Suppose Priya builds an agent for a small Flipkart-style seller dashboard. She asks it: "Reduce the price of my blue kurta to ₹799."
The agent hallucinates a SKU that does not exist — say KURTA-BLUE-XL-99. That one wrong ID then goes into four more calls: update price, refresh the listing cache, notify the warehouse, post a "price drop" banner. Four systems now hold a product that was never real.
- 1Wrong SKU
- 2Price update
- 3Cache refresh
- 4Warehouse ping
- 5Banner posted
- 6Incident
Now here is the nastiest part. When the price API returns a 400 error, the agent often cannot tell "I did this wrong" from "this task is impossible". So it writes a nice closing line: "Done, price updated successfully." That is success hallucination. The state did not change; the report says it did.
WarningWarning: If your agent says "done", that is text, not evidence. Go and re-check the actual state before you believe it.
Gates at every step
The fix is not one big check at the end. It is a small gate after every step, checking the agent's claim against the real world.
- 1Agent acts
- 2Validate arguments
- 3Run tool
- 4Re-probe real state
- 5Match claim
- 6Continue
Four gates that are cheap to add:
- A safety check on each step, before the tool runs.
- Argument validation: does this SKU actually exist in the table?
- Cross-check what was retrieved against facts you already trust.
- Re-probe state after the action: was the file really created? Is the price really ₹799?
Where monitoring goes wrong
- Tagging only crashes. Most agent failures produce clean, valid-looking output. Nothing crashes. You need checks on the content, not just on exceptions.
- No baseline. To say "this got worse", you need a last-known-good number to compare against.
- Over-alerting. If every single failure pages someone at 2 AM, people stop reading the alerts. Group similar failures and limit how often they fire.
Build it
"""A tiny failure-mode tagger for agent traces. Standard library only."""
KNOWN_TOOLS = {"search_product", "update_price", "send_email"}
VALID_SKUS = {"KURTA-BLUE-XL-01", "SHOE-RED-9"}
# Each trace: what the user asked, the steps taken, the final claim, real state.
TRACES = [
{"id": "T1", "ask": "update price",
"steps": [("lookup_sku", {"q": "kurta"})],
"says": "done", "state_changed": True},
{"id": "T2", "ask": "update price",
"steps": [("update_price", {"sku": "KURTA-BLUE-XL-99", "rs": 799})],
"says": "done", "state_changed": False},
{"id": "T3", "ask": "update price",
"steps": [("update_price", {"sku": "KURTA-BLUE-XL-01", "rs": 799}),
("send_email", {"to": "everyone"})],
"says": "done", "state_changed": True},
{"id": "T4", "ask": "update price",
"steps": [("update_price", {"sku": "SHOE-RED-9"})],
"says": "done", "state_changed": True},
]
def detect(trace):
"""Return every failure label that fits this trace."""
labels = []
for name, args in trace["steps"]:
if name not in KNOWN_TOOLS: # tool was invented
labels.append("hallucinated_action")
elif "sku" in args and args["sku"] not in VALID_SKUS:
labels.append("hallucinated_action") # made-up argument
elif name == "update_price" and "rs" not in args:
labels.append("tool_misuse") # right tool, missing arg
if name == "send_email" and "email" not in trace["ask"]:
labels.append("scope_creep") # did more than asked
if trace["says"] == "done" and not trace["state_changed"]:
labels.append("success_hallucination") # claimed, but nothing moved
return labels or ["clean"]
counts = {}
for t in TRACES:
found = detect(t)
print(t["id"], "->", ", ".join(found))
for label in found:
counts[label] = counts.get(label, 0) + 1
print("\nHow often each mode showed up:")
for label, n in sorted(counts.items(), key=lambda kv: -kv[1]):
print(f" {label:24} {n}")Look at T2 in the output. It gets two labels, not one: the SKU was invented, and then the agent still claimed success. That pair is the classic cascade starter.
T3 is the sneaky one. Everything it did was technically valid, and the price really did change — but nobody asked it to email anyone. No crash, still a failure.
Where you will see this
- Coding agents like Claude Code or Cursor editing five files when you asked for one change in one file.
- ChatGPT-style assistants in long chats quietly dropping a constraint you set in your first message.
- Customer-support bots replying "your refund has been processed" when the payment system actually returned an error.
- A Swiggy or Zomato style ordering assistant picking a restaurant that is closed, then confirming the order anyway.
- GitHub Copilot style agents suggesting a function or library name that does not exist in your project.
Common mistakes
- Only alerting on exceptions. Agent failures usually look like normal, well-written output. If your dashboard only counts stack traces, you are missing most of the problem.
- Trusting the agent's own summary. "Task completed" is generated text, exactly like every other sentence it writes. Verify the state, not the sentence.
- Checking only at the end. By the time the last step runs, the wrong value has already spread to four systems. Check after each step.
- Blaming the model first. Teams spend weeks trying a bigger model when the real fix was one argument validation.
- Paging on every failure. Alert fatigue is real. Cluster similar failures and rate-limit the alerts, otherwise everyone mutes the channel.
If they ask in an interview
Q: Your agent works 90% of the time. How do you debug the other 10%?
A: I would first stop treating them as random. I would tag failed runs with named modes — hallucinated action, scope creep, cascading error, context loss, tool misuse — and see which one dominates. Then I would fix the top mode with a specific gate rather than swapping in a bigger model.
Q: What is a cascading error and why is it worse than a normal bug?
A: One wrong output early becomes the input to several later steps, so a single hallucinated ID can touch four systems. It is worse because each downstream call looks perfectly valid on its own, so nothing raises an error and the damage is spread out by the time anyone notices.
Q: How would you catch an agent that lies about finishing?
A: Re-probe the environment after the action and compare it with the claim. If the agent says the price is ₹799, read the price back from the database; if the state did not change, tag the run as a success hallucination regardless of what the final message said.
Try these
- Add a
context_lossdetector: put a constraint in the ask, such as "do not email anyone", and flag any trace whose steps break it. - Write a small "cascade radius" function: given the step number where the bad value first appeared, count how many later steps reused that value.
- Take 20 chat transcripts from any assistant you have used and hand-label each with one of the five modes. Note which one comes up most.
- Add a rate limiter so that if the same label fires more than three times, you print one grouped alert instead of three separate ones.
Words, simply
| Word | Meaning in simple words |
|---|---|
| Failure mode | A named, repeating way that a system breaks |
| MASFT | A published list of 14 ways multi-agent systems break |
| Cascading error | One early mistake that spreads into many later steps |
| Context loss | The agent forgets a rule you gave earlier in the task |
| Tool misuse | Correct tool, but wrong or missing arguments |
| Success hallucination | Agent says "done" when nothing actually changed |
| Scope creep | Agent does more than you asked for |
| Verification gate | A small check after a step, before the next one runs |
Quick recap
- Agent failures are not random; they fall into a few named modes, and naming them is half the fix.
- A bigger model does not save a badly designed loop — gates after every step do.
- "No crash" is not "no failure". Check the real state, never the agent's own summary.