AI agents

Routing a Failed Repair to a Human Queue: Codes, Ownership

Antonio Evans9 min read

A flow diagram in flat style: a failed repair card enters a router box labeled 'Reason code'.
Key points

Key points

  1. 1A failed repair must carry a reason code at the moment of failure, because a later reviewer cannot reliably reconstruct whether the cause was the prompt, the tool or the data.
  2. 2Each reason code maps to exactly one owner, one required action and one deadline measured from the failure timestamp.
  3. 3Unmatched failures go to triage with the shortest clock, so the taxonomy itself gets fixed instead of silently absorbing misroutes.
  4. 4Reason codes work like denial and quarantine codes in other fields: they only pay off when the code is assigned before the fix workflow starts.

Code the failure before anyone fixes it

Most repair loops fail in the same quiet way. A model produces an output that does not pass its check, an automatic repair attempt runs, the repair also fails, and the item lands in a general queue labeled "needs review." The queue grows, nobody knows who should close anything, and the same failure keeps arriving through different inputs.

The fix is a decision made in the failure-handling code, not in a meeting. At the moment the repair fails, the system writes a reason code onto the item. That code is the routing key. It says what kind of cause produced the failure, which in turn says who can change that cause.

This is the same discipline used in healthcare revenue cycle work, where a payer denial is coded before any appeal or correction starts. As one analysis of denial programs puts it, a payer reason code tells you what the payer's system determined but not whether the authorization was never requested, was requested for the wrong service, or was obtained and never attached, and each of those is a different process failure with a different fix (Revecore). The same gap exists in repair queues. "Repair failed" is not a cause.

The second half of the discipline is ownership. Quarantine workflows for flaky tests make the same point: a quarantined item must be kept in a reviewed manifest with an owner, an issue, a reason and an expiry date, and CI should fail when those records expire (QA Job Fit). A repair queue without owners and deadlines is a quarantine without expiry, which is to say a backlog.

A reason code taxonomy that routes to real owners

Five codes cover the practical cases. Each has a single owner type, one required action and a deadline that starts at the failure timestamp, not at the moment someone notices the item.

Reason codeAssigned whenOwnerRequired actionDeadline from failure timestamp
PROMPT_AMBIGUITYThe output is defensible under a plausible reading of the instructions, or two runs disagree on what the task wasPrompt owner (the person who owns the prompt or system instruction)Rewrite or add a constraint, then replay the failed input and the regression set5 business days
TOOL_DEFECTThe trace shows the tool returned an error, wrong shape, stale data or a timeoutTool owner (the team that owns the integration or function)Fix or harden the tool, add a contract test for the failing case2 business days
DATA_QUALITYThe right answer requires a record that is missing, stale, contradictory or wrongly joinedData owner (the steward of the source table or document set)Correct the record at source, document the lineage gap, backfill if widespread3 business days
POLICY_BLOCKA guardrail or policy correctly refused the action, and the request itself is the problemSafety reviewerDecide: allow, restrict further, or escalate to a policy change with sign-off1 business day
UNMATCHEDNo code fits, or two codes are equally plausibleTriage (the team that owns the taxonomy)Assign the correct code, then fix the classifier so the next case routes automatically1 business day

The relative deadlines carry the interesting decision. Tool defects get a tight clock because they usually break many callers, not one. Policy blocks get the tightest because a guardrail firing is a signal about an action the system should or should not be able to take, and leaving it open is a standing risk. Prompt ambiguity gets the longest clock because identifying and fixing it is genuine writing work, and rushing it produces prompt churn that breaks other cases.

The 1-day clock on UNMATCHED is the design rule that keeps the whole scheme honest. If unmatched items sit, the taxonomy silently rots and everything drifts back into a general queue. Triage owns the taxonomy, so a misfit is triage's work, not the requester's.

Diagnosis first: traces make the code evidence, not opinion

A reason code is only useful if it is derived from evidence. Without a record of what happened inside the run, three people will assign three different codes to the same failure.

Production agent failures are hard to debug precisely because a single run involves multiple model calls, tool calls, retrievals and state changes, and any step can introduce the error while a flat log leaves every step disconnected (Braintrust). The same source gives the canonical example: a support agent gives a wrong refund answer, the final log shows only the confident wrong answer, and the actual cause is a retrieval step returning an outdated policy document. That distinction is exactly the difference between DATA_QUALITY and PROMPT_AMBIGUITY.

The broader workflow literature makes the same point: most agent failures are not only prompt failures, and a bad answer can come from weak instructions, the wrong tool call, missing context, a bad retrieval result, a handoff to the wrong specialist, a schema mismatch or a guardrail that fired too late (Medium, No Time). Treating the prompt as the whole system is how a TOOL_DEFECT becomes a prompt rewrite that never fixes anything.

So the rule is: no code without a trace. The three-question test below turns a trace into a code in under a minute.

1. Did the step that failed do what it was told, given what it received?
   No  -> check the input to that step.
         Input was correct and task clear -> TOOL_DEFECT
         Input was stale/wrong/missing     -> DATA_QUALITY
   Yes -> go to 2.

2. Was the task genuinely ambiguous across plausible readings?
   Yes -> PROMPT_AMBIGUITY
   No  -> go to 3.

3. Did a guardrail or policy refuse it?
   Yes -> POLICY_BLOCK
   No  -> UNMATCHED (triage owns it within 1 business day)

Note the ordering. Input checks come first because they are objective: the trace either shows a stale document or it does not. Ambiguity comes second because it is a judgment call, and it should only be reached once the mechanical causes are excluded. Policy comes third for the same reason.

Ownership rules that survive contact with a real team

Assigning a code is easy. Making ownership stick is the operational work. Four rules do most of it.

One owner, never a team name. "Data team" is not an owner. The item needs a named person or a named on-call rotation. Severity and priority models in incident response depend on the same discipline: declaring a severity should immediately determine who is paged, whether an incident commander is required and what follow-up is owed, because responders should be mitigating, not negotiating who joins (Rootly). A repair queue item should behave the same way.

The owner at assignment stays the owner for the first cycle. Reassignment is the most common way a queue stalls. If the prompt owner believes the cause was data, they log that as a comment with the trace evidence and the assigned owner makes the first decision. Only the owner can return to triage with the UNMATCHED code attached.

Code ownership maps to the code. Where the repository already has code owners for the prompt file, the tool wrapper or the data pipeline, the reason code should map to the same owner. This is how quarantine systems attribute flaky tests: webhooks carry the file path, the code owner and the reason the test was marked flaky, and those drive the alert to the owner and the ticket creation (Trunk). Repair routing can reuse that mapping instead of inventing a second one.

Every code is verifiable. Before a code is assigned, the failure must reproduce against the same code and inputs, or the trace must show a non-reproducible condition such as a timeout or rate limit. The distinction matters: re-running on the same commit and getting a pass where the first run failed indicates a non-deterministic problem, while a consistent failure indicates a real defect (ContextQA). The same logic separates a repair that failed because of ambiguity from one that failed because of timing.

Deadlines, escalation and the numbers that show it working

Measure the deadline from failure_timestamp, the moment the repair attempt failed, not from when the item was read. Store it alongside the code:

{
  "item_id": "rep_8842",
  "failure_timestamp": "2026-03-04T14:32:01Z",
  "reason_code": "TOOL_DEFECT",
  "owner": "integrations-oncall",
  "required_action": "fix contract: order_lookup returns null for guest orders",
  "due_at": "2026-03-06T14:32:01Z",
  "trace_span_id": "span_9f2a",
  "escalation": ["owner_lead@t+1d_overdue", "queue_owner@t+2d_overdue"]
}

Two escalation steps are enough. At one day overdue, the owner's lead is notified. At two days overdue, the queue owner reassigns or explicitly accepts the delay in writing. Auditing standards in other domains treat the acceptance of risk as an explicit, documented judgment rather than an implicit one; audit sampling guidance, for example, frames the justification for accepting uncertainty as resting on a stated relationship between cost and the consequences of error (PCAOB AS 2315). Overdue repairs deserve the same treatment.

Now a worked example with numbers. Suppose one queue receives 300 failed repairs in a week:

Reason codeItemsShareDeadlineOn-time closure
PROMPT_AMBIGUITY9632%5 days71%
TOOL_DEFECT6020%2 days88%
DATA_QUALITY8428%3 days64%
POLICY_BLOCK217%1 day95%
UNMATCHED3913%1 day81%

Read that table as a worklist. Prompt and data together are 60% of volume, so those two owners get the most capacity. Data quality is the worst on-time performer at 64%, which is the signal to look at whether the data owner has the authority to correct records or only to report them. And 13% unmatched is high; a healthy figure is a few percent, and every unmatched item is evidence the classifier needs a new branch or a clearer definition.

The comparison that matters for capacity planning is code mix, not raw volume. A week where the total drops from 300 to 250 but UNMATCHED rises from 13% to 25% is a worse week, because it means routing is degrading and the apparent improvement will reverse.

Turning recurring failures into permanent coverage is the last step. The practice that works is to capture the failed trace, label the failure mode, promote it into a versioned regression dataset and gate releases on a scorer for that pattern (Braintrust). The reason code you already assigned is the label. Regression testing exists because a prompt change that fixes one complaint can quietly break ten other answers (Langfuse), which is why each PROMPT_AMBIGUITY fix should ship with the failing case attached.

Where repair queues usually go wrong

Too many codes. Ten codes produce inconsistent assignment and a routing table nobody maintains. Five is enough for most teams; add a sixth only after UNMATCHED has held a consistent pattern for several weeks.

Codes assigned after the fix. If the reviewer fixes first and codes second, the code records what the fixer did rather than the cause. Assign the code at failure time, in code. The denial-analysis workflow is explicit that categorization belongs at the initial review and that programs stay reliable only when the workflow requires it: no root cause code, no move to appeal or correction (Revecore).

A general queue as a fallback. A fallback queue without a deadline is where items go to be forgotten. The fallback is triage with a one-day clock, and triage's job is to change the taxonomy.

Treating the queue as the solution. Quarantine is a tool, not the whole solution: without a process for fixing or removing items it produces a growing backlog and the same erosion of trust it was meant to prevent (Trunk). A repair queue with no deadline discipline is that backlog with better labels.

Using the code as a blame instrument. Reason codes route work; they do not score people. The moment a code becomes a performance metric, it gets assigned defensively and stops being evidence.

If the item is a policy block, the safety reviewer needs the full record. Agent audit logging practice is a useful reference: each action is recorded as a structured entry with a timestamp, the invoker, the source and the outcome, so any entry can be traced back to the request that caused it (Nylas). A POLICY_BLOCK item arriving without that record forces the reviewer to guess, which is how a one-day deadline becomes a week.

For teams running this across several model providers, the mechanics of the repair loop should not depend on which model produced the output. Quanta Inference (https://quantaintelligence.ai/inference) exposes text, image, video, audio and embedding models through one OpenAI-compatible API key, so the failure-handling code that emits reason codes stays the same whichever model is behind the call.

FAQ

FAQ

Should every failed repair get a human owner?

Only repairs that fail after the allowed automatic retries. A first-pass repair that fails and then succeeds on a deterministic retry is not a queue item; it is a diagnostic signal. Route the item to a human when the repair fails, the trace shows a cause a human must change, and no automatic process can resolve it.

What is the difference between a reason code and a severity level?

A reason code says who can change the cause. A severity level says how fast the organization must act. They are separate scales: severity decides who gets paged and what follow-up is owed, while priority decides how fast work starts and what it displaces (Rootly). A repair queue needs both, but the reason code is what routes the item to the right owner.

Who assigns the reason code when it is unclear?

The system assigns UNMATCHED automatically, and triage owns the decision. Triage has one business day to pick the right code, hand the item on, and then change the classifier or the code definitions so the next similar case routes without a human. If UNMATCHED stays high, the taxonomy is the problem, not the failures.

How do I stop the deadlines from becoming shelf art?

Make the deadline part of the item and make the escalation automatic. Notify the owner's lead one day overdue and the queue owner two days overdue, and publish the on-time rate by reason code every week. A deadline with no escalation is a date, not a control.

Does the taxonomy need to match how other teams code failures?

Where another function already codes failures, reuse their mapping rather than inventing a parallel one. Code ownership for flaky tests already travels with alerts and tickets as file path, owner and reason (Trunk), and audit sampling work is built on the same principle that a sample only supports a conclusion when the evaluation method is defined in advance (PCAOB AS 2315). One mapping per repository and pipeline beats a second taxonomy that drifts.

Sources

Sources

  1. How to Build a Denials Root Cause Analysis Program
  2. Quarantine Flaky Tests in a CI Pipeline
  3. how to trace and debug AI agents in production - Braintrust
  4. How to Debug AI Agents with Traces and Evals | No Time
  5. Incident Priority vs Severity: P1, P2, P3 and SEV Levels
  6. Eradicating flaky tests - Trunk.io
  7. Flaky Tests in Automated Testing: Root Causes & Fixes
  8. AS 2315: Audit Sampling
  9. How to turn LLM production failures into regression tests
  10. LLM regression testing: fail CI before regressions ship
  11. Audit AI Agent Activity (Claude, Copilot, MCP)
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles