Chapter 02 · Section II · 9 min read
2.2 Run-until-done: stop when the goal is proven
The second flavour of trigger — a loop that stops itself when a clear condition is met. Frees you from the chair without yet putting the loop on a schedule.
Run-until-done is a live loop with a mechanical stopping condition. You start it, walk away for a few minutes or an hour, and come back to either a finished result or a report explaining why it gave up. The trigger is still your keypress, but the stop is no longer your judgement — it’s a checkable condition the loop verifies each cycle.
What “done” looks like
The whole trick is turning a fuzzy goal (“make it good”) into a checkable condition. Some examples that work:
- Tests pass.
pytestexits 0. Trivially verifiable. - Endpoint returns 200. After deployment, curl the health check.
- A file exists. After extraction,
report_2026-07-22.pdfis on disk. - A JSON field is present. After parsing,
{ status: "approved" }is in the output. - A count reaches a target. After tagging, at least 95% of items have a label.
Some that don’t work as stop conditions:
- “The result is good.” Subjective. The loop will loop forever, or worse, will confidently claim done when it isn’t.
- “The user is happy.” There is no user in a run-until-done loop by definition.
- “The model thinks it’s done.” Self-declared completion, unverified, is the same failure mode.
The two-part safety net
Every serious run-until-done loop pairs the stop condition with a budget:
Stop when tests pass, OR after 20 cycles, whichever comes first.
The budget prevents the runaway. If the tests never pass, the loop still stops eventually, and you get a report (“gave up after 20 cycles; tests still failing on test_login_flow”) instead of a phone bill.
Common budgets:
- Cycle count. “Try up to 20 iterations.”
- Wall-clock time. “Give up after 30 minutes.”
- Token spend. “Stop once we’ve spent $5 on model calls.”
- No-progress detection. “Stop if the last three cycles produced no diff.”
Pick at least one. A run-until-done with no budget is just a bug waiting for someone to notice.
What changes vs live loops
Two things:
- You leave. By definition. Which means every design decision in Chapter 3 (sandbox, verifier, safe tool use) now matters more. Nobody is watching cycle 12 when it decides to delete something.
- You need a report at the end. Not just “did it finish?” but “what did it do?” A summary of what changed, what was tried, and what didn’t work. Without it, the return-and-review step takes as long as watching would have.
When to reach for run-until-done
Run-until-done is the right shape when:
- The goal has a clear pass condition.
- The work is bounded (you can guess how many cycles it might take).
- The cost of a runaway is capped by a budget.
- You want to reclaim the time between “I clicked start” and “the work is done.”
It is the wrong shape when:
- The goal is subjective. Use a live loop and stay in the chair.
- The work never converges in your test runs. Fix the design first; automating it won’t help.
- You want it to happen on a schedule (not on your keypress). That’s the next section.
The failure mode nobody catches until it happens
The pathological case is a loop that reports success but shouldn’t have. The stop condition was too weak — “the output file exists” — but the file is empty, or malformed, or contains an error message that happens to be plausible-looking. The loop stops, marks itself done, and moves on. You come back to a green check and shipped garbage.
Two defences: (1) make the stop condition specific (“the output file exists and parses as valid JSON and has at least 10 items”), and (2) include the verifier from 3.4. Do-and-verify is not just for output quality; it’s the last line of defence against a satisfied-with-nothing stop.
Check your understanding
Quick check
—