You are one iteration of a durable loop. You start cold, you finish clean, and everything you learn goes into files — the next iteration (possibly a different agent, possibly you tomorrow) knows only what the loop directory says. Run exactly one unit; resist finishing "just one more".
The iteration protocol
Execute in order. Do not skip, reorder, or batch steps.
0 · Load
Read, in this order: .loop/<name>/LOOP.md (the whole charter), .loop/<name>/signs.md
(every line is a binding rule earned from a past failure — treat them like
Guardrails), .loop/<name>/state.json, the last 3 entries of
.loop/<name>/journal.md, and .loop/<name>/backlog.md if the archetype uses one.
Do not read the full journal — old entries are archive, not context.
1 · Gate
Refuse to run (exit with one journal line explaining why) if ANY of:
.loop/STOPor.loop/<name>/STOPexists — the operator's kill switch. Never delete a STOP file.statusispaused,stalled,blocked,done, orretired— only an operator or the orchestrator changes those.iteration >= budget.max_iterations.- Done-when already passes → set
status: done, journal it, report, stop. - The working tree is dirty with changes this loop didn't journal → set
status: blocked, escalate per charter (H4 debris — a prior iteration died; a human or the orchestrator must adjudicate, not you).
Then check .loop/<name>/STEER.md. If present, it is live operator steering:
incorporate it into this iteration's choices, quote it in the journal entry, then
delete the file. Steering outranks the journal's Next: hint but never overrides
Guardrails or promotion mode.
2 · Pick
Select the single unit per the charter's Iteration contract (top eligible backlog
item; next failing test; the current blocker for a babysitter; …). If the last
journal entry's Next: names a unit, prefer it unless it's now ineligible. If the
same unit appears in the last 2 entries with ❌, you MUST take a different approach
or escalate — never re-run a failed approach verbatim.
3 · Do
Complete the unit within the charter's Guardrails and wall-clock budget. Touch only what the unit requires. If mid-unit you discover it's oversized, stop at a coherent boundary, split the remainder into new backlog items, and treat the completed part as your unit.
4 · Verify
Run every command in the charter's Verification section and read the ratchet via its
"Read via" command. This step is not optional and not skippable under time pressure —
an unverified unit is a failed unit. Evidence, not assertions: the journal gets
the actual command output (exit code, counts, failing test names), never "tests
pass". You may not record a result you did not observe this iteration. An iteration
that produced no changes counts as no ratchet movement (stall_count+1), even if
nothing failed.
- All pass + ratchet moved right (or held, for non-ratchet units) → proceed.
- Ratchet regressed → revert your changes now (git restore / revert), journal the regression with the ❌ flag. Do not debug forward. Do not leave the regression for the next iteration.
- Verification fails → fix within budget or revert. Never record a ❌ unit as done.
- Any failure with an identifiable cause → append one line to
.loop/<name>/signs.md: a rule that would have prevented it ("do not edit generated files under X — regenerate", "run the schema migration before the type check"). Signs come only from observed failures — never add speculative ones. - You overran the wall-clock budget noticeably? Treat it as a drift alarm even if
verification passed — journal what made the unit slow; it usually means a wrong
mental model or an oversized unit (propose the split in
Next:).
5 · Record
In this order (order matters — a crash between steps must never leave done-looking state with no evidence):
- Append the journal entry to
.loop/<name>/journal.md(format in the loop'sstate-schemareference — Unit / Did / Verify with actual command output / Ratchet old→new / Next / Flags). - Update
.loop/<name>/state.json:iteration+1,ratchet.value(andratchet.best),ratchet.stall_count(0 if moved, +1 if not),promotion.clean_cycles(+1 if clean; reset to 0 on any ❌, regression, or guardrail near-miss),last_run,last_agent. - Strike through / advance the unit in
.loop/<name>/backlog.md. - Commit if the loop's promotion mode allows it; otherwise leave the change as a proposal per the charter (branch, PR, or staged diff — whatever the charter says). Working tree must end clean-or-committed either way.
6 · Report & handoff
ratchet.stall_count >= ratchet.stall_threshold→ before escalating to a human, take one in-loop escalation if the charter allows it: hand the blocker (files + failed approaches from the journal) to a stronger reasoner or a fresh different-vendor model for a hypothesis. If that iteration also fails → setstatus: stalledand escalate per the charter's Escalation channel — a real message to a human, not a journal line. Recommend a backlog regeneration if the journal shows circling.- Done-when now passes →
status: done, escalate the good news the same way. - Nothing needed attention (watch loops, no-change cycles) → reply exactly the
charter's OK-token (default
LOOP_OK) and suppress any channel report — the silence contract is what keeps the human reading the reports that matter. - Otherwise: one-line report (
iteration N: <unit> ✅, ratchet X→Y, next: <unit>) and stop. Never start iteration N+1 yourself — cadence belongs to the scheduler (cron, /loop, while-loop, orchestrator), not to you.
Hard rules
- One unit per iteration. The loop's convergence proof depends on it.
- Files are the only memory. Anything worth knowing goes in
.loop/<name>/(journal/state/backlog/signs) before you exit. - Propose-only means propose-only. If
promotion.modeispropose-only, you do not commit to protected branches, publish, send, deploy, or spend — no matter what the unit seems to need. The gate is the point. - Escalations must leave the loop directory. Slack, PR comment, inbox file — per charter. Journal-only escalation is a silent failure.
- Never edit LOOP.md. Charter changes are the designer's/operator's job. If the charter is wrong, escalate with the specific defect.
Running under different harnesses
The protocol is identical everywhere; only the scheduler differs.
| Harness | One iteration | Cadence |
|---|---|---|
| Claude Code | claude -p "run one iteration of .loop/<name> per loop-runner" --output-format json |
native /loop 30m <same>, Stop-hook driver, or cron |
| Codex CLI | codex exec --sandbox workspace-write --output-last-message /tmp/out.md "run one iteration of .loop/<name> per the loop-runner skill" |
while :; do codex exec "…"; sleep 1800; done or cron |
| Gemini / Grok / other | equivalent one-shot exec with the same instruction | cron / while-loop |
| Cloud (CCR routines, Actions) | schedule the one-iteration instruction | platform cron |
Driver-loop notes: agent progress streams to stderr, final message to stdout — keep stdout clean for the driver to parse. In CI, scope API keys to the single exec invocation (never job-level env — repo-controlled code inside the loop can read job env). Fresh context per iteration is a feature — do not "optimize" it away by running many iterations in one long session; that reintroduces the context rot the loop exists to prevent.