---
name: loop-controller
version: 1.6.0
description: |
  Add a verifiable stop condition and a mandatory guardrail stack to any task so an autonomous loop converges instead of thrashing or burning the budget — the foundation harness every loop skill composes on, so it loads first and then routes to the right primitive (/goal vs /loop vs Stop-hook vs a bash Ralph loop vs a dynamic workflow). Use whenever the agent must keep working until something is provably true — "loop until", "keep going until", "keep the suite green until it passes", "run until green", "work until done", "iterate until", tests pass, coverage hits a target, a contract's criteria hold, or a queue is empty — or to schedule a recurring check and cap iterations and budget ("iteration cap", "runaway agent", "overnight build").
requires_claude_code: false
execution_modes:
  native:
    requires: ["lifecycle_hooks", "schedule_recurring"]
    quality: equivalent
  in-session:
    requires: ["read_files", "write_files", "run_shell"]
    quality: degraded-safe
refuse_if: ["unattended_without_budget_enforcement"]
min_plan: starter
disable-model-invocation: true
allowed-tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "Agent", "Workflow"]
composes_with: ["orchestrator", "fix-until-green", "orchestrator-task-loop", "contract-conformance-loop", "babysit", "coverage-loop", "perf-loop", "self-healing-loop", "migration-loop", "nightly-docs-and-changelog", "dependency-health-loop", "codebase-exploration-loop", "repo-cleanup-loop", "qe-agent", "contract-auditor", "diagnose-loop", "context-manager", "model-adaptation", "find-unknowns", "loop", "schedule"]
spawned_by: ["orchestrator"]
---

# loop-controller

> **Why this is a deliberate (`disable-model-invocation`) skill.** A loop edits,
> commits, and spends tokens on its own. You want to *type* `/loop-controller`
> (or have the orchestrator dispatch it), not have Claude silently start an
> autonomous loop because a test happened to fail. The discipline below is what
> makes that safe.

## The one rule that governs every loop

**A loop is only as good as its stopping condition.** Loops *converge* when
"done" is an external signal the agent cannot argue with — a test exit code, a
coverage percentage, an empty queue, a fresh evaluator's verdict. They *diverge*
— oscillate, thrash, or run the budget to zero — when "done" is a subjective
judgment the agent grades itself on. Agents are pathological optimists about
their own work; left to self-assess, they declare victory early.

There is a subtler failure an objective proof does **not** cure: a signal that
is mechanical, default-FAIL, and un-gameable can still measure the **wrong
thing**. A test suite green against a mock, a stub, or a fixture is all three of
those — and proves nothing about whether the real product works. That is
*convergence on a fiction*, and because it wears the costume of rigor (an exit
code! a number!) it slips past review more easily than a subjective claim would.
65 green tests against a mocked backend look exactly like 65 green tests against
the real one. So the proof must measure the **real goal**, not a stand-in for it
(Step 2) — un-gameable *and* pointed at the thing that actually matters.

So the entire job of this skill is to turn a fuzzy "keep working on X" into a
**convergent** loop: a mechanical proof *of the real goal*, the right execution
primitive, and a guardrail stack that stops the loop whether or not the proof is
ever met.

Everything else here — fix-until-green, coverage-loop, perf-loop — is a *config*
of this harness: a specific proof plugged into the same machinery. Author new
loops against this skill; don't reinvent the loop engine.

## First — does a concrete loop already exist?

Twelve configs of this harness already ship in `skills/loops/`. Route the intent
to one of them before authoring anything — Steps 0–6 below are for the loop that
*doesn't* exist yet. (Every one of these is `disable-model-invocation: true`:
dispatch it by its slash command, or let the orchestrator dispatch the build
loops. None auto-fires.)

| The intent is… | Dispatch |
|---|---|
| "make tests / lint / typecheck pass", red CI, "run until green" | `fix-until-green` |
| Drain a multi-agent build's shared task list until every task passes its gate (Agent Teams) | `orchestrator-task-loop` |
| Build until an authored contract's criteria all hold (build-until-spec) | `contract-conformance-loop` |
| "Keep my PR green / rebased / answer review comments while I'm away" | `babysit` |
| "Raise test coverage to N%" | `coverage-loop` |
| "Get this metric under its budget" (latency, bundle size, memory) | `perf-loop` |
| "Watch the logs / CI and fix what breaks" | `self-healing-loop` |
| "Migrate / transform every file in this enumerated set" | `migration-loop` |
| "Keep the docs + changelog from rotting" (nightly) | `nightly-docs-and-changelog` |
| "Keep dependencies current and audited" | `dependency-health-loop` |
| "Map this unfamiliar codebase until my questions are answered" | `codebase-exploration-loop` |
| "Weekly repo hygiene — stale branches, PRs, worktrees" | `repo-cleanup-loop` |

No row fits → continue below and author the new loop against this harness.

## Step 0 — Should this even be a loop?

A loop has real setup cost and its own failure modes; the mistake is looping
things that don't earn one. Before building anything, put the task through four
questions — a "no" on any of them is a strong signal to just do the work manually
(or once) instead:

1. **Does it repeat?** A one-off is usually faster by hand. Loops pay off on work
   that comes back — every PR, every night, every new ticket.
2. **Can "done" be verified mechanically?** If nothing but a human can reject a
   bad result, the human is still the real gate and the loop saves little. This is
   the same requirement as Step 2 — no mechanical proof, no convergent loop.
3. **Can the agent act end-to-end?** If it must stop for permission or missing
   context every few minutes, that's assisted manual work, not a loop.
4. **Is "done" objective?** "Tests pass" / "queue empty" loops; "the design feels
   right" / "the strategy is sound" does not. Subjective, high-stakes, judgment-
   heavy work (architecture rewrites, auth, payments, prod deploys) is where a
   human stays in the driver's seat — keep it manual, or gate it hard (guardrail 4).

Pass all four and the 5-part contract below will fill in cleanly. Fail one and
the honest move is to not build the loop.

## The 5-part loop contract (required)

Before running anything, write the loop's contract. A loop that can't fill in
all five lines isn't ready — the blank is the bug.

| Part | Question it answers | Example |
|---|---|---|
| **trigger** | What starts this loop? | "red CI on branch X" / "every 30m" / "user runs `/fix-until-green`" |
| **action** | What does one iteration *do*? | "run the gate, fix one root-cause failure, re-run the whole gate" |
| **proof** | What mechanical signal proves done? | "`npm test` exits 0 **and** lint exits 0 **and** typecheck exits 0" |
| **memory** | What state survives between iterations? | "`PROGRESS.md`, the feature-list JSON, git history" |
| **stop** | Every way this loop ends | "proof passes **OR** 20 iterations **OR** no-progress for 3 rounds **OR** budget cap" |

The `proof` and `stop` lines are load-bearing. Keep this contract in the loop
skill's body (or at the top of the run) so any reviewer can audit convergence at
a glance. This is the Forward Future "loop library" 5-part schema, adopted as the
required contract. The taxonomy of common loop shapes (fix-until-green,
build-until-spec, refine-until-quality, research, review, migration, coverage,
perf, self-healing, exploration) lives in `references/authoring.md`.

## Step 1 — Choose the primitive

The first decision is *which engine runs the loop*. Get this wrong and you burn
money: a scheduler pointed at finishable work re-runs blindly on the clock; a
"work until done" wrapper pointed at an external poll spins forever.

**The decision rule:** *Are you pushing work to a finish line, or watching for
something to change?*

| If the job is… | Use | Because |
|---|---|---|
| Push to a finish line, proof is **provable from Claude's own output** (test exits 0, git clean) | **`/goal`** | A small fast model judges the transcript each turn; "no" feeds the reason back. The closest native "loop until done." |
| Push to a finish line, proof needs a **script/file/tool check** the model can't just assert | **Stop-hook gate** | A hook runs the real check and blocks exit until it passes — ships *with* the skill. |
| **Watch / poll** for an external change on a cadence | **`/loop`** | A thin scheduler that re-runs a prompt or slash command on an interval; it does not push to a finish line. |
| One big mechanical change across **many files in parallel** | **`/batch` / dynamic workflow** | Fans out across worktree-isolated subagents; compose with the `orchestrator`'s Workflow mode. |
| **Greenfield**, want a fresh context window every iteration | **`claude -p` bash loop (Ralph)** | State lives on disk; each pass starts clean. Needs a sandbox (`--dangerously-skip-permissions`). |

Do not conflate `/goal` (finish line) with `/loop` (watch). They are different
primitives with different failure modes. Full mechanics, constraints, and the
exact invocations for each — including `/goal`'s evaluator-can't-read-files
limit and `/loop`'s session-scope/expiry/no-catch-up rules — are in
`references/primitives.md`. **Read it before authoring.**

**Hosts without these primitives** (Gemini CLI, Codex, Cursor, OpenCode, a bare
API agent): the table above is Claude Code-native — do not pretend `/goal`,
`/loop` or a Stop hook exist. Two routes remain. **In-session:** state the
proof command and iteration cap up front, get the owner's one approval (or use
the plan the orchestrator already had approved), then iterate in the live host
session without further prompts, running every guardrail in Step 3 yourself;
stop at the cap or on HITL triggers (Step 3, guardrail 4). **Unattended:** only through `scripts/run_guarded.py` around an
owner-approved bounded adapter for the host's non-interactive CLI — one whose
per-call token/cost ceiling is known — with declared call/token/cost/wall-time
limits (see *Executable external controller* below). With neither, refuse
unattended work rather than looping on prompt discipline alone.

## Step 2 — Make "done" mechanical and default-FAIL

A convergent loop needs a proof signal the agent cannot rationalize past.

- **Phrase "done" as an observable.** "all tests in `test/auth` pass and lint is
  clean," not "the auth code looks correct." If the proof is a number, name the
  artifact that produces it (coverage report, benchmark output, the gate's exit
  code).
- **Default-FAIL.** Every criterion starts `false` and only flips on *evidence*.
  Store the criteria as JSON, not prose — a model is far less likely to quietly
  rewrite a JSON `"passed": false` than to soften a sentence. (Anthropic's
  long-running-agent harness stores the feature list as JSON for exactly this
  reason.)
- **Measure the goal, not a stand-in.** An objective signal is necessary but not
  sufficient — it also has to exercise the *real* thing the loop is for. If the
  goal depends on a real dependency (a live service, real data, an integration, a
  deploy) and the proof only touches a mock / stub / fixture of it, the loop will
  converge — green, confident, and wrong. Either the proof exercises the real
  path at least once, or it is *explicitly* labelled scaffold-level ("the mocked
  build is internally consistent") with a separate goal-level proof named. A
  green that came from measuring the stand-in instead of the goal is a coverage
  gap wearing a green badge — the same class of bug as a green that came from
  moving the number instead of fixing the cause (Step 3, guardrail 6), and just
  as much a *finding*.
- **Separate the grader from the doer for any subjective bar.** When "done"
  can't be reduced to an exit code (UI quality, doc clarity, API ergonomics),
  the proof is a **fresh-context evaluator subagent** — spawned with **no
  Write/Edit tools** so it cannot "fix" a failure by lowering the bar, and
  blind to how the work was built so it can't rubber-stamp its own reasoning.
  This is the GAN / Plan-Generate-Evaluate pattern. A same-model critic that
  saw the build approves mediocre work; an external signal (tests) or a fresh
  evaluator is what stops the rubber-stamp. See `references/authoring.md`.

## Step 3 — Install the guardrail stack (mandatory)

Every loop ships with all of these. They are not optional polish — a documented
multi-agent loop ran **11 days and burned tens of thousands of dollars** because
it had observability but no *enforcement*. Alerting is not a guardrail;
termination is.

1. **Iteration cap.** A hard `--max-iterations` (or "stop after N turns" baked
   into the `/goal` condition — `/goal` has no native cap). This is the primary
   backstop when the proof is never met.
2. **Token / cost budget with enforcement.** A ceiling that *terminates* the
   loop, not just warns. `/goal` has no built-in budget — embed an explicit turn
   cap and abort condition; watching `/cost` in prompt prose is advisory only, not
   hard cancellation. Unattended or bash/Ralph execution requires an **external
   process wrapper** enforcing max calls, wall-clock timeout, token budget, and
   process locks with `SIGTERM`/`SIGKILL` cancellation. Unattended runs must
   produce a durable proof artifact and run within external budget ceilings. A
   50-iteration run on a large codebase can cost $50–100+. The number that
   actually tells you whether the loop is worth running is **cost per *accepted*
   result**, not tokens spent or iterations run: a loop that opens five PRs where
   you merge one, or emits a daily report no one reads, can cost more than doing
   the work by hand. Track yield, not spend — a low accept rate means the loop is
   manufacturing review debt, and the fix is a tighter proof (Step 2), not a
   bigger budget.
3. **No-progress / oscillation detection.** Stop if iterations stop changing
   state, or if output repeats (≥~90% similarity to a recent iteration), or if
   token use grows quadratically rather than linearly. Thrashing and budget
   exhaustion are the two dominant non-convergence modes — detect both.
4. **HITL checkpoint before anything irreversible.** Pause for a human before a
   DB write, a deploy, an external API call, a force-push. Unattended loops run
   only what is reversible and has a hard verifier.
5. **Durable checkpoints.** Save working files, diff and proof receipts every
   iteration. Commit only when the owner has authorized commits. Never reset,
   discard another worker's changes, or delete work as an automatic recovery
   step; propose an exact scoped rollback and obtain approval first.
6. **Never let the loop weaken its own gate.** Forbid editing or deleting tests
   to make them pass, silencing a check with an ignore directive, or relocating
   a violation into the checker's blind spot. A green that came from moving the
   number instead of fixing the cause is a *finding*, not a win. When a gate
   flips red→green, read the diff that did it.

The full stack — including the `stop_hook_active` guard for Stop-hook loops, the
oscillation thresholds, and the staged-adoption / rollback ladder — is in
`references/safety.md`.

### Executable external controller

Use `scripts/run_guarded.py` from the actual `SKILL_ROOT` for POSIX bounded
adapters. It reserves worst-case call/token/cost liability **before dispatch**,
checks a frozen verifier manifest before/during/after commands, uses an exclusive
lock, and cancels the whole process group at the wall-time ceiling. Failed calls
keep their reservations; consumed state cannot be restarted with reset counters.
Read `references/safety.md` for the budget/manifest interface and proof boundary.
A command with unknown internal calls or cost is BLOCKED: this wrapper is not a
provider traffic meter. Native primitives without a verified hard-bound adapter
still refuse unattended use. Completion of bounded dispatches is not task/QE acceptance.

## Step 4 — Externalize state (so iterations are stateless)

A loop that remembers across iterations only through the conversation breaks the
moment context compacts or a fresh-context pass starts. Externalize to
**path-addressable files**:

- a **progress file** (`PROGRESS.md` / `claude-progress.txt`) — what's done, what's next;
- a **task / feature-list JSON** — the default-FAIL criteria;
- a live **plan/TODO** (`fix_plan.md`) — rewritten freely each pass;
- an **`init.sh`** — how to build and run;
- **git history** — the durable checkpoint trail.

Each iteration starts by *reading* these, does one increment, then *updates*
them and commits. Read the caps (max-iterations, budget) and the state-file
paths from `.claude/profile.yaml` when present, so the same loop skill works
across projects without hard-coding. (This is the orchestrator/role-skill
convention; loops follow it.)

## Step 5 — Run, watch for divergence, know when to kill

- **One task per iteration.** Trust the loop to pick the most important next
  thing; don't batch. Spawn subagents for expensive search/verification, but
  **cap build/test parallelism at 1** — two agents building at once destroy the
  backpressure signal.
- **Re-verify the whole, not just the change.** After each fix, re-run the
  *entire* proof (full suite, every page, the whole rubric), not only the unit
  you touched. "Restart the streak" is how you catch a fix that broke something
  else.
- **Roll a loop back to attended (or kill it) when** any of: token spend grows
  non-linearly, output similarity >90% across iterations, a fresh evaluator and
  your own judgment disagree, or it's about to touch an irreversible resource
  without a checkpoint. A suspiciously easy convergence is itself a finding —
  inspect before trusting.

## Step 6 — Long-run behavioral hygiene (Claude 5 family)

Steps 1–5 make the loop *converge*. This step keeps the **model's behavior** honest over
a long run on the Claude 5 family (Fable 5 / Mythos 5), where a single turn can run for
minutes and an autonomous run for hours. These are prompt-level additions to the loop's
brief — the harness enforces convergence; these keep the agent from lying, quitting early,
or panicking about context on the way there. Drop-in instruction text and the 5-part
mapping for each are in `model-adaptation/references/long-run-hygiene.md`; wire what the
loop needs:

- **Evidence-backed progress (anti-fabrication).** Instruct the agent to audit each
  progress claim against an actual tool result before reporting it. This is *distinct from*
  guardrail 6: guardrail 6 stops the loop from gaming the mechanical *gate*; this stops the
  model from *narrating* work it never verified. Both matter; neither substitutes.
- **Don't end a turn on a promise.** Deep in a run the model can say "I'll now run X" with
  no tool call, or pause to ask when it already has enough. `/goal` + Stop-hooks catch this
  mechanically (Step 1); add the prompt-level last-paragraph self-check and, for unattended
  runs, the autonomous-operation reminder so the agent doesn't lean on the hook.
- **Don't surface the budget countdown to the model.** Guardrail 2 watches `/cost` for
  *enforcement* — but that number is a **harness** stop signal, not something to show the
  model. Seeing a remaining-token countdown makes the Claude 5 family prematurely summarize,
  offer to hand off, or trim its own work. Read the budget from externalized state (Step 4)
  and decide in the harness; if a count must be visible, add the "you have ample context,
  continue" reassurance.
- **Model & effort per iteration.** Effort is the primary intelligence/latency/cost dial: `high`
  default, `xhigh` for the hardest proof/verify steps, `medium`/`low` for routine passes.
  Lower effort on the Claude 5 family often beats `xhigh` on prior models — reduce it if a
  loop converges but each iteration runs longer than the work needs. Model tiers the same
  way — pick both per `model-adaptation`'s *Model & effort tiering* policy: workers and
  routine passes tier down, but the fresh-context evaluator (Step 2) stays on the top tier.
- **Send-to-user for verbatim mid-turn output.** A loop otherwise only speaks by *ending*
  its turn for HITL. For long async loops that must surface a deliverable or a direct answer
  *without* stopping, give the agent a client-side `send_to_user` tool plus the elicitation
  line — never route the model's reasoning through it (that's the `reasoning_extraction`
  refusal landmine; see `model-adaptation`).

The fresh-context evaluator (Step 2) is itself one of these patterns — the guide's
"fresh verifiers beat self-critique" — so it's already wired; just run it *periodically*
on a long build, not only at the end.

## Comprehension debt — the human-side stop condition

Steps 1–6 keep the *machine* honest. There is one failure they don't catch,
because it lands on the human, not the loop: a loop that ships correct, green
diffs faster than anyone reads them. Each merge feels like progress, but the
codebase starts moving faster than the team's understanding of it — the tests
pass, yet nobody can say *why* the code is shaped the way it is. That's
**comprehension debt**, and it's the antidote's mirror image of convergence-on-a-
fiction: there the proof was too weak, here the proof was fine but human
understanding silently fell behind. The bill arrives at the next bug, in a module
no one can reason about.

The guardrails don't fix this because it isn't a convergence problem — it's a
review problem. Keep it in check by keeping loops on **small, readable diffs**,
having a human actually read the diff a red→green flip produced (guardrail 6
already asks for this), and — for any substantial or fully agent-driven change —
running a **comprehension quiz before merge**: `find-unknowns` owns that move
(explain the diff and what it touches, then test that you can pass a quiz on the
non-obvious behavior). Autonomy that outruns understanding isn't a faster team;
it's a deferred debugging session.

## Using it as the harness other loops compose on

Concrete loop skills don't re-implement any of the above — they *fill in the
contract* and inherit the machinery:

- **`fix-until-green`** — proof = test+lint+typecheck all exit 0; primitive =
  `/goal` or a Stop-hook gate; the canonical first instance.
- A new loop = a new `skills/loops/<name>/` whose SKILL.md states its 5-part
  contract, names its proof artifact, and points back here for the guardrails.
  The authoring walkthrough (with the loop taxonomy and a frontmatter template)
  is `references/authoring.md`.

Under the **orchestrator**, loops slot in at two levels — and both are built:
`orchestrator-task-loop` is the outer loop over the shared task list (re-assign
until every task passes its gate), while the inner loops run per role —
`fix-until-green` at the wave/QA gates, and `contract-conformance-loop` /
`coverage-loop` / `perf-loop` / `migration-loop` dispatched when the mission
calls for them (see the orchestrator's `references/phase-guide.md`, *Optional
build loops*).

## Reference files

- `references/primitives.md` — `/goal`, `/loop`, `/batch`, dynamic workflows,
  Stop-hooks, and the bash-Ralph loop: exact mechanics, constraints, invocations,
  and the implementation-approach tradeoff table. Read before choosing a primitive.
- `references/safety.md` — the full guardrail stack, `stop_hook_active`,
  oscillation/no-progress detection, the cost footguns, and the staged-adoption
  and rollback ladder.
- `references/authoring.md` — the 10-archetype loop taxonomy, the fresh-context
  evaluator / default-FAIL pattern in detail, and a step-by-step template for
  writing a new loop skill that composes on this one.
