Failure Taxonomies & Triage

7 min read

E17
Operation · Evaluation & Observability

Failure taxonomies & triage.

"It hallucinated" is not a bug report, and a backlog full of it is why teams read a thousand traces and ship nothing — the label describes the smoke, and every agent failure is a cascade whose visible end is the least informative part of it. Label the first step that went wrong instead: that one change turns a pile of anecdotes into a queue with owners, because each first-wrong-step class has exactly one fix and exactly one person who can make it.

STEP 1

Symptom taxonomies do not route work.

The categories teams reach for first — hallucination, loop, refusal, wrong answer, too slow — are descriptions of the last thing a human noticed. They feel like classification because they partition the failures, but they fail the only test that matters: given the label, does anyone know what to change?

  • They are downstream of each other. A retrieval miss at step 2 produces a fabricated citation at step 9. Labelled "hallucination", it lands on whoever owns the prompt. The prompt is fine; the index is stale.
  • They collapse unlike causes. "Loop" covers a tool that returns the same error forever, a plan with no termination condition, and a model that cannot tell two near-identical search results apart. Three teams, three fixes, one bucket.
  • They are unfixable by construction. Nobody is assigned "reduce hallucination". People are assigned "make the retriever return the invoice", which is a ticket.

This is the same accounting problem that credit assignment handles inside a multi-agent system, and it does not go away just because the trace is linear.

STEP 2

Label the first wrong step.

Define one rule and hold everyone to it: the label goes on the earliest step at which a competent operator, holding exactly the context the agent held, would have done something different. Everything after that point is downstream and gets no label at all. It takes about ten minutes to teach and it eliminates most disagreement between annotators.

A first pass over almost any tool-using agent lands in six or seven classes:

  • Task interpretation — the agent solved a different problem than the one asked. Fix lives in the prompt, the clarification policy, or the intake form.
  • Grounding & retrieval — the needed fact was reachable and was not fetched, or the fetched thing was wrong or stale. Fix lives in the index, the query, or the freshness pipeline.
  • Tool selection — right intent, wrong tool, or no tool where one was needed. Fix lives in tool descriptions and the size of the tool set.
  • Argument construction — right tool, wrong arguments. Fix lives in schemas, enums and defaults, not in the model.
  • Tool-error handling — the tool told the truth and the agent ignored it, retried it identically, or invented a value to route around it. Fix lives in error message design and retry policy.
  • State & memory — the agent contradicted or forgot something established earlier in the same run. Fix lives in compaction and the memory write path.
  • Stopping — it quit with the job half done, or would not stop. Fix lives in termination conditions and budgets.

Note what this list is: your tools, your index and your policies, one line each. That is the point. A taxonomy that names components you own produces tickets; a taxonomy that names model behaviours produces meetings. It is the operational face of trajectory evaluation — same shift from outcome to process, but aimed at prioritisation rather than scoring.

STEP 3

Derive it from your own traces, not from this page.

The list above is a starting shape, not an answer. The classes that will actually dominate your queue are specific to your tool surface — a team whose agent lives inside one flaky partner API will find that half its failures deserve their own subclass, and the generic taxonomy will hide that. Build v1 bottom-up:

  • Two people read the same 100 traces independently, writing a free-text sentence for the first wrong step. No categories yet. This is the expensive part and there is no shortcut; budget a day.
  • Cluster the sentences together, out loud, in one session. Categories that need a paragraph to distinguish get merged. Categories holding more than ~25% of the sample get split.
  • Re-label 30 fresh traces independently against the draft and compute agreement. Under about 80% the definitions are not yet sharp enough to hand to anyone else — usually two classes are overlapping, and the disagreements point straight at which.
  • Freeze v1 with written definitions and one real example each. The example does more work than the definition.

The machinery for running this repeatedly — guidelines, annotator agreement, tooling — is annotation & labeling ops. This step is the one-off that tells that machinery what to label.

STEP 4

Sample so the taxonomy is not a portrait of your complaints.

Whatever you read is what you will fix, so the sample decides the roadmap. Reading only thumbs-down traces gives you a taxonomy of what annoys vocal users, which is not the same as a taxonomy of what your agent gets wrong. Draw from three streams and keep them separated:

  • Known-bad — explicit negative feedback, escalations, aborted runs. High density of real failures, badly biased toward visible ones. See production feedback signals.
  • Cost tail — the p99 of steps, tokens or wall-clock. Nearly every run here is a failure, whether or not anyone complained, and this stream is where stopping and tool-error-handling bugs surface first.
  • Uniform random — the boring one, and the only stream that gives an unbiased estimate of how common each class is. It is also where you discover the failures nobody reports because the output looked fine.

Label all three, then weight back to the random stream's prevalence before you prioritise. A class that is 40% of your complaint queue and 3% of production is a support-messaging problem, not an engineering one — and the reverse case, common and silent, is the one that costs you a customer without a ticket. Retention limits on what you can still go back and read are set by trace sampling & retention; decide them before you need the traces, not after.

STEP 5

Every class needs an exit, and the exit is a detector.

A taxonomy that only produces a monthly slide has failed. Each class earns its place by carrying three things:

  • An owner — the person who can change the thing the class names. If no such person exists, the class is defined at the wrong level.
  • Regression cases — at least one trace per class promoted into the eval set, with the input that produced it. This is the main way a class stops recurring; it is also how the eval set stays alive rather than saturating.
  • A detector — a rule, a check or a judge that fires on new occurrences without a human reading the trace. A class you cannot detect automatically is a class you can never say you fixed; you can only say you stopped noticing it.

Detectors are usually cheaper than they look. Tool-error-handling shows up as an identical tool call repeated with identical arguments. Stopping-too-late is a step count above a threshold. Retrieval misses can be caught by asking whether any retrieved chunk contains the entity in the final answer. Reserve the LLM judge for the classes where cheap rules genuinely fail, and calibrate it — the judge is itself a component that can drift, and its false-positive rate goes straight into the counts you are prioritising by. Once the detectors run continuously they feed regression detection, and the taxonomy becomes a live dashboard rather than a study.

STEP 6

Treat "other" as the health metric.

The taxonomy decays, and it decays fastest right when your system is changing most. A model swap changes the mix of what the agent gets wrong; a new tool adds classes; a fixed class stops appearing and its definition rots quietly next to the live ones. One number tells you where you stand: the share of labelled traces landing in other.

  • Under 5% — healthy, or your annotators are forcing traces into the nearest bucket. Spot-check a few "confident" labels to tell those apart.
  • Over about 15% — the taxonomy no longer describes the system. Re-derive from a fresh 100 traces; it is a day, and it is cheaper than a quarter of misrouted tickets.
  • After any model, prompt or tool change, re-label a small fresh sample before trusting the old proportions. Changing the model does not just move the pass rate, it moves the shape of the failures — and tool drift does it to you without any change of yours at all.

Do the cheap version this week: pull 50 random production traces and 20 from the cost tail, and write one sentence per trace naming the first step that went wrong — no categories, no tooling, no meeting. The clusters will be obvious by trace 40, and the two biggest are almost never the ones the team has been arguing about. Then give each cluster an owner and one detector before you write a single fix; without the detector you will not be able to prove the fix worked. Start with tracing in place, because a failure you cannot replay is a failure you cannot classify.