A configuration of
loop-controller. That skill supplies the loop machinery — primitive selection, the full guardrail stack, state externalization. This skill supplies the two things specific to draining the task list: a mechanical proof (every taskcompletedand passing itsTaskCompletedgate) and the assignment/supervision discipline that keeps the loop from declaring victory on a half-drained list. Readloop-controllerfor the guardrails; they're inherited, not repeated here.Why
disable-model-invocation: this loop spawns teammates and drives an entire task list to completion on its own — assigning, supervising, gating, and re-triggering its own stop. You want to type/orchestrator-task-loop(or have the orchestrator dispatch it) — not have Claude silently start an unbounded team-supervision loop because a task board happened to exist.Experimental. Agent Teams is disabled by default and gated behind
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1, with known limitations (no session resumption for in-process teammates, lagging task-state propagation, slow shutdown). This is a prototype-behind-the-flag skill. If the flag is unset, it refuses to start and points you at the orchestrator's subagent/sequential path (see Step 1 + the degradation note).
The 5-part contract
| Part | This loop |
|---|---|
| trigger | A populated Agent Teams shared task list (tasks pending/in_progress, team spawned) and CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 — or an explicit /orchestrator-task-loop. Dispatched by the orchestrator after contracts exist and the board is seeded (one task per contract-bounded unit, blockedBy edges from the dependency graph). |
| action | One supervision pass: read the board → for every idle teammate (surfaced by TeammateIdle), assign the highest-priority pending task whose blockedBy set is fully completed → let teammates work in their own context windows and claim tasks → on completed, run that task's TaskCompleted gate (pass → done; fail → rolled back to pending with the failure as feedback) → surface blocked/ambiguous tasks for HITL. Assignment, not implementation — the loop routes and supervises; teammates do the work. |
| proof | The board is drained: every task completed AND each passed its TaskCompleted gate AND no blockedBy edge points at a non-completed task — observed in one full read. Default-FAIL: assume undrained until that whole-board predicate holds. (See "The proof".) |
| memory | The shared task list file is the durable state (task states + blockedBy + gate verdicts survive across passes and fresh contexts) — this loop's "feature-list JSON," default-FAIL. Plus a checkpoint commit per task, the team's peer-message log, and an optional PROGRESS.md roll-up. No cross-iteration state lives in the lead's conversation. |
| stop | board drained (success) OR hard iteration (pass) cap OR the 3-failure circuit breaker (board/task unchanged across 3 consecutive passes, or a task ping-pongs completed→gate-fail→pending ≥3×) OR enforced token/cost budget cap OR an HITL-blocked task with no unblocked work left. |
The proof: a fully drained task list, default-FAIL
"Done" is not "every teammate said they finished." It is a mechanical, default-FAIL predicate over the whole board, re-evaluated after every pass ("restart the streak"):
DONE ⟺ for every task t on the board:
t.state == completed, andtpassed itsTaskCompletedgate (last verdict PASS — acompletedtask whose gate is unrun or failed does not count), and- every
b ∈ t.blockedByis itselfcompletedand gate-passing (no dangling dependency), and - no
pending/in_progresstasks remain, and noidleteammate has assignable work.
Each clause starts false and flips only on evidence read from the task-list
file. The proof is over the board, not the last task touched: completing task
N while a TaskCompleted rollback re-opened task M means the board is not
drained — only a full re-read catches it. This is exactly loop-controller's
default-FAIL + re-verify-the-whole rule applied to a task board instead of one
exit code, and it is where this loop differs from a single-task loop: the verifier
is the board predicate, evaluated every pass.
Step 1 — Detect the Agent Teams runtime, or degrade
Before assigning anything, confirm the substrate. Precedence:
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1must be set. If it is unset, this loop refuses to start — tell the user to set the flag, or hand back to the orchestrator's subagent/sequential path (the orchestrator's runtime tree: subagents via Task/Agent, else sequential). The shared task list + Teammate/Task hooks this loop relies on only exist under native Agent Teams.- A spawned team + a populated shared task list. If contracts exist but the
board isn't seeded, that's the orchestrator's job, not this loop's — seed one
task per contract-bounded unit of work, with
blockedByedges from the dependency graph, then loop.
Read caps (max passes, budget), state-file paths, and the seed source from
.claude/profile.yaml / the orchestrator's plan when present — don't invent tasks
the plan already defines. Substrate mechanics (states, blockedBy, TeammateTool,
peer messaging, the flag, known limitations) are in references/agent-teams.md.
Step 2 — Assign ready work, keep idle workers fed
One pass routes work; it does not write code:
TeammateIdle= "assign new work." When a teammate goes idle, pick the highest-prioritypendingtask whoseblockedByis fullycompletedand assign it. This feeder is what keeps the loop converging instead of stalling with idle workers and undrained tasks.- Teammates work in their own context windows and claim tasks
(
pending→in_progress). All coordination flows through the shared file + peer-message log — no central bottleneck. - Cap build/test parallelism per
loop-controllerStep 5. Fan out search and read-only verification freely, but don't let two teammates run the integrated build at once — concurrent builds destroy the backpressure signal.
The assignment algorithm in full (priority + unblocked-set selection) is in
references/hooks.md under the TeammateIdle feeder.
Step 3 — Gate each completed task (the inner loop hooks in here)
When a teammate marks a task completed, its TaskCompleted gate fires —
this is the proof's atom and the loop's backpressure edge:
- Pass → the task counts toward the proof.
- Fail (exit 2) → the task is rolled back to
pending/in_progresswith the failure surfaced as feedback. A task does not count until its gate is green. - Each task's gate can be driven by a
fix-until-greeninner loop. The teammate working a code task runsfix-until-greento drive its tests+lint+typecheck red→green; only then doesTaskCompletedpass and this outer loop counts it. The outer loop never reaches into a task's gate — it consumes the verdict. (This is the inner/outer composition; see "Under the orchestrator".) - The gate may be a deterministic script (mechanical bars) or an
agent-type/prompt hook — a fresh-context evaluator subagent with no
Write/Edit tools (
loop-controllerStep 2) for subjective bars; for build-until-spec tasks,contract-auditoris the natural evaluator. - QA-gated tasks consult the qe-agent's
qa-report.json(gate_decision.proceed, CRITICAL blockers, contract_conformance/security ≥ 3) — the same gate the orchestrator already uses. The loop informs, it does not decide.
Step 4 — Re-verify the whole board, checkpoint
After any task flips, re-scan the entire board against the §proof predicate —
not just the task that moved. "Restart the streak": a fix that completed task N
but a rollback that re-opened task M means the board is still undrained, and only a
full re-read catches it. On each task that passes its gate, commit a checkpoint
naming the task — the git trail is the loop's undo (git reset --hard to the last
green task + re-loop) and post-mortem. When the predicate holds for the whole
board, the loop is done; report the drained board as evidence.
Guardrails specific to this loop
Inherits the full stack from loop-controller → references/safety.md. The caps
this loop sets, mapped onto the team substrate:
- Hard iteration cap — a max number of supervision passes (read from
.claude/profile.yamlif set). Hitting it is stop-and-escalate, never "lower the gate." - Enforced token/cost budget — a ceiling that terminates, not just warns. Agent Teams uses 3–5×+ a single session's tokens; the documented 11-day / tens-of-thousands-of-dollars runaway had observability but no enforcement. Set per-teammate budget caps and a board-level ceiling.
- No-progress / oscillation = the orchestrator's 3-failure circuit breaker. If
board state (or a single task) is unchanged across 3 consecutive passes, or a
task ping-pongs
completed→gate-fail→pending≥3×, stop and escalate. This isloop-controller's guardrail and the orchestrator's existing breaker — one vocabulary, not a new one. Seeskills/orchestrator/references/circuit-breaker.md. - Checkpoint commit per task — commit working state each time a task passes its gate (Step 4).
- HITL for ambiguous/blocked tasks — pause and surface any task that's blocked with no path forward, ambiguous, or about to touch something irreversible (deploy, prod DB write, external API, force-push). Don't let the loop guess past a real block.
- Sandbox for any unattended run — workspace/worktree isolation so one teammate's failure can't contaminate the board or other teammates.
- Never weaken the gate. Forbidden, each a finding: editing the
TaskCompletedgate, the task list, orqa-report.jsonto mark a task done. A task that wentcompletedby relaxing its gate is not progress — read the diff that flipped it.
Promote to unattended/overnight ONLY when every loop in the build has all five
(safety.md Stage 3): a hard external verifier, an enforced token budget,
no-progress detection, checkpoint commits, and a sandbox. Until then, run
attended — and because Agent Teams is experimental, stay attended until the
loop completes consecutive clean runs (the staged-adoption ladder in
loop-controller/references/safety.md). Roll back to attended / kill when:
token spend grows non-linearly, board state is unchanged across passes, a
TaskCompleted gate and human judgment disagree, or a task is about to touch an
irreversible resource without a checkpoint.
Degradation (required, requires_agent_teams: true). When
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS is unset, this loop does not run — fall back
to the orchestrator's subagent path (Task/Agent fan-out with the wave gate driving
each wave red→green via fix-until-green) or sequential mode. The proof and the
discipline survive; only the shared-task-list substrate and the Teammate/Task hooks
are unavailable.
Choosing the driver primitive
Per loop-controller Step 1, this loop's primitive is the Stop/SubagentStop
hook + TeammateIdle hook combination: the Stop hook blocks the lead's exit while
the board predicate (§proof) is not yet DONE; TeammateIdle keeps workers fed so
the board actually converges between blocks. Secondary, for interactive runs: a
/goal-style condition over the whole board ("every task completed and passing
its TaskCompleted gate, or stop after N passes"). The hook scripts, the
stop_hook_active guard, and the auto-conversion-to-SubagentStop note are in
references/hooks.md.
Under the orchestrator (the wiring this unblocks)
loop-controller already notes the two-level structure — an inner loop per role,
an outer loop over the shared task list — and says "that orchestrator wiring is a
separate, later skill." This is that skill.
- Outer = this skill. Loops over the whole board (assignment + supervision):
drain every task to
completed+ gate-passing. - Inner =
fix-until-green. Drives one task's gate red→green (three exit codes, no cheating). They compose: a task'sTaskCompletedgate is satisfied by dispatchingfix-until-green; the outer loop consumes the verdict. qe-agentstill decides the build gate. Echoingfix-until-green: the loop informs, theqe-agent'sqa-report.json/gate_decisiondecides — a rollback or a circuit-breaker escalation is a real blocker, not a number to paper over.- Generalizes ralph-orchestrator: orchestrator decomposes → spawns workers →
loops the board;
validation.failedis a worker re-trigger. Here the orchestrator is the event router andTaskCompletedis thevalidation.passed/failededge, onto Agent Teams' native shared task list.
Reference files
references/agent-teams.md— the shared-task-list substrate: states (pending→in_progress→completed),blockedByedges, seeding one task per contract-bounded unit, teammates claiming/working in their own context windows, peer-message coordination, the drained-board predicate as a checkable routine, theCLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1flag, and the known-limitations caveats.references/hooks.md— the three hooks in full: theTaskCompletedgate (payload, exit-2 rollback, backing it withfix-until-greenorqa-report.json), theTeammateIdlefeeder (assignment algorithm), and theStop/SubagentStopre-trigger (script +stop_hook_activeguard + auto-conversion).