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:
claude—claude -p --dangerously-skip-permissions --output-format …, prompt on stdin.codex—codex exec --dangerously-bypass-approvals-and-sandbox -, prompt on stdin.cursor—cursor agent --print --force --trust --workspace … "<prompt>"orcursor-agent --print --force --trust --workspace … "<prompt>".gemini—gemini --approval-mode default --prompt "<prompt>".opencode—opencode 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.shis the runner); the spec-side helper tools it drives are Python 3.11+ run viauv. - 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, defaultclaude; also supportscodex,cursor/cursor-agent,gemini, andopencode). - Credentials / auth:
ghmust be authenticated for the PR checks; the loop is designed to run with no push/write credentials in the environment (it hard-deniesgit pushandghwrites). - Network: Reaches GitHub (via
gh) and the agent/model backend; the agent itself performs the model calls.
The pieces
| File | Role |
|---|---|
specs/ | The functional description of the product — one spec per area. The desired state the loop reconciles code against. |
IMPLEMENTATION_PLAN.md | Prioritised work items (the gaps). One work item = one branch = one PR. |
AGENTS.md | Loop-scoped operational rules (repo map, validation commands, branch + hard-limit rules). |
PROMPT_plan.md / PROMPT_build.md / PROMPT_update.md / PROMPT_consolidate.md | The per-beat prompts. |
loop.sh | The 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 rewritesIMPLEMENTATION_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 pushandgh pr createare in.claude/settings.jsonask— the human's step. Each beat ends at a local commit and prints the exact push +gh pr create --webcommands.
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, ortouch STOP(exits after the current iteration). SPEC_LOOP_BASE— branch to fork work items from. Defaults tomain; set it explicitly to build on top of a different branch.SPEC_LOOP_AGENT— supported headless agent CLI or wrapper to run (defaultclaude;codex,cursor,gemini, andopencodeare first-class harnesses).SPEC_LOOP_HARNESS— override the invocation convention when the CLI name does not imply it (claude,codex,cursor,gemini, oropencode).SPEC_LOOP_MODEL— model passed to the agent CLI. Defaults tosonnetfor Claude; Codex/Cursor/Gemini/OpenCode use their configured default unless this is set.SPEC_LOOP_EFFORT— reasoning-effort band:low,medium, orhigh. 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). Frameworkhighmaps to each CLI's top value (max/xhigh);low/mediumare not always pass-through (OpenCodelow→minimal). Exact values are in the effort table inspecs/spec-loop-runner.md.SPEC_LOOP_PR_LIMIT— number of open PRs to include in duplicate-work checks (default100).SPEC_LOOP_PLAN_MAX— plan line count that triggers one consolidation round before building (default500). 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.