I counted dcode's loop and found the stopping condition
Two thirds of internal/loop is test, and the file that decides what done means protects its own tests from whoever is stuck in the loop.

I went to count the lines in internal/loop in dcode expecting to
find where the complexity was, and what I found instead was a ratio I had not
noticed while writing it: 2,580 lines of implementation against 5,541 of
test, six files of code against twenty-three of test, two to one in a part of
the system that on paper does one very simple thing — perceive, act, observe the
result, decide again.
The hard part, I discovered rereading what I had written myself, is the part that decides the turn is over.
The file that decides what done means
done.go runs to 415 lines and carries a comment that sums up the whole problem
better than I could rewrite it here: a stopping condition is something that can
be checked, and prose does not qualify. "The tests pass" is a fact. "The code
is clean" is not, and a criterion judged by the model hands the decision about
being done back to the model, now with twenty turns spent on the way there.
It is the question almost nobody answers deliberately, which is why it ends up answered by accident, with the loop stopping when the model thinks it has finished. Writing the test before the code solves this for a reason that is not the one TDD essays usually give: a failing test is a stopping condition the machine can check on its own, so the turn has somewhere to go and knows when it arrived. Without that, "done" is an opinion, and it is always the opinion of the party with an incentive to finish.
Why the tests need protecting from whoever is inside the loop
The part I found most uncomfortable to reread sits in the definition of the criteria set, where there is a list of protected paths — the test files, typically. The comment explains why, and the explanation is about incentive rather than trust: without that protection the rest is theatre, because an agent that cannot get out of the loop discovers that the shortest way out is to weaken the thing measuring it.
Then comes the line I took seriously enough to implement: a false test is strictly worse than a false report, because a false report is discovered by running something, while a false test sits in the repository pretending to be coverage forever.
It is not a prohibition — sometimes fixing the test is the work. It is visibility, and that is the whole difference between a loop that ensures quality and one that manufactures the appearance of it.
Every turn costs the entire conversation
A language model has no memory between calls, and what looks like continuous dialogue is the whole history being resent from the beginning on every turn. The consequence is direct and unintuitive: what costs is the number of turns, not the size of the task.
That reorganises what counts as good code inside an agent. Independent calls go together in a single turn, because reading three files across three rounds costs three entire conversations while reading all three at once costs one. A long process goes to the background and is awaited once rather than polled every ten seconds — polling is the most expensive antipattern there is here, since each poll pays the full history just to discover it has not finished. And a check that already passed is not repeated until the code changes.
This is not penny-pinching. It is the difference between an agent that finishes the task and one that spends the budget confirming everything is fine.
What survives the turn and what evaporates in it
Anything that matters and is not written to disk goes away in the compaction, at
the end of the session, or in that --resume that did not restore what you
assumed it would. Hence the asymmetry worth internalising: what sits in the
context window is volatile and expensive, and what sits in a file is durable and
cheap.
Architecture decisions, module contracts, acceptance criteria and the reason behind a choice belong on disk, where they stay readable for the agent next session, for another person next month, and for you once you have forgotten. A specification is not bureaucracy for the agent; it is the part of the state that survives the turn.
The characteristic failure of an agent loop is insisting
A call is blocked and the agent tries again, identically. It is blocked again and it tries a slightly different path. A test fails and it edits the test instead of the code, which is exactly the behaviour the protected paths list exists to make visible. The search finds nothing and it repeats with another word, and another, and another.
Every loop needs a limit that does not depend on success, because three attempts in the same direction are a symptom that the direction is wrong, and the right exit is to stop, say what was tried, and hand the decision back to whoever has the context to make it. An agent that knows how to stop is worth more than one that knows how to persist.
Evidence has to sit at the same level as the claim
The last turn is the one that most readily falsifies itself, because it is
tempting to check a string in the HTML and declare the page working. grep
finding the right text proves the text is in the file, and does not prove the
page renders, that the CSS loaded, or that the link is clickable.
Opening it in a browser and looking costs one more turn and is the only one that truly closes the loop. The principle holds everywhere, and it is the same one those 5,541 lines of test encode: if you claim it works, the proof is it working.
What improves on its own and what does not
The model chooses what to do inside the turn, and that part improves with every version somebody ships, without you doing anything. The loop decides how many turns exist, what each one carries, and when they end.
That second part only improves if somebody designs it, which is why the two thirds of test are there. The turn is easy to write. Knowing it ended is what costs.