Agent Failures Don't Throw

An agent loops, a user gives up, a jailbreak slips past, and the trace still says 200 OK. The failures that matter to agents are semantic, and your logs can't see them. Reflexes are small classifiers that read every turn and label the ones that broke: frustration, jailbreaks, loops, policy violations. Eight ship out of the box, one API call, under 90ms. Train your own in an hour.

Tejas Bhakta
Tejas Bhakta
June 23, 20266 min read
Agent Failures Don't Throw

A web server fails by throwing. You get a 500, a stack trace, a line number. An agent fails by succeeding at the wrong thing. It loops for twenty turns, refuses a reasonable request, gets talked out of its system prompt, closes the ticket by deleting the test. Every call returns 200 OK.

That's the gap. Your traces, your logs, your latency graphs all watch the plumbing. None of them watch the conversation.

Same eight turns, two viewsthe plumbing is green while the conversation breaks
What your traces see
200
200
200
200
200
200
200
200
What actually happened
ok
ok
frustrated
ok
loop
loop
jailbreak
ok

Every call returned 200. The failures that matter are in the text, and nothing in the trace is looking at the text.

The stakes keep climbing while the visibility stays flat. Agents pull from more tools every month, call their own subagents, run for hours with no human in the loop, and now sit in healthcare, finance, and support queues where a quiet failure costs real money. The harder the agent works, the less a 200 tells you.

The failures live in the text

Error rate and latency throw, which is why you already watch them. The signals that actually tell you whether the agent is any good never make it to your logs at all, because they're semantic. They're buried in what the user and the agent said to each other. A user typing "this is the third time I've asked" and then giving up. A prompt that coaxes the agent out of its own rules. An agent hitting the same tool eight times, getting the same error every time, and trying a ninth. A perfectly reasonable request the agent refused for no reason anyone can find. The agent spilling its chain of thought into the reply.

None of that surfaces on its own. Something has to read the turn and put a label on it, and that something is what we call a reflex: a small classifier that looks at one turn and gives you back one answer. Not a 1-to-10 score that slides around every time you tweak the wording. An actual label, the kind of thing you can count, watch across a release, and get paged over when it jumps.

Eight reflexes, one API call

Nothing to train up front. Send a turn to the API and the label is back in under 90ms. Eight of them come ready to go:

ReflexFires when
User Frustrationthe user is angry, repeating themselves, or giving up
Jailbreakthe input is trying to break the agent out of its instructions
Stuck in a loopthe agent is repeating an action with no progress
Guardraila turn crosses a policy line you defined
Incomplete thoughtthe agent stopped mid-task or left the job unfinished
Leaked thinkinginternal reasoning leaked into a user-facing reply
Difficultythe turn is hard or the agent needs more information
Ambiguitythe request is underspecified

One call, one classifier, one label:

bash
json

Run every turn through it. frustrated at 0.97 becomes a row in your dashboard instead of a number sitting in a transcript nobody's ever going to open.

A rate you can alert on

One label on its own is just a data point. Point that same reflex at all of your traffic and it turns into a rate, and a rate is what you actually run a product on. Frustration sits flat at some baseline for a while. Then a deploy goes out and it jumps:

User-frustration rate, last 48 hoursone reflex on 100% of traffic
2 days ago↑ system prompt v2.4 deployednow

Frustration held near 12% until v2.4 shipped, then doubled within hours. No one read a thousand transcripts to find it. The reflex ran on every turn and the rate moved.

The new system prompt went out at v2.4 and the frustration rate doubled inside a few hours. Nobody had to sit down with a thousand transcripts to catch it. The reflex read every turn and the chart just showed you.

Now shipping becomes an experiment. Push a prompt or model change to 5% of traffic, keep the rest as control, and watch the rates move. If refusals or frustration climb once the change lands, you shipped a regression, and you found out that same day instead of a week later through the churn numbers. That's A/B testing on what the agent actually did, not on what your eval set guessed it would do.

When we don't ship the signal, train it

Eight reflexes cover the common failures. Your product has failures that are yours alone: a medical agent that should never give dosing advice, a coding agent that quietly stubs out a failing test, a support agent drifting off-policy. Describe the behavior, bring a few labeled examples or let us generate a synthetic set, and you have a custom reflex in under an hour. It serves on the same API, at the same latency, beside the built-ins. The public surface is OpenAI-fine-tuning-compatible, so if you've trained a model through the OpenAI SDK, you already know the shape.

The labels feed back into the agent

A reflex is a detector, but the output is training signal. Every label is a row that can become an eval case, a fine-tune example, or a reward term in RL. The turn that flagged as frustrated today is the behavior your main agent learns to avoid tomorrow.

The data enginecheap enough to run on everything, every day
01
Run on 100%
Label every turn in production, not a 1% sample.
02
Surface
Flag the rare turn: a jailbreak, a loop, a frustrated user.
03
Sample
Pull those turns into a dataset worth training on.
04
Improve
Feed evals, fine-tunes, RL rewards. Repeat.

A big model on every turn is too expensive to run on all of production. The detector has to be tiny, or the loop never closes.

I learned this at Tesla, building the stack that went through driving data at scale to surface the frames engineers wanted to train on: the obscured camera, the rare cut-in, the long-tail case the model kept missing. The fleet produced petabytes. Nothing accurate enough to be useful was cheap enough to run on all of it, so the whole game was finding a filter you could afford to run on everything, because the rare event you need is never in the sample.

Treat your production traffic as a funnel. At the top, reflexes run on 100% of turns and flag a few percent. That flagged slice is small enough to hand to the expensive review you could never point at all of production: your team reads the turns that matter, and your own agents dig into the rest. Reflexes don't replace that judgment. They decide what's worth it, so nobody drowns in transcripts that returned 200 and meant nothing.

The production funnelcheap on everything at the top, expensive on a slice at the bottom
All production
Reflexes — tiny, automated, every turn
100%
Reflex-flagged
Heavier agents read the flagged turns in depth
~5%
Worth a human
Your team works the slice that matters
~0.1%
volume falls ~1000×compute per item rises ↓

The cost of the top filter sets how much of production you can afford to look at. Cheap enough to run on 100%, and the expensive review below it only ever touches the slice worth the spend.

That only works because the detector is cheap enough to leave running on everything. One backbone pass labels every turn with many heads, so the tenth reflex costs almost nothing. Read how we built it.

The label was never the hard part. Running it on all of production was, and that's the part we solved.

Try a reflex on your own traffic, or read the docs.