Blog

One AI agent used 1.06M tokens and never wrote the patch

· the plori team

TL;DR. Put the stop decision outside the model. A hard token or time budget caps the worst case, but it cannot tell busy work from progress. We stop earlier only when the runtime sees an equivalent action fail in the same way repeatedly. An earlier checkpoint gives the agent time to save partial work before its tools are removed.

One agent in a 50-instance SWE-bench Verified run stayed busy until it had used 1.06 million tokens. It inspected the repository, reasoned, and called tools. It never wrote the patch.

At first, the empty result looked like a storage failure. We checked the workspace path and wrote a positive-control file through the same mount. The control survived. No patch had been lost. The agent had simply kept working without making its work durable.

That distinction matters. A crash is easy to count. This run emitted normal events right up to its budget, so every health check said the agent was alive. We call this non-convergence: the agent is still acting after durable progress has stopped.

Is an AI agent stuck in a loop a real production problem?

Yes. The July 2026 paper When Agents Do Not Stop scanned 6,549 public LLM-agent repositories and confirmed 68 infinite-agent-loop failures across 47 projects.

Public issue reports describe the same symptom from the user's side. In one Codex report, the agent repeatedly ran pwd, compacted its context, and started again. Another report described repeated status polling that kept re-entering the model and consuming credits. Those reports do not establish a prevalence rate. They do show that "still running, no longer getting anywhere" is a failure users can recognize and pay for.

What counts as progress when the agent is still active?

For a coding agent, progress must eventually become durable. The patch exists, a targeted test passes, or the investigation rules out an approach. Model output and tool calls show activity. Neither proves progress.

There is no universal progress score. A test suite can run for ten minutes without streaming output and still be the best next action. A debugging session may need several failed experiments before the first useful edit. A read-only poll can return unchanged state many times because the job being watched is genuinely slow.

We therefore use a deliberately narrower definition for automatic stopping. The runtime asks whether an equivalent action has produced an equivalent failure again, with no successful observation between them. That signal catches a tight failure loop. It does not pretend to measure every kind of stalled reasoning.

Why does a maximum iteration limit fail to solve this?

OpenAI's Agents SDK exposes a maximum-turn limit. LangGraph has a recursion limit. These controls answer one necessary question: how much execution can one request consume?

They do not answer whether the latest step was useful.

A limit low enough to catch a short retry loop can also stop ordinary work on a large repository. A high limit gives legitimate tasks room to finish, then gives a stalled task the same room to burn. Model families also pack different amounts of work into one round, and a round may contain several tool calls. Step count is therefore a damage boundary, not a progress detector.

Keep the hard boundary. It protects cost and availability when every smarter signal misses. Just do not wait for it to be the first sign that the run stopped moving.

What did our benchmark runs show?

The 1.06-million-token case gave us a clean non-convergence example: no patch, no storage loss, and no crash before the budget ended. A later 50-task SWE-bench Verified report showed the broader economic symptom:

Outcome Tasks Total cost Average cost per task
Resolved 26 $13.8603 $0.5331
Unresolved 24 $14.6807 $0.6117

The unresolved tasks consumed 51.4% of that run's $28.5404 total cost. They also cost about 15% more per task on average than the resolved group.

That is not a loop rate. The unresolved row includes wrong patches, timeouts, and platform or harness failures. It is an investigation queue, not proof that half the agents stopped progressing. We use individual traces, such as the 1.06-million-token empty result, to diagnose non-convergence. The aggregate tells us how much money sits on the unresolved side of the ledger.

Which loop signal is safe enough to stop automatically?

We chose the smallest claim the runtime can verify without interpreting the agent's reasoning: the same tool action returned the same failure repeatedly.

The detector sits outside the model. A success clears the streak; a changed action or failure starts a new one. We keep healthy read-only polling out of this mutating error guard. After a small number of exact repeats, the runtime tells the agent to re-read the error and change approach. If nothing changes, further calls are no longer executed.

That polling exemption is not permission to spend model calls on waiting. When the watched state has not changed, the harness should wait without another inference and wake the model when new output arrives or the job needs attention. Error-loop detection and efficient waiting solve different problems.

Exact matching is conservative on purpose. A duration, line number, or changed argument can make two practically identical failures look different. We accept those false negatives because a false positive is worse: it can terminate a run that is slowly learning something useful.

Semantic similarity sounds more capable, but it moves a safety decision into a fuzzy classifier. "The same test failed" may mean the patch had no effect. It may also mean five other assertions were fixed and one remains. We do not have enough evidence to stop on that distinction automatically.

Why save partial work before stopping the run?

An exact-repeat detector catches the obvious loop. It misses the slow burn: the agent tries different commands, receives different errors, and still never writes the artifact the user asked for.

Our second control is an earlier budget checkpoint. It arrives while tools still work and asks the agent to write the best partial artifact it has now. Then it asks one blunt question: is the current approach converging? A run with a credible path can continue. A run without one has explicit permission to stop and explain the blocker.

The checkpoint is advisory. The model is allowed to continue, so the hard budget remains outside it. Its main job is timing. Once a token boundary has removed tool access, a final summary cannot write the patch that was still sitting in the model's plan.

Saved work also changes the cost of a stop. Each plori agent keeps its files on a persistent account disk, so a later turn can resume from the partial patch or report instead of starting from the transcript alone.

What should the user see when a stuck agent stops?

A circuit breaker should end the loop without turning it into a bare platform error. We give the model one final round with no tools and drop any call it still tries to make. That round can only produce a handoff: where the saved work is, what blocked the run, and the next concrete step.

If the model still emits no useful text, the runtime supplies a deterministic fallback. The turn ends cleanly, records a non-convergence reason, and leaves existing workspace files intact.

"Stopped because the same verification kept failing; partial patch saved" tells the user what happened. "Agent failed" throws away the only useful part of the run.

What does this approach still miss?

Superficially different actions evade the exact detector. So do successful commands that never advance the task, such as repeatedly inspecting the same directory. Those cases reach the token or time boundary unless the budget checkpoint changes the model's course.

It cannot infer that a long-running tool has stopped making progress. That decision needs tool-specific evidence, such as a process heartbeat or an execution state, rather than a generic language-model judgment.

The policy intentionally prefers an occasional expensive false negative over a cheap false positive that destroys legitimate work. If your workload has a reliable domain signal, use it. A workflow runner can watch terminal state. A compiler agent can compare the failing-test set. The generic runtime should stay narrow.

Limitations

The 1.06-million-token example came from one SWE-bench Verified task under one agent and budget configuration. The later 50-task cost split measures resolved versus unresolved work, not non-convergence by itself.

We also do not yet have a clean before-and-after production estimate for credits saved by the stop policy. The detector is designed to bound a failure we observed, and our replay tests show that it ends that trace early while preserving a handoff. A cost-reduction claim should wait for enough comparable production runs after rollout.