How do you stop an agent that never decides it's finished?
An agent loop runs under an explicit budget — a maximum step count, a token ceiling, and a wall-clock limit — plus a termination contract that forces every run to end in one of a small number of named states: success, failure, or escalation. When the budget is exhausted before the model reaches one of those states on its own, the harness ends the run itself rather than letting it continue indefinitely.
Also known as step-budget loop, agent circuit breaker, termination contract
What problem does this solve?
Without an external budget, nothing stops an agent loop that keeps calling tools and re-reading their results. The model has no reliable internal sense of "I've tried this six times, stop" — a failed tool call sits in context looking exactly like fresh information, so it reasons forward from it as though a new attempt might succeed. Left alone, the loop ends only when something outside the model intervenes: a platform timeout, a budget, or a human watching the trace.
The cost compounds because each additional failed attempt adds more context the model has to re-read, without changing the constraint that caused the failure. A harness with no distinction between "still making progress" and "looping" treats both identically, so an unbounded loop and a legitimately long task look the same until one of them quietly burns ten times the expected cost.
How does it work?
The harness — not the model — enforces a hard ceiling on steps, tokens, and wall-clock time per run, decided in advance from the expected complexity of the task. When any ceiling is hit, the harness force-terminates the run into one of a fixed set of terminal states, the same way a circuit breaker trips rather than waiting for the fuse to melt on its own schedule.
A termination contract defines what those terminal states actually mean before the pattern is useful: succeeded, failed-with-reason, budget-exceeded, or escalated-to-human. Every run logs exactly one of these, so "how did this run end" is answerable from a dashboard, not by reading a transcript. Progress detection — has the agent retrieved new information, changed state, or only repeated a prior call — lets the harness terminate early on a stuck run well before the hard ceiling, rather than waiting out the full budget on something that was never going to resolve.
An escalation path matters as much as the ceiling itself — a run that hits its budget without succeeding should hand off to a human with the transcript and the reason, not simply fail silently or retry from scratch.
What are the moving parts?
- Set per-run ceilings before launch
Size the step, token, and wall-clock ceilings to the task's expected complexity, before the first production run, not after an incident.
- Detect no-progress steps
Track whether the agent retrieved new information, used a new tool, or changed state — repeated calls with none of these count as no progress.
- Force a terminal state on ceiling or no-progress
The harness ends the run itself once a ceiling is hit or a no-progress threshold is crossed, rather than waiting for the model to decide to stop.
- Define the fixed terminal states up front
Succeeded, failed-with-reason, budget-exceeded, escalated — every run ends as exactly one of these, decided before any run happens.
- Escalate to a human on non-success
A run that hits its budget without succeeding hands off to a human with the transcript and the reason, instead of failing silently or retrying blind.
- Log the terminal reason on every run, always
Every run — not just the failed ones — writes its terminal state as structured data, so the distribution is queryable, not anecdotal.
When does it fit, and when doesn't it?
Use it when
- Agent runs vary widely in step count for the same task type, and the tail is expensive
- Tool calls can fail in ways that look like fresh information to the model (a rate limit, a transient error)
- You need a hard, auditable answer to 'how much can one run cost' before shipping to production
- Multiple engineers or teams build agents against the same harness and should not each reinvent termination logic
Don't use it when
- The task is a single bounded tool call with no loop at all — there's no runaway condition to bound
- Step counts are already tightly, predictably bounded by the task's own structure (a fixed 3-step pipeline)
- You can't yet define what 'progress' means for the task — a budget without a progress signal just makes failures happen at a fixed cost instead of preventing them
What does it actually cost?
A hard ceiling on the worst-case cost and duration of any single run
A legitimately long, complex task can be cut off before it would have succeeded — the ceiling has to be sized generously enough to avoid false terminations
Every run ends in one of a small, named set of states, which becomes queryable data
Building and maintaining a real progress-detection check is nontrivial — a naive one (step count alone) misses loops that vary arguments slightly each time
Stuck runs terminate long before the outer ceiling, saving spend
Tuning the no-progress threshold takes iteration; too aggressive and it kills runs that were about to succeed
A defined escalation path turns budget exhaustion into a handled case instead of a silent failure
Escalation needs a real destination — a human queue, an alert — that has to be resourced, or the 'escalated' state just becomes a different flavor of dropped task
How do you know it's working?
- Steps-per-run and token-spend-per-run as distributions (p50 vs. p99), not averages
- Share of runs ending in each terminal state (succeeded / failed / budget-exceeded / escalated)
- Rate of near-identical repeated tool calls per run
- Cost per completed task vs. cost per attempted task
What failure modes does this prevent?
A Ship Audit checks which of these patterns your system actually needs, prioritized against what's most likely to break first.