spec-loop

September 13, 2026 · View on GitHub

Capability: substrate:framework-dev

Harness: Claude Code, Codex, Cursor, Gemini CLI, OpenCode, Kiro

A spec-driven build loop for this framework, in the general Ralph style (run a fresh agent context against a fixed prompt, repeat), adapted to the framework's human-in-the-loop posture. The full write-up is in docs/spec-driven-development.md; this is the operator quickstart.

The loop drives a headless agent CLI and is not tied to one harness. SPEC_LOOP_AGENT picks the CLI (default claude) and SPEC_LOOP_HARNESS picks its run convention:

  • claudeclaude -p --dangerously-skip-permissions --output-format …, prompt on stdin.
  • codexcodex exec --dangerously-bypass-approvals-and-sandbox -, prompt on stdin.
  • cursorcursor agent --print --force --trust --workspace … "<prompt>" or cursor-agent --print --force --trust --workspace … "<prompt>".
  • geminigemini --approval-mode default --prompt "<prompt>".
  • opencodeopencode run --auto --model … "<prompt>", prompt as a positional argument.

SPEC_LOOP_HARNESS defaults from the agent basename, so SPEC_LOOP_AGENT=codex, SPEC_LOOP_AGENT=cursor-agent, SPEC_LOOP_AGENT=gemini, or SPEC_LOOP_AGENT=opencode is usually all that is needed:

SPEC_LOOP_AGENT=codex tools/spec-loop/loop.sh build 5
SPEC_LOOP_AGENT=cursor-agent tools/spec-loop/loop.sh build 5
SPEC_LOOP_AGENT=opencode SPEC_LOOP_MODEL=anthropic/claude-sonnet-4-5 \
  tools/spec-loop/loop.sh build 5

Gemini CLI: the launcher uses --approval-mode default instead of --yolo. With the shipped profile, calls requiring confirmation are refused headlessly, so unattended build iterations that need edits cannot complete. See the Gemini runner notes for supported use and upgrade impact.

All conventions run the agent non-interactively. The profiles other than Gemini request automatic approval; their safety rails (never push, never open a PR) come from the OS sandbox, missing push/write credentials, repo hooks, and the loop's own guards. Claude also gets per-invocation --disallowedTools hard-deny flags; the other harnesses rely on their own policy/config plus the external sandbox. See the SECURITY notes in loop.sh.

Prerequisites

  • Runtime: Bash + coreutils (loop.sh is the runner); the spec-side helper tools it drives are Python 3.11+ run via uv.
  • CLIs: git (required — must run inside a git checkout), gh (for open-PR duplicate-work checks), and one supported headless agent CLI (SPEC_LOOP_AGENT, default claude; also supports codex, cursor / cursor-agent, gemini, and opencode).
  • Credentials / auth: gh must be authenticated for the PR checks; the loop is designed to run with no push/write credentials in the environment (it hard-denies git push and gh writes).
  • Network: Reaches GitHub (via gh) and the agent/model backend; the agent itself performs the model calls.

The pieces

FileRole
specs/The functional description of the product — one spec per area. The desired state the loop reconciles code against.
IMPLEMENTATION_PLAN.mdPrioritised work items (the gaps). One work item = one branch = one PR.
AGENTS.mdLoop-scoped operational rules (repo map, validation commands, branch + hard-limit rules).
PROMPT_plan.md / PROMPT_build.md / PROMPT_update.md / PROMPT_consolidate.mdThe per-beat prompts.
loop.shThe runner.
../spec-inventory/Deterministic compact inventory helper appended to prompts as a routing aid.

Modes

./tools/spec-loop/loop.sh              # build, unlimited iterations
./tools/spec-loop/loop.sh 10           # build, max 10 iterations
./tools/spec-loop/loop.sh plan         # gap-analysis → rewrite the plan (no code changes; 1 pass, add N for more)
./tools/spec-loop/loop.sh update       # back-fill specs from functionality others contributed (1 pass, add N for more)
./tools/spec-loop/loop.sh consolidate  # shrink the plan when it grows too long (1 pass, add N for more)
  • plan — compares specs/ against the code and rewrites IMPLEMENTATION_PLAN.md. Plans only; no commits. It also checks open PRs and does not add work items that are already in flight.
  • build — implements the single highest-priority work item on its own <slug> branch, validates, and commits there. If the top plan item is already covered by an open PR, it skips to the next uncovered item.
  • update — the inverse of plan: scans the code for functionality not yet described by a spec (someone contributed it the normal way) and brings the specs back in sync, on a sync-specs-<timestamp> branch.
  • consolidate — shrinks the plan without losing planned work (build auto-switches to this when the plan grows past ~500 lines).

The two non-negotiables

  • A branch per work item. Build/update never commit to the integration base; each carves out its own branch, so every change is one reviewable, revertible PR.
  • Never pushes, never opens a PR. git push and gh pr create are in .claude/settings.json ask — the human's step. Each beat ends at a local commit and prints the exact push + gh pr create --web commands.

Security

The loop runs the agent with --dangerously-skip-permissions, so it must be launched inside the project's sandbox harness, with no push/write credentials in the environment. The flag bypasses the agent permission layer (.claude/settings.json deny/ask) but not the OS sandbox (clean-env + filesystem/network), which stays the real boundary; for Claude, as defence in depth, the loop also hard-denies git push and gh via --disallowedTools. Full rationale: docs/spec-driven-development.md § Security and unattended agent loops.

Stop / configure

  • Stop: Ctrl+C, or touch STOP (exits after the current iteration).
  • SPEC_LOOP_BASE — branch to fork work items from. Defaults to main; set it explicitly to build on top of a different branch.
  • SPEC_LOOP_AGENT — supported headless agent CLI or wrapper to run (default claude; codex, cursor, gemini, and opencode are first-class harnesses).
  • SPEC_LOOP_HARNESS — override the invocation convention when the CLI name does not imply it (claude, codex, cursor, gemini, or opencode).
  • SPEC_LOOP_MODEL — model passed to the agent CLI. Defaults to sonnet for Claude; Codex/Cursor/Gemini/OpenCode use their configured default unless this is set.
  • SPEC_LOOP_EFFORT — reasoning-effort band: low, medium, or high. Unset means no effort flag is passed. Mapped per harness (Claude/Kiro --effort, Codex -c model_reasoning_effort=…, OpenCode --variant; Cursor/Gemini have no knob and silently omit). Framework high maps to each CLI's top value (max / xhigh); low/medium are not always pass-through (OpenCode lowminimal). Exact values are in the effort table in specs/spec-loop-runner.md.
  • SPEC_LOOP_PR_LIMIT — number of open PRs to include in duplicate-work checks (default 100).
  • SPEC_LOOP_PLAN_MAX — plan line count that triggers one consolidation round before building (default 500). The consolidate beat targets ~300 lines (hysteresis) and runs at most once until the plan drops back under the limit, so a plan that is long because of pending work never re-consolidates in a loop.

Not the RFCs

The specs are the functional description of the code. The docs/rfcs/ are the separate normative governance layer — the loop respects them as constraints and never reads or edits them.