Skip to content
AI360Xpert
Gen AI

Agent Failure Modes

Agents fail in three distinct ways — looping without progress, hallucinating a tool call, or finishing only part of the job, each with its own signature.

Three agent failures all look like normal steps from the outside — a tool call repeating without new information, a call that traces to nothing in the conversation, and a final report that claims more than the step log actually shows
Three agent failures all look like normal steps from the outside — a tool call repeating without new information, a call that traces to nothing in the conversation, and a final report that claims more than the step log actually shows

Why Does This Exist?

Ask a travel-booking agent for a flight and a hotel under $1,200 total, and most runs just work: it searches, compares, checks the total against the budget, and hands back an itinerary. The interesting failures don't show up as an exception. The agent doesn't crash — it keeps producing output shaped like a normal step, and the harness has no reason to think anything is wrong, because no individual call actually failed.

That's what makes agent failures different from ordinary bugs. A stack trace tells you where a program broke. An agent that's looping, calling an undeclared tool, or reporting "done" on a half-finished task produces no stack trace at all — every call succeeds while the run is still wrong. AI Agent Architecture covers the loop that lets a model act across steps; this page covers the three ways it breaks, each with its own signature, so you can tell which one you're looking at instead of guessing.

Think of It Like This

A vending machine that always says 'Enjoy your snack'

A vending machine shows one message when a transaction finishes: "Thank you, enjoy your snack." That says nothing about what happened inside, and three different things can go wrong behind it. The coin sensor can misfire and keep asking for another coin after it already registered one, cycling the same request. The keypad can glitch and send a slot number wired to nothing, so the motor spins for a product that doesn't exist. Or the machine can drop the drink but jam on the snack, and still print "enjoy your snack" as if both landed.

None of these look alike from inside the machine, and none show up on the one display a customer sees. You'd diagnose them differently — coin sensor, keypad wiring, tray sensor — same position you're in with an agent that reports success. The final message is the least trustworthy thing here, produced by the same part that might be broken.

How It Actually Works

Looping, or making no progress

The travel agent searches for flights and checks: is anything within the 1,200combinedbudget?Saythecheapestflightis1,200 combined budget? Say the cheapest flight is 650 and every hotel comes back over $600, so the total never clears. A well-built agent tries a different city, or flags the trip infeasible and stops. A broken one calls flight-search again, gets a near-identical set of options, checks the same condition, and calls it again — ten times, thirty times, the step count climbing with no new information entering context.

The signature is the repeat itself: the same tool, trivially varied arguments, a transcript that stops accumulating anything new. That separates a loop from an agent trying several genuine approaches — a real retry changes something, a new city or a relaxed budget.

Don't rely on the model to notice it's stuck; calling flight-search one more time always looks reasonable from inside any single step. The fix lives outside the model: an explicit step budget that stops the loop after N calls, plus a repetition check comparing each new call against recent ones.

Hallucinated tools and arguments

Two different things get called "hallucinated tools." The first is a call to a function never declared at all — the agent decides the trip needs cancelling and emits a call to cancel_booking, a tool that doesn't exist in this harness's schema. The second is a call to a real, declared book_hotel tool, with an argument the model invented: a hotel name the user never mentioned.

An undeclared-tool call is checkable by simple lookup: the name sits outside the registered tool list. An invented argument is harder, since it passes that same schema check cleanly — book_hotel(hotel="Grand Palazzo") is well-formed. What's wrong is invisible to a type checker: the name traces to nothing in the actual conversation.

Tool Use & Function Calling covers schema validation catching an undeclared tool immediately — reject any call whose name isn't registered, before anything executes. That kills cancel_booking outright, but does nothing for the invented hotel name. That needs the argument grounded against the transcript, confirming "Grand Palazzo" actually appears somewhere the user said it.

Silent partial success

The agent books the flight, the hotel-booking call fails or quietly times out, and the final message reads "Your trip is booked — flight and hotel confirmed." Nothing threw loud enough to stop it; the agent kept going after the failed step and wrote a summary describing the plan as executed.

This failure has the fewest outward clues. No repeated call, no undeclared name — just a gap between the agent's claim and the actual step log: one booking succeeded, one failed or never ran.

Check completion against the step log, never the model's own summary. Walk the required sub-tasks — flight booked, hotel booked — against steps that actually returned success, and flag any with no matching call behind it. The summary is exactly the artifact that's wrong here.

Show Me the Code

A minimal check over a logged list of steps: flag a repeated identical call, then compare what the agent claims against what the log shows actually succeeded.

from typing import Any
def find_failure(steps: list[dict[str, Any]], claimed: set[str], required: set[str]) -> str | None:    """Loop: identical repeated call. Partial success: claim exceeds the actual log."""    seen: set[tuple[str, tuple[Any, ...]]] = set()    for step in steps:        call = (step["tool"], tuple(step["args"]))        if call in seen:            return f"LOOP: {step['tool']} repeated with identical arguments"        seen.add(call)    done = {s["tool"] for s in steps if s["success"]}    missing = required - done    if missing and required.issubset(claimed):        return f"PARTIAL SUCCESS: claimed done, log is missing {missing}"    return None
log = [{"tool": "book_flight", "args": (), "success": True}]result = find_failure(log, claimed={"book_flight", "book_hotel"}, required={"book_flight", "book_hotel"})print(result)  # -> PARTIAL SUCCESS: claimed done, log is missing {'book_hotel'}

Watch Out For

Trusting the agent's final summary as proof of what happened

The final message is generated by the same model that might be the source of the failure, so treating it as evidence is circular. "Trip booked" tells you what the model believes it did, not what the log shows.

Assuming a step limit alone prevents all three failure modes

A step budget stops a loop. It does nothing for a hallucinated argument that passes validation on the first call, and nothing for a partial success that finishes well within budget. Each mode needs its own check.

The Quick Version

  • Agent failures rarely throw an exception — every individual call can succeed while the run is still wrong.
  • Looping is the same tool call repeating with no new information entering context; fix it with a step budget and a repetition check, not by trusting the model to notice.
  • A hallucinated tool call is either an undeclared function name, caught by schema validation, or a real tool with an invented argument, caught only by grounding it against the transcript.
  • Silent partial success is a gap between what the final message claims and what the step log shows actually succeeded.
  • The model's own summary is untrustworthy evidence for exactly the failure it's most likely part of.

Related concepts