EngineerPrep / Engineering Research · Failure Pattern Study

The Agent That Would Not Stop: Why Your Loop Has No Boundary

47 successful iterations, zero state change, and nobody owning the word 'done'

2026-09-15 · 7 min read

A task went in at 09:14. At 11:52 the agent was on iteration 47 of the same task, and every one of those 47 iterations had succeeded.

No exception. No error code. No 500 in any log. Every tool call returned 200. The loop simply had no idea what "done" meant, and nothing in the system was going to tell it.

Nothing failed. That is the problem.

When an agent loop runs away, the instinct is to look for the broken call — the 500, the timeout, the exception. Runaway loops usually don't have one. The more painful shape is the loop where every step is locally defensible:

iter 41  search_catalog    200   3 results
iter 42  read_spec         200   spec v3
iter 43  summarize         200   0.9 confidence
iter 44  validate          200   "incomplete coverage"
iter 45  search_catalog    200   3 results
iter 46  read_spec         200   spec v3
iter 47  summarize         200   0.9 confidence

The validator keeps saying incomplete coverage. The model keeps responding the only way it knows how: gather more, try again. The catalog keeps returning the same three results, because that is all there is. Each iteration is reasonable. The sum is a task that can never terminate, executed at full price per step.

That is the failure under investigation in AT-02 of the production failure lab roadmap: a loop has no reliable completion or iteration boundary. Not a tool bug. Not a model bug. A missing contract at the loop level — which is exactly the layer nobody owns.

Three boundaries, all missing at once

Every bounded loop in ordinary software answers three questions before it starts. Agent loops shipped without any of them:

BoundaryThe question it answersWhat exists in a typical agent loop
Completion contractWhat observable state means done?"the model says it is done"
Iteration capWhat is the maximum price of being wrong?nothing, or a number picked to not break demos
Progress checkIs the state actually changing?none — confidence is not progress

The third one is the subtlest and the one that does the damage. A model's self-reported confidence can sit at 0.9 forever while the underlying state does not move an inch. Confidence is the model's opinion about its own answer. Progress is a delta in the world — a file changed, a row written, a state transition observed. In 47 iterations above, one of those was present and it was the wrong one.

What it costs, mechanically

The runaway is bad enough at flat cost per iteration. It is worse than flat, because agent loops carry their history:

iter  5   prompt  1.9k tok   $0.011
iter 15   prompt  6.4k tok   $0.038
iter 25   prompt 12.8k tok   $0.077
iter 35   prompt 19.1k tok   $0.115
iter 45   prompt 25.4k tok   $0.102

Every iteration appends the last one's tool results to the context, so iteration n costs more than iteration n−1 even with identical work. A loop with no boundary is not a linear burn — it is a snowball with a completion criterion that may never arrive. The same mechanism, one level over, is why a bounded loop can still 3x your bill: retries nested inside iterations multiply, which is its own lab (the fifteen-call request). The unbounded case is just that failure with the ceiling removed.

The cap is not the fix

The reflexive fix is max_iterations = 10. Ship it and you have traded a runaway for a coin flip:

A cap answers how long, not when to stop. The completion contract is the part teams skip, because writing it forces the uncomfortable question: what observable state change does this agent actually produce? For a summarizer it is a document written. For an order agent it is an order in a stated state. For a research agent it is a set of citations that resolve. If you cannot write that predicate, the loop has nothing to converge on, and no iteration count will save it — it will just fail more cheaply.

What a boundary looks like

The loop that replaced ours, reduced to its contract:

DONE    := order.status == CONFIRMED            (checked against the store,
                                                  never against the model's claim)
BUDGET  := 8 iterations OR $0.50 OR 90 seconds  (whichever fires first)
PROGRESS:= at least one state delta per 2 iters (else: stop, escalate)
UNRESOLVED := hand off with the trace attached  (never silently absorb)

Three properties matter more than the specific numbers. Done is checked outside the model — against state it cannot edit, because a model grading its own homework will eventually pass itself. The budget is first-class — spending past it is an incident, not a rounding error, and it triggers the same reconcile-don't-retry logic as any other unresolved outcome. Giving up is a modeled outcome — the agent can end in "escalated", not only in "done" or "crashed". Most runaway loops in the wild are agents that were never given a legitimate way to quit.

The pattern, generalized

Once stated as a boundary problem, it stops being an agent bug and becomes a systems invariant:

The loop isThe missing boundaryWhere you have seen it before
An agent iterating on a taskcompletion contract + capbatch jobs without a deadline
A retry policy without a budgetiteration cap on retriesthe retry storm
A workflow engine re-driving a stepprogress check on state deltaduplicate side effects
Multi-agent delegationwho is allowed to hand work to whomthe recursive delegation loop

The family resemblance is not accidental. Every one of these is a loop whose termination was delegated to a component with no authority to decide it — a scheduler that trusts the worker, a client that trusts the server, an agent that trusts itself.

Where this goes next

The boundary contract above is what we teach in the AI agent course, and the pattern pairs with two earlier reports: why parent executions look healthy while children burn, and the agent that charged twice — the cousin failure, where the loop ended but the side effect did not.

The runnable AT-02 lab — The Agent That Would Not Stop — is live: the reproduced loop above, the evidence room, and the bounded rewrite with the test that fails before and passes after. The rest of the hundred are on the public roadmap with their real status, because "coming next" is a promise rather than a product.