Headless AI Agent Loops: Iteration Cap and Completion Sentinel

The honest loop and the lying loop bill the same $1.75. Only the exit state tells them apart.

Headless AI Agent Loops: Iteration Cap and Completion Sentinel

You already built the overnight loop. The work is cut into single-task passes so each one starts clean, which is its own argument. The permissions are tightened, because going headless removed the human who used to approve each action, and something has to replace that role. The cron entry fires at 1 a.m.

One job from the old workflow still has no owner. Somebody used to watch the clock, weigh the work against the night, and say that’s enough. That somebody was you, and you are asleep.

When the loop runs, someone has to decide the work is finished, and there are exactly two candidates. The agent is a poor judge because it is agreeable: pointed at more work it will find more work forever, pointed at caution it will quit one bug short. The harness is a poor judge because it is blind: it can count passes, and it cannot tell a finished backlog from a stuck one. The whole design question is how to wire brakes to those two broken judges so each one covers the other, and what to do on the night neither can call.

Here is the whole piece in one bill. You add two brakes and a third exit state. The third state costs nothing on a clean night and nothing on a typical one. On the one night it matters, a task the agent cannot finish, it saves you nothing at all over a design that lies to you: both stop 5 passes into a 12-pass budget, both cost $1.75 and 7.5 minutes. One calls the task done. One calls it blocked. Cost alone cannot tell you which loop just lied to you; only the exit state can, and that is the argument.

Before any arithmetic, thirty seconds on your own runner settles whether the rest is for you. Open the script or CI job that drives your unattended run and find the line that stops it.

There are three possible answers. A counter, or a framework turn limit: you have the first brake already. An exact token the agent emits and the harness greps for: you have the second. “The agent decides when it’s done”: that is the third answer, and it means you have neither.

If your loop already exits on a cap, on a verified sentinel, and on a blocked state, with three different exit codes, close the tab. If the only thing that ends your run is the agent’s own prose, keep reading. If the only thing that stops your loop is the agent deciding to stop, the loop does not have a stopping condition.

What the night costs when nobody says stop

Section titled “What the night costs when nobody says stop”

Fix one concrete case and keep it for the rest of the piece. A headless loop working a TODO.md with five tasks on it, T1 through T5. One iteration means one full pass at one task: read the code, make the edit, run the tests. Call each pass $0.35 and 90 seconds. Both are toy numbers, invented so you can check every figure below with a pencil; declared here and again at the end.

Now price the two bad mornings. In the first, the loop never stops. Five tasks finish in five passes. Then the model, asked to keep going until the work is done, finds more work. Pass six improves a docstring. Pass seven re-runs a green suite to be sure. Pass eight refactors a helper nobody complained about. Every one of those passes is a helpful response to “keep going.” But nothing inside the loop bounds the count, so at $0.35 a pass the bill is whatever the night is long enough for.

In the second, the loop quits at pass three. The prompt said “if you’re unsure, stop and ask,” so the agent, agreeable to a fault, stopped to ask. T4 and T5 are open. Your unattended run was never unattended; it just had a very literal stand-in who left early.

An agent in a loop has no native sense of done. It has a sense of what a helpful response looks like right now, and that sense bends in whichever direction the prompt leans, past the truth in both directions. The tragedy is that the agent is still the only party present who knows whether the backlog is empty. The judgment is broken in both directions, and it lives in the only judge that can see the work. That is the tension, and no prompt fixes it.

Brake one: a ceiling the agent cannot argue past

Section titled “Brake one: a ceiling the agent cannot argue past”

The first brake lives in the harness, not the prompt. It is an iteration ceiling, a hard cap on how many passes the loop may buy. The agent never sees it, cannot reason about it, cannot talk you out of it.

#!/usr/bin/env bash
MAX_ITERS=12
i=0
while (( i < MAX_ITERS )); do
output=$(agent --headless --prompt "$(cat run-prompt.md)")
echo "$output"
(( i++ ))
done
echo "Hit iteration ceiling ($MAX_ITERS) without stopping." >&2
exit 1

MAX_ITERS is 12, and that one number is the loop’s declared worst night: 12 passes is $4.20 and eighteen minutes. Remember 12. It gets paid off exactly on the blocked night below, where the design that does everything right needs 5 of them.

The ceiling is dumb on purpose. It does not know whether the work is done. It knows only that past twelve passes, something is wrong: a task is unsatisfiable, the model is circling, the backlog regenerates itself. A run that hits the ceiling should exit non-zero and page you, never pretend it succeeded.

One refinement worth making early: count the thing that actually costs you money. Passes are a proxy. An agent re-sends its conversation on every step, so pass eleven costs more than pass two, and twelve cheap laps and twelve context-stuffed laps are not the same bill. If your harness can read token usage, a token or dollar budget is a tighter ceiling than a raw lap count. Either way the principle holds: the ceiling is a number the agent cannot see and cannot move.

And be honest about what the ceiling does not protect. A cap bounds how many times the agent acts. It says nothing about how bad one action can be, and an agent that drops a production table on pass two never reaches pass twelve. That half of the problem belongs to full-autonomy-is-a-small-blast-radius, which computes how far a single bad iteration can reach through sandbox, allowlist and scoped credential. This piece only counts laps. The ceiling bounds the worst night the loop is allowed to have. It was never asked whether the work got done.

The ceiling bounds the bad case and ignores the good one: the run that finishes early because the backlog really is empty. For that, the agent needs a voice, and the voice has to be shaped so the harness can hear it without parsing prose.

That shape is a completion sentinel: one exact token, defined in the prompt, whose emission is the only legitimate way to claim completion.

## Completion protocol
Work through the unchecked items in TODO.md, one per iteration.
When - and ONLY when - every item is checked and no new work
is implied by the codebase, emit this exact token on its own line:
<<NO_MORE_TASKS>>
Do not emit it as a prediction, a plan, or a "probably done."
Emit it only after you have verified the backlog is empty.
Never paraphrase it. The string must match exactly.

The detection is deterministic: grep -q "<<NO_MORE_TASKS>>" either matches or it doesn’t. The alternative, parsing free prose for “I’m done” or “task complete,” is the classic anti-pattern, because natural language is ambiguous. “I’ve finished analyzing the first file” is not a stop signal, but a substring match on “finished” thinks it is. One exact token that cannot be paraphrased deletes that whole class of accident. The harness part of brake two is three lines:

Terminal window
if grep -q "<<NO_MORE_TASKS>>" <<< "$output"; then
echo "Agent signaled completion on iteration $i. Exiting clean."
exit 0
fi

What the sentinel does not buy is trust in the decision to emit it. A model can raise the token with two tasks still open. This failure has a name: premature completion, a run declaring victory on a half-done job and exiting quietly. The sentinel makes the claim legible, readable by the harness without interpretation. Readable and true are different properties, and only the first is guaranteed. That gap is what the next section closes.

The protocol belongs in your rules, the persistent context the agent reads on every run. A convention that governs many runs is exactly what a rules file is for: write it once, and every loop inherits it. The gate is deterministic; the judgment underneath is not.

Here is the uncomfortable part. <<NO_MORE_TASKS>> means “I believe I’m done,” and belief is the input you already decided not to trust. The grep launders a fuzzy judgment into a crisp token, and the judgment stays fuzzy underneath.

So do not accept the sentinel on its own. Accept it only when an objective check agrees:

Terminal window
if grep -q "<<NO_MORE_TASKS>>" <<< "$output"; then
if npm test --silent && [ -z "$(grep -L '\[x\]' TODO.md)" ]; then
echo "Completion claimed and verified. Exiting clean."
exit 0
fi
echo "Agent claimed done, but tests fail or TODO has open items. Continuing." >&2
fi

The sentinel proposes; the test suite disposes. “Are the tests green?” is a question the agent cannot be agreeable about. A red suite is a red suite. You have stopped asking the agent whether it is finished and started asking it to produce a state of the world that is finished, then letting the world answer. The strongest form of the second brake is a fact the harness checks, and the feeling never enters the wire.

Brake two, in full, is “let the agent signal a candidate completion, then verify it.” The claim lives where the agent’s knowledge lives, and the check lives where objective truth lives.

Two exits is one too few. A loop that can only keep going or declare done has no vocabulary for the most common real outcome of an unattended run: stuck. The agent hits a task it cannot satisfy this pass. A missing credential. A test that needs a service nobody started. A requirement only you can settle.

Its two legal moves are both wrong. Grind against the wall until the ceiling fires, and one impossible task eats the whole budget. Or emit <<NO_MORE_TASKS>> anyway, and a blocked backlog gets called finished. Give it a third sentinel:

If a task cannot be completed without information or access you
do not have, do NOT keep retrying and do NOT mark it done.
Emit this token on its own line and stop:
<<BLOCKED: one-line reason>>
Terminal window
if grep -q "<<BLOCKED" <<< "$output"; then
reason=$(grep -o "<<BLOCKED:[^>]*" <<< "$output")
echo "Agent blocked on iteration $i: $reason" >&2
exit 2
fi

Now the run has three honest exits: done, exit 0. Blocked, exit 2. Capped, exit 1. The distinction pays for itself the next morning, because each exit routes differently. A clean finish needs nothing from you. A block needs the missing piece and a re-run from where the loop stopped. A cap needs an investigation into which of the three things that can go past twelve actually did. Collapse blocked into either of the other two and you lose the one signal that says which morning you’re having. Done, blocked and capped are three different mornings, and two exit codes can only give you two of them.

Fix the night. T1 through T4 are completable. T5 needs a credential the agent does not have, and no amount of retrying produces it. Five tasks, one impossible: hand-traceable.

Compute the ground truth the obvious way, before any design has its say. Four passes finish T1 through T4. The fifth pass is the first attempt at T5, and it fails. So the block point is pass 5, arrived at as 4 finished plus 1 failed attempt, and no harness design changes that number. What differs between designs is what happens on it.

Ceiling only has no early exit at all, so pass 5 teaches it nothing. It burns all 12 passes, $4.20, eighteen minutes, and exits 1 without saying why. There is the plant from the brake-one section, paid off: the loop’s worst allowed night is 12 passes, and this design needs every one of them.

Sentinel only stops at pass 5, and this is the modeled failure. After one failed attempt at T5, the agent decides four out of five is close enough and raises <<NO_MORE_TASKS>>. The harness greps it, verifies nothing (this design has no verifier), exits 0. The morning report says the run finished. T5 is silently unresolved.

Three brakes also stops at pass 5. The agent recognizes on that same first attempt that the credential is missing, and it now has a legal move that is neither grinding nor lying: it raises <<BLOCKED: T5 needs the deploy credential>>. The harness exits 2. The morning report says exactly one task is unfinished, and why.

DesignPasses spentCostWall clockWhat the exit says
Ceiling only12$4.2018.0 min”Something is wrong.” It cannot say what.
Sentinel only5$1.757.5 min”Done.” T5 is still open.
Three brakes5$1.757.5 min”Blocked: deploy credential.” True.

Check the payoff. The designs that can stop early finish in 5 of the 12 planted passes: 7 fewer than the ceiling, 58.3% off the worst case, exactly fifty-eight and a third with nothing rounded until the display. Every figure in the table comes from the same four inputs: 12 passes, a 5-task backlog, $0.35, 90 seconds. You can check the whole column with a pencil. Both early-stopping designs land on 5 passes, $1.75 and 7.5 minutes, and nothing in this table can tell you which one lied.

Look at the two bottom rows until the discomfort lands. Sentinel only and three brakes spent the same 5 passes, the same $1.75, the same 7.5 minutes. They tie on every number this piece has trained you to check. And they disagree about the only fact that matters, which is whether T5 got done. One row says done and is wrong. One row says blocked and is right.

Same bill.

Call that the honest tie: the night two designs cost exactly the same and only one of them tells the truth.

The tie reorganizes everything above it. If the third brake had saved money over the lying sentinel, you could pick harness designs by reading invoices. It saves nothing on the night that matters, and the next section shows it costs nothing on the nights that go fine. So the third brake is not a cost brake, it is a truth brake, and the exit state is the only wire that carries the signal.

This also settles why neither brake works alone, now with numbers instead of adjectives. The ceiling alone is correct but wasteful: it runs the full 12 every night, including the clean ones. The sentinel alone is cheap but unbounded the night the agent never raises it, and unverified the night it raises it early. Pair the two and the common night is cheap while the worst night is bounded. Add the third state and the bad night is cheap, bounded and true. Saving money is what the brakes do on the way past. Telling the truth is the job.

The blocked night is the dramatic one. These two are the ones you get most weeks.

Clean night: all five tasks complete, no retries. The work needs 5 passes, $1.75, 7.5 minutes. Typical night: T3’s test fails once for a flaky reason, the agent re-runs it, the count is 6 passes, $2.10, 9.0 minutes.

DesignCleanTypicalBlocked
Ceiling only12 ($4.20, 18.0 min)12 ($4.20, 18.0 min)12 ($4.20, 18.0 min)
Sentinel only5 ($1.75, 7.5 min)6 ($2.10, 9.0 min)5 ($1.75, 7.5 min)
Three brakes5 ($1.75, 7.5 min)6 ($2.10, 9.0 min)5 ($1.75, 7.5 min)

Two things to see. First, the third brake is free. Sentinel only and three brakes are identical in the clean and typical columns: the blocked sentinel sits unused on the good nights, and a brake that costs nothing until the night it is needed is the cheapest kind of brake there is. Second, the ceiling-only row is flat 12 everywhere, including the clean night where nothing went wrong: it pays $2.45 and ten and a half minutes for 7 passes nobody needed. Bounded was never the same as smart.

This is the picture to keep; the close replays it. The third brake costs nothing on the good nights and buys the truth on the bad one. The ceiling-only row buys neither.

The ceiling is not news, and you may already have it as a flag. Most harnesses ship a built-in turn cap: Claude Code has --max-turns in headless mode; LangChain’s classic AgentExecutor has max_iterations, 15 by default; LangGraph has recursion_limit, 25 by default; the OpenAI Agents SDK’s Runner takes max_turns (all four checked against each project’s docs, August 2026). The bash counter above illustrates a mechanism; before you hand-roll one, check whether your harness already hands you the first brake. The parts that are this piece’s to add are the sentinel-and-verifier pairing and the third exit state.

A hard cap is a backstop. It fires after the budget is already spent on a stuck loop, and it cannot tell you the loop was stuck on pass 2 of 12; it can only stop counting at 12. Catching a stuck loop early, by noticing it re-runs the same failing command or emits the same tool call with nothing changing, is a real and different technique. This piece does not build it.

The tie cuts both ways. The honest-tie table says the third brake saves no money on the night that matters. That is deliberate, and it is the point. Read it plainly: if your only problem is spend, a cap alone closes it, and the rest is ceremony. The third brake is for readers whose problem is a morning report that lies.

The failure this piece circles also has a named academic framing: “When Agents Do Not Stop: Uncovering Infinite Agentic Loops in LLM Agents” (arXiv 2607.01641, July 2026) names the failure class and builds IAL-Scan, a static scanner that reported 68 confirmed non-stopping loops across 47 projects out of 6,549 repositories it was run on. Title, date and those abstract figures verified against the paper’s abstract page; nothing beyond it is cited. A ceiling is a budget brake. Noticing the loop is stuck before the budget pays for it is a different brake, and it is not built here.

Replay the table once more and look only at the blocked column, bottom two cells. 5 passes. $1.75. 7.5 minutes. Identical. The cell that says done and the cell that says blocked tie on every number in this piece, and cost cannot break the tie. The exit state breaks it, and the exit state is a decision you make the afternoon before, in the harness, where the agent cannot reach it.

Every number here has been a dollar figure or a clock, and neither was ever the point. The ceiling and the sentinel decide which layer gets to be wrong. Let the harness be wrong about timing: it does not know whether 12 was too many passes or too few, and it never will. Never let the agent be wrong about truth: a done that isn’t done is worse than a run that costs more, because the expensive run is at least honest about what happened to your night.

What this piece leaves open is the clock nobody is watching. The three-brakes loop exits blocked on pass 5 of the blocked night, but a loop can also spend five passes making no progress at all, re-running the same failing command, and nothing here notices until the cap catches it. That is a different brake, on a different clock, and someone has to build it. Two neighbors hold the other axes this piece set aside: the-loop-that-rereads-its-diary solves what a loop remembers between passes, where this piece argues only about when it stops, and loop-engineering-is-context-engineering is the wider frame: stopping conditions are context primitives like any other part of the loop. Let the harness be wrong about how long. Never let the agent be wrong about whether.


About the numbers. The running example is a toy, invented for traceability: a 5-task backlog, a 12-pass ceiling, $0.35 and 90 seconds per pass. Those four inputs are assumptions, not measurements, and every dollar figure, minute count and percentage in this piece is exact arithmetic on them, re-checked with an independent script before publishing. Two constructions are deliberate and should not be read as observations of real agents: the sentinel-only loop’s false done after exactly one failed attempt is an invented dramatization of premature completion, chosen to make the tie land on identical numbers, and real premature completion is not that deterministic. The framework-flag facts (Claude Code --max-turns, LangChain max_iterations at 15 by default, LangGraph recursion_limit at 25 by default, OpenAI Agents SDK max_turns) were verified against each project’s documentation in August 2026 and will drift. The arXiv paper is cited for its title, its date and the figures its own abstract reports; nothing beyond the abstract is used.

For the per-tool mechanics, see Headless & CI for running the loop unattended, Rules for writing the completion protocol as persistent context, and Configuration for treating the cap and the sentinel as tunable settings rather than buried constants. If your blocked nights pile up, your failed agent runs are labeled data covers mining them afterwards; and the unit of dispatch inside a session, a different loop entirely, is the-agent-loop-you-already-have.