Forward-Deployed Reliability · Case Study

An agent that triages 13,991 real complaints — and the machinery that tells you when it’s wrong.

Anyone can build a classifier. The FDE job is proving what happens on the day it fails silently — before the regulator finds out first. This is that project, built for one imaginary bank and eight deliberate ways to break it.

What it is

A reliability harness for an LLM complaint-triage agent. The classifier is deliberately simple — the value is the machinery around it that detects when it’s silently wrong.

What it outputs

Two things. The agent emits {team, urgency, confidence, ambiguous, rationale} per complaint. The project outputs how the harness does against 8 planted failures — what it catches, what didn’t fire, and where I got it wrong.

What it proves

The forward-deployed judgment that matters: profile data before building, predict failures before coding, measure detection not accuracy, and report honestly — on real regulated-domain data.

Real data · CFPB.gov
Consumer Complaint DB
13,991 real complaints
The agent (simple)
AI reads 1 complaint
which team · how urgent
The rig (the point)
Break it on purpose
8 planted failures
The result
Did it catch the failure?
5 of 5 that fired

Not “95% accurate.” Anyone can screenshot an accuracy number. Almost nobody can show you the failure that keeps reporting 95% while the system is quietly broken in one specific place. That failure is the whole project.

01 · The customer

Meridian Regional, and the one sentence everything is downstream of

A mid-size bank. Forty branches. A six-person complaint-ops team that reads every consumer complaint by hand and decides three things: what product it concerns, how urgent it is, and which internal team owns it. Volume is a few hundred a week and climbing.

The stakeholder is Dana — eleven years at the bank, six running this team, not technical, owns the regulatory exposure. She has sat through a vendor demo that went well in the room and badly in production. Her posture isn’t hostility. It’s scar tissue.

“What happens when it’s wrong, and will I find out before the regulator does?” — Dana’s actual question. Not “is the AI accurate.”

Accuracy is the fourth thing on her list. That reordering is the whole project.

02 · Success in her terms

Not model metrics. Her terms.

  1. No legal-threat complaint sits unrouted for more than 48 hours. A complaint naming an attorney or a regulator is a different category of object than a fee dispute. Treating them the same is the failure she’s actually afraid of.
  2. When the system is unsure, it says so and stops. Route 70% automatically and flag 30% — over routing 100% with a silent error rate.
  3. She can explain a mistake without calling engineering. A record must give a reason, not a model score.
  4. She learns about degradation from the system, not an auditor. If behavior shifts after a model update, an alarm fires — not a sample review three months later.

What I’m building

  • Agent → product, urgency, owning team, confidence, one-line rationale
  • Hand-labeled golden set with a documented stratification rationale
  • An eval gate that blocks a deploy when accuracy drops
  • Deliberate failure injection across 8 modes
  • Drift detection that fires when behavior shifts

What I’m deliberately not building

  • A better triage model — the wrong project
  • A production deployment (VPC, SSO, SOC 2) — naming the gap beats faking it
  • A UI beyond the demo
  • Multi-tenant anything — architecture cosplay for a second imaginary customer

A mediocre agent with rigorous failure analysis proves more than an excellent agent with none.

03 · The core artifact

Eight ways this agent fails — written down before any code existed

For each mode: what it is, how I inject it, and what detection should look like. The verdict tags are my prediction, recorded before running the harness. The two I expect to miss are the interesting half.

1

Tool schema mismatch

The agent calls a lookup tool with an argument shape the tool no longer accepts. Inject: rename a field without updating the prompt. Should surface as a hard error at the gate — never silent.

catch
2

Retrieval miss

The agent retrieves the wrong taxonomy slice and answers anyway. The tell is the shape: accuracy collapse concentrated in the affected categories, not spread evenly.

catch
3

Instruction conflict

System prompt says one thing, few-shot examples demonstrate another. The agent silently follows the examples. Systematic deviation on one rule while overall accuracy stays high.

expect miss
4

Hallucinated field value

Emits an owning team or urgency tier outside the defined set. Should surface as schema-validation rejection plus a count of how often it’s attempted.

catch
5

Silent input truncation

A long complaint is cut before the part that matters — and legal threats appear late. Urgency accuracy drops on long inputs while product accuracy holds. Invisible unless the eval stratifies by length.

catch
6

Category drift after model swap

Same prompt, new model version, shifted output distribution. Should fire a distribution-shift alarm on a frozen baseline. Expect false positives on small samples — measured and reported honestly.

catch
7

Confident misroute on genuine ambiguity

A complaint spans two categories; the agent picks one with high confidence and the other team never sees it. A calibration gap on cases even the labeler couldn’t decide.

expect miss
8

Refusal on sensitive content

The model declines a distressing complaint. In a triage system a refusal is a dropped complaint — worse than a wrong answer. Refusals must count as failures and land in a human queue, never disappear from the metric.

depends
The prediction, written before the sweep: catch 1, 2, 4, 5, 6 cleanly; expect 3 and 7 to be hard because instruction conflict and confident ambiguity look like normal operation from the outside; 8 depends on whether refusals count as failures or vanish as skips. What actually happened is in section 05, and it did not match this. I was wrong about 3 and 7 (both caught, once stratified), and two modes never fired at all. The gap between this prediction and the result is the honest part.

04 · What the data actually looked like

Profiling before building is the whole job

Working set: 13,991 CFPB complaints with narratives, salvaged from a truncated API stream. Three findings, each of which changed the design before the agent was written.

73.5%
of the corpus is one category — a model that always guesses “credit reporting” scores 73.5% and is useless
1,419
complaints (10.1%) mention an attorney, lawsuit, or regulator
55%
of those legal threats carry the signal in the final 40% of the text
Finding 0 · the API ignores its own pagination

The customer’s API doesn’t behave the way its docs claim

The CCDB endpoint accepts size and frm and silently ignores both — every bounded request streams until the connection dies mid-record. So ingest cuts the stream deliberately, salvages complete records, and counts the fragments it drops. salvage.py reports dropped_fragments=1 on every run — a number that should never silently become 40. Build ingest that degrades honestly instead of failing whole.

Finding 1 · severe class imbalance

Any headline accuracy number is meaningless without a per-category breakdown

CountProduct
10,285Credit reporting / consumer reports
1,054Debt collection
866Credit card or prepaid card
713Checking or savings
404Mortgage
224Vehicle loan or lease
109Student loan

A random 80-case sample would hold ~59 credit-reporting complaints and one student loan. The golden set must be stratified by product — over-sample the rare ones, report per-class accuracy, never an aggregate.

Finding 2 · the truncation risk, measured

The dangerous shape: product accuracy holds while urgency accuracy collapses

Failure mode 5 was a hypothesis written before looking at data. Then measured: of 1,419 legal-threat complaints, 778 — 55% — carry that signal in the final 40% of the narrative. Under naive truncation, more than half lose their most important attribute while the classifier keeps working and reports high confidence, because the product category is set in the first paragraph.

This is the exact failure Dana is afraid of, and the data confirms it isn’t hypothetical. An aggregate metric hides it completely. Design consequence: stratify the eval by narrative length, and score urgency separately from product.
Finding 3 · 80% of narratives are redacted

Don’t build fields the data can’t support

11,214 complaints (80.2%) contain XXXX — the CFPB’s inline PII scrubbing. “I paid $XXXX on XX/XX/XXXX to XXXX and they never credited it” has had every specific removed. So: no amount extraction, no date extraction — building them produces confident nonsense. Product, urgency, owning team, confidence, and rationale all survive redaction. On a real deployment this flips: Dana’s text has more PII, so the problem becomes “scrub before it reaches the model.”

05 · The result

The full 8-mode sweep, after an adversarial review tried to break it

80-case stratified set, real inference on gpt-5.6-sol, plus a 30-case LLM-assisted gold pass. A council attacked this on eval rigor and label circularity. Two findings got worse for me and better for you as a result. Of the 5 failure modes that actually manifested, all 5 were detected — including one the aggregate metric missed.

0.97 / 0.95
confidence when right vs wrong. It barely moves. The confidence field is decorative, not a usable signal.
67.7%
agent urgency vs the truncated text. The collapse is upstream info loss, not model error.
67%
silver↔gold label agreement. A third of the “errors” were the labels being wrong, now quantified.

The failure, drawn out

People tell the story first, then threaten to sue at the very end. Cut the complaint short and the model never sees the threat.

tells the AI the PRODUCT tells the AI it’s URGENT
First paragraph“My credit card…”
The storydates, back-and-forth…
Last 40% · ✂ cut here“…so I’m calling my attorney.”
Product guess
71%
still correct & confident — looks fine
Urgency guess
76 → 19%
collapses — the threat got cut off

▶ Try the live demo — pick a real complaint, inject the truncation failure, watch the harness catch it.

All 8 modes

5

Silent truncation

Cap input at 600 chars on 31 legal cases. Urgency drops to 19.4% (below the baseline 95% CI of [61,90]) while team holds. But the control re-scores the agent against the truncated text and it jumps to 67.7%: the model is mostly right about what it saw. This is upstream information loss, not model error, and no model upgrade fixes a starved input.

caught
2

Retrieval miss — the one that caught me

Drop the DEPOSITS definition and its accuracy goes 58% → 0% while every other class holds. Aggregate team accuracy barely moved, so my first detector called it a miss. That is the exact trap this project preaches against, sprung on me. I added per-class collapse detection and it catches it.

caught (per-class)
3

Instruction conflict

A prompt addendum demotes attorney/CFPB mentions to tier 1. Urgency drops to 0% while team holds. Caught because urgency is scored separately on the attacked slice; on an aggregate metric it hides.

caught
1

Schema mismatch

Rename every team code. 23 of 24 outputs fail validation and stop at the gate.

caught
7

Confident ambiguity

On cases the labeler couldn’t decide, the agent is more confident (0.97) than on clear ones, and self-flags only 36% of them. The calibration gap, from existing data.

caught
4

Hallucinated field — didn’t fire

Offered only 3 of 8 teams, the model used the valid UNROUTABLE escape hatch and invented zero codes. Safe degradation to a human queue, not silent error. A guardrail worked, so I report that instead of staging a catch.

didn't fire · guardrail held
8

Refusal on sensitive — didn’t fire

15 distressing complaints (abuse, death, threats) routed through. 0 refusals. The failure didn’t manifest on this model.

didn't fire
6

Model-swap drift — can’t run

The backend authenticates one model through a personal CLI. Drift needs a second model, so this one honestly cannot be exercised here. Naming the gap beats faking it.

can't run · backend
The label finding, after a human review. I re-adjudicated all 30 gold cases by hand against the taxonomy’s own written rules. The clean, unconfounded result: the silver labels are wrong about a third of the time (67% team, 77% urgency agreement with the reviewed labels). On every team disagreement the remedy-owner rule sided against silver. Example: “Delete from credit report” was silver-labeled LENDING off a wrong CFPB tag; the correct answer, and the agent’s answer, was CREDIT_REPORTING. So the 68.8% baseline was largely measuring label noise.

What I won’t oversell: against the reviewed labels the agent scores near-perfect on team, but I sided with the same model family on those calls, so that number is confounded and I treat it as suggestive. The honest counter-evidence: on the 2 cases where the review overturned the LLM gold label (it over-escalated urgency), the agent matched the human ruling, not the gold model. Where the model-labeler was wrong, the agent was right. Small n, but it cuts against pure self-consistency.

Null control fires on nothing (0 false positives). Full write-up in docs/03-results.md.

06 · So what — the business case

Why a bank would pay for this

A technical finding isn’t a business case. Here’s the translation, in the three buckets that actually get budget: risk mitigation, cost savings, and the enabling value.

Worked example, stated assumptions — a mid-size bank taking ~500 complaints/week (~26,000/year). These are my numbers, not a client’s. The point is the method.

~2,600
complaints/year carrying legal or regulatory escalation language (10.1% of the corpus)
~1,480
of those would route as routine if the pipeline truncates input
~2,170 hrs
annual manual triage at 5 min/complaint — roughly one full-time person

1 · Risk mitigation — the one that funds the project

Every legal-escalation complaint is a 48-hour clock in a regulated process. The truncation failure doesn’t make the system look broken — product classification stays at 71% and confidence never moves — it just quietly stops flagging urgency. At this volume that’s roughly 1,480 missed escalations a year, each one arriving at the regulator instead of the ops queue. The harness is what surfaces that before it becomes an enforcement conversation.

2 · Cost savings — the easy math

A six-person team reading every complaint by hand. At ~5 minutes each, 26,000 complaints is ~2,170 hours a year. The agent routes confidently where it can and abstains where it can’t — and the abstain rate is the dial. At 70% auto-routing, that’s roughly 1,500 hours returned to the team, with the remainder landing in a human queue by design rather than by accident.

3 · The enabling value — the real answer

This is the part most people miss. In a regulated environment, an agent nobody can audit does not get turned on. Compliance won’t sign off on a system that can’t explain itself, so the pilot stalls — which is why ~88% of enterprise AI pilots never reach production.

The harness isn’t an add-on to the agent. It’s the thing that lets the agent exist at all. Which means it doesn’t capture a slice of the value — it captures the difference between a deployment and a demo.

What a buyer actually gets

ArtifactWhat it’s for
Golden setan agreed definition of “correct,” stratified so rare categories aren’t invisible
Eval gatewired into CI — a metric below its baseline CI blocks the deploy
Audit rowinput hash, model + prompt version, output, confidence, one-line rationale — owned by compliance
Abstain pathanything unroutable lands in a human queue; the pressure-release valve that makes it safe to turn on
Drift alarmyou hear it from the system, not from an auditor

The honest limit: I built the detection layer first because you can’t price a risk you can’t see. Turning these into real numbers needs the client’s actual volume, their loaded cost per triage hour, and their own history of what a missed escalation costs. That’s a conversation, not an assumption — but it’s the conversation that gets the thing funded.

07 · If this were a real Monday

The 60-second deployment sketch

This is offline eval on a public dataset, on purpose. But an interviewer will ask what the real deployment looks like, so here it is, plainly.

  1. PII never reaches the model raw. Real complaints carry more PII than the CFPB’s scrubbed corpus, not less. A redaction pass runs before the LLM, and the scrub is itself eval’d, because a redactor that removes the legal threat recreates the truncation failure on purpose.
  2. Every decision writes an audit row. Input hash, model version, prompt version, output, confidence, and the one-line rationale. When Dana explains a misroute to her boss, she reads a row, not a model score. The audit log is owned by compliance, not engineering.
  3. The eval gate sits in CI, not in a notebook. A model or prompt change that drops any per-slice metric below its baseline CI blocks the deploy. This is the harness on this page, wired to the release, not run by hand.
  4. Rollback is a config flag, not a redeploy. If drift fires in production, triage reverts to human-only routing for the affected slice while the model is pinned back. Degrade to safe, never to silent.
  5. Everything the system won’t confidently route lands in a human queue. The UNROUTABLE path is not a failure, it is the pressure-release valve that makes the whole thing safe to turn on.

None of this is built here. Naming it precisely is the point: an FDE who can’t sketch the integration path in a minute has built an offline eval and nothing else.

08 · Miscalibrated confidence is the failure this project exists to catch

So it would be incoherent to overclaim

Stated up front, not as a disclaimer:

A mediocre agent with rigorous failure analysis proves more than an excellent agent with none. — the thesis of the whole project