Why a green dashboard can still be wrong
Traditional monitoring answers one question: is it up? HTTP 200 on every call, p99 latency normal, CPU and memory fine, error rate zero. None of those metrics has an opinion on the question that actually matters: is the agent quietly doing something insane? The signals that were moving, tokens per turn, dollars per hour, tool calls per request, weren't on any dashboard.
One real agent ran at a rate that annualised into six figures for days, every dashboard green the whole time, until someone happened to read the bill. It was luck that it was caught on day nine and not day ninety, and luck is not a control.
Why agents run away
Agents terminate fine, almost always. A runaway needs a specific condition to defeat the exit, and it isn't "someone forgot an exit": the exit was there, it was a judgement call, and a rare input defeated it. Five conditions keep showing up.
Tool results, often thousands of characters each, pile up until the context is so large the model loses the thread and keeps calling tools without noticing it already has the answer. This is the one teams hit most.
The input pushes the model toward a stop condition it can't reach, so it keeps acting. A "how did you actually verify that?" follow-up can send an agent into hundreds of retrieval and search calls, trying to be sure of something it can never be fully sure of.
The model returns malformed or empty output, the SDK re-invokes it with the same context, and it emits the identical reasoning again. A loop inside the model, not the tools.
A tool keeps failing, or two tools quietly undo each other, and the agent keeps calling with no check that it's making progress.
This one isn't the agent at all. Something outside it re-invokes the work (a queue, a scheduler, a retry) and the work crashes before it's marked done. It never reaches a terminal state, so the re-driver picks it up again next tick and re-bills the model, forever.
Bound the loop
Any competent engineer can add a max-iterations cap, which is exactly why people ship one and stop. A single cap breeds false confidence: it doesn't touch a poison-pill re-drive, and it does nothing about an agent that stays inside every limit while quietly getting more expensive per answer. Cap more than one thing, and trip on whichever comes first.
Cap at roughly 12 per turn, set from your own traffic, above the heaviest legitimate turn. Catches a tool-calling loop that never checks it already has the answer.
Cap at roughly 6 per turn. Catches the no-tool regeneration loop, the model re-emitting the same malformed output. Only works if your instrumentation surfaces SDK regenerations as separate blocks within the turn, so check that first.
Cap at roughly 120 seconds per turn. Whichever of the three trips first, stop and hand back what you have. Better still, pair it with a hard per-session token budget: a deterministic dollar ceiling per request, so the worst case is a known number.
Watch the spend two ways
Stopping the loop is the easy half. Seeing the next one coming is the half that matters, and it takes two instruments, because there are two different failures to catch, and each is blind to the other.
Tool calls and tokens per session against a p99, emitted every cycle from inside the event loop (not per turn: a runaway turn may never call end_turn). One stuck session is invisible in the fleet total but a klaxon against one normal session. This is the signal that catches the acute runaway in minutes instead of days.
Bedrock's InputTokenCount / OutputTokenCount per model, alarmed when the rate drifts above a steady-state ceiling. Catches what the per-session alarm is blind to: a slow bleed (everything's within its cap, but the whole fleet is quietly getting more expensive per answer), and a poison-pill re-drive smeared across many cheap sessions that each look normal alone.
Full write-up
The real numbers, the EMF instrumentation code, and why the alarm threshold is fragile on purpose: Every dashboard was green while the agent burned six figures a year.