Skip to content

Heartbeat

Step 5 · Conditional Loops — Run Until Done

No clock at all: the next beat starts because the last one finished and the goal still isn't met. The whole design lives in a single sentence — the…

No clock at all: the next beat starts because the last one finished and the goal still isn't met. The whole design lives in a single sentence — the stopping condition.

The hook

"Make the tests pass, then stop." Eleven minutes later the suite is green and the loop is idle. Now run the exact same prompt on a messier repo: forty minutes in, it's "fixing" the same test for the ninth time, each fix undoing the last. One sentence separated those two afternoons — and it was not the goal. It was the stop.

Run-until-done (plain English)

A conditional loop has no timer. Its heartbeat is the condition itself: beat, check the condition, beat again, until the condition holds. It's the natural shape for work that genuinely ends — empty the checklist, green the suite, resolve the findings.

And because nothing external paces it, everything rides on three stops actually working:

  1. Success — the condition is provably met. Write it like a spec, because it is one: "suite exits 0", "no unchecked boxes" — never "looks good."
  2. Limit — a max-runs cap it can never exceed. This is your doom-loop insurance. That ninth identical "fix" above should have been flatly impossible. A sane limit — plus per-item retry caps like MAX_RETRIES and a watchdog on retries — turns an infinite argument with reality into a bounded one that escalates to you.
  3. No progress — nothing changed for 3 consecutive beats → log it and stop. Progress is measured against the spine, never against effort.

The folk name for the degenerate case is the Ralph loop — the same beat repeated naively until something gives. Here's the punchline worth tattooing on: with a provable stop, a limit, and a no-progress rule, even a Ralph loop is safe. Without them, no amount of model intelligence is.

Diagram

The mechanics in each tool

Each tool expresses the same three stops. Watch where the success test and the cap live:

# Claude Code — live docs: https://docs.claude.com/en/docs/claude-code
> /loop Run the suite. Fix the FIRST failure only. Log one line.
  Stop when the suite passes OR after 15 runs.
# Goal-shaped variants (e.g. /goal) pair the maker with a checker that
# reads the transcript and judges "actually done?" — maker ≠ checker,
# applied to done-ness itself. Retry guards (MAX_RETRIES-style caps,
# retry watchdogs) belong on every conditional loop.
# OpenCode — live docs: https://opencode.ai/docs
# The cap is structural — the for-loop IS the run limit:
for i in $(seq 1 15); do
  npm test >/dev/null 2>&1 && break        # provable success stop
  opencode run "Run the tests. Fix the FIRST failure only."
done
# Bounded agent steps per run are configurable — see the live docs.

[!NOTE] Going deeper: "stopping condition = spec" is the bridge back to the spec-driven primer — a stop you can't write precisely is simply a goal you haven't specified yet. The doom loop and its cousins get a full page in failure modes.

Check yourself

Q: "Stop when the code is clean" vs. "stop when npm run lint exits 0, or after 10 runs, or after 3 beats with an unchanged lint count." What does the second version have that the first doesn't — name all three?

Answer

All three stops: a provable success condition (lint exits 0 — a fact), a limit (10 runs — doom-loop insurance), and a no-progress rule (unchanged lint count for 3 beats — stuck-detection measured against real state). "Clean" is a feeling. The loop can neither prove it nor be safely trusted to pursue it unboundedly.

Try With AI

Break 3 tests yourself in a throwaway repo. Run "Fix the FIRST failing test only, then stop" by hand, three times over. Now wrap it in a conditional loop with the three stops and run it once. Diff the two experiences: what you did between the manual runs — check, decide, re-prompt — is exactly what the condition, limit, and progress rule now encode for you.

When it goes wrong

SymptomCauseFix
Ninth identical "fix" of the same testDoom loop — no per-item retry capMAX_RETRIES-style cap per item; escalate after N attempts
Loop declares victory, suite still redStop written as a feelingSuccess = machine-checkable fact, verified by a checker, not the maker
Runs forever making cosmetic changesNo no-progress stopProgress measured against the spine; 3 unchanged beats = stop
Hits the limit every single dayLimit hiding a real problemThe limit stop is a signal, not a nuisance — read the log, fix the cause

Glossary terms used on this page: conditional loop, doom loop, stopping condition, run limit — see the glossary.

Sources: run-until-done mechanics come from Panaversity's Loop Engineering: A Crash Course (S1) and Scheduled Tasks: The Loop Skill & Cron Tools (S4); stopping-condition-as-spec from Spec-Driven Development (S3). Full attribution: resources/sources.md.