Skip to content

Repository files navigation

Build-Loop Kit — a portable autonomous build loop

A generic, reusable extraction of the autonomous build loop used to build this project. Drop it into any project to get a self-verifying, anti-gaming loop that builds a backlog one feature at a time, proves each works before marking it done, and resumes cleanly across sessions. It runs on Claude Code or OpenCode (the enforcement engine is tool-agnostic bash+node; only the two agent prompts are runner-shaped — pick + set up your runner in SETUP.md); your app can be any stack.

What problem it solves: "vibe-coding" agents confidently mark broken work as done and degrade as context fills. This loop fixes both: fresh context every iteration, generation separated from evaluation, state on disk, and evidence-gated marking (a feature can't be marked green unless it re-proves green). Based on the 2026 long-running-harness / "Ralph loop" patterns.

The model — three roles, one feature at a time

ORCHESTRATOR (main session)         IMPLEMENTER (mod-<module>)        VERIFIER (integration-verifier)
  picks the next feature       →      builds ONE feature only     →     judges it (never edits code)
  marks / commits / records    ←      returns a ≤2k summary       ←     PASS/FAIL + evidence

Separating the builder from the judge is the key lever: a model grading its own work is biased to approve it; a separate, skeptical evaluator is an honest gate.

The wakeup cycle (one iteration — fresh context)

WAKE (fresh context: read progress log + backlog + git log)
 1–3 startup + smoke (init.sh)
 4   pick ONE        → next-feature.sh / eligible-wave.mjs  (SPINE-FIRST, then priority → dep-depth → unblocks → id)
 4.5 reuse-first     → grep existing code + module doc + <module>-LEARNED.md + ENGINEERING_PRACTICES + code-quality + senior-review + (spine/seam/schema rules)
 5   implement       → mod-<module> agent builds to the code-quality + senior bar (real wiring at seams)
 6   verify          → integration-verifier: tamper-check FIRST + rules verify + held_out + e2e + adversarial refute (panel if high-risk) + SENIOR + spine/seam/wiring checks
 6.6 quality+security → code-quality + security + senior lens (recorded) → record-lesson.sh into <module>-LEARNED.md
 7   record          → changelog.sh add (verdict: verify:pass + SENIOR + lessons) + progress line + module doc → mark.sh pass (re-runs verify; REQUIRES the entry — exit 7 without it)
 7.5 wave-gate      → wave-gate.sh BEFORE commit: verify.sh --quick + corruption guard + SPINE boot + writes a tree-matched RECEIPT; never git add -A
 8   regression      → regression-due.sh → full sweep when DUE (enforced); cutover-debt.sh → per-wave mock-debt visibility
 8.5 live-sweep      → next-live.sh → mark.sh <id> live for runnable provisional→live cutovers
 9   stop-gate       → at NONE: loop-stop-gate.sh (cutover · coverage · spine · seams · wiring · docs-produced · receipt · full gate · e2e) must be GREEN
SLEEP → next wake starts fresh

Scoped runs. Set LOOP_ONLY=<id-prefix> to build just ONE initiative (e.g. LOOP_ONLY=DASH- runs only the dashboard features), or LOOP_EXCLUDE_MODULES=<csv> to skip modules an autonomous loop shouldn't touch (e.g. plan-mode-gated money/state modules a human builds attended). Both restrict only what is OFFERED — dependency resolution and the DAG are unchanged — and both arms (build + live cutover) honor them identically.

Durable state (survives any context reset) — 5 layers

What passes between fresh iterations. Canonical read-order: CONTEXT.md.template.

  • WHAT to buildfeatures.json (backlog + optional per-feature implementation/tests/ docs_update guidance + cutover fields). Loop edits only passes/retries/live_passes; the rest is immutable, enforced by mark.sh. Schema: backlog/features.schema.json.
  • WHAT was doneprogress.md (append-only prose: id · result · evidence · reused-what · Q/S verdict · next) + git history + changelog.jsonl/CHANGELOG.md (structured per-feature: files touched · what · verify · SENIOR · lessons · commit — recovers the per-feature file attribution that bundled wave commits lose, and is queryable: changelog.sh query --file <p>|--module <m>|--id <feat>).
  • HOW we buildCLAUDE.md/AGENTS (+@import chain: memory index, rules, ENGINEERING_PRACTICES.md), auto-loaded.
  • HOW each module worksdocs/modules/*.md + each module's CLAUDE.md.
  • WHY + lessons — ADRs + history, and ENGINEERING_PRACTICES.md §"Lessons & gotchas" (negative knowledge — what already bit us). The layer is managed (audit-docs.sh enforces the floor).
  • HOW agents improve — per-module .claude/agents/learned/<module>-LEARNED.md: a living, append-only memory each fresh mod-<module> agent reads FIRST, written by the implementer (pitfalls) and the verifier (findings) via record-lesson.sh. The mod-*.md template stays role-stable; this file grows, so agents get better over iterations instead of rediscovering the same gotchas. (audit-docs.sh floor.)

Anti-gaming guarantees (why "done" means something)

  1. Evidence-gated mark (+ anti-vacuous)mark.sh pass re-runs the feature's own verify and flips passes only on exit 0 AND only if it actually ran — a verify that exits 0 but is all-skipped / collected 0 tests is REFUSED (a skipped or empty test is not evidence). audit-verifies.sh statically flags unconditional-skip smells. (See LEARNINGS.md §1.)
  2. Immutable contract, free guidancemark.sh snapshots title/behavior/steps/verify/deps/module/priority and the cutover/spine fields (requires_live_wiring/live_verify/spine/cross_schema_of/produces/ consumes, so a feature can't dodge the provisional/spine/schema gate by flipping them) and refuses changes; a verify may be strengthened, never weakened, via a tracked feature. The optional implementation/tests/docs_update guidance is NOT frozen (so it can be refined mid-build).
  3. Independent + adversarial verify — a separate fresh-context agent (never the author) tries to refute every feature; risk:"high" triggers a 2-of-3 refutation panel DETERMINISTICALLY (a field, not prose inference). The verifier tamper-checks the diff (weakened/skipped verify, hardcoded test inputs, __eq__ overrides, mock-without-live) and may run a withheld held_out_verify (composition the implementer never saw) to defeat validation-saturation.
  4. No weak verifyaudit-verifies.sh flags existence-only checks on code modules, mock-only-without- live-wiring, DB-faked schema tests, AND a live_verify that can false-green (curl without -f).
  5. Stop-gate — the run may only end when EVERY gate is green (cutover · coverage · spine · seams · wiring · docs-produced · wave receipt · full gate · e2e), not when the agent thinks it's done.
  6. No cutover debt (mock-only ≠ done) — a feature whose unit verify mocks a boundary sets requires_live_wiring:true + a live_verify; it stays provisional until mark.sh <id> live re-proves the REAL wiring. loop-stop-gate.sh blocks the run from ending while any feature is passes && requires_live_wiring && !live_passes. audit-verifies.sh detects mock-only verifies (JS/TS and Python); cutover-debt.sh surfaces the debt EVERY wave (not just at the stop-gate) so it can't silently accumulate; the verifier must hit the live path and FAIL on a 404. (See EVOLUTION-FROM-LIFEOS.md.)
  7. Senior bar + self-improving agents — "done" requires clearing a senior code-review lens (rules/senior-review.md: SENIOR ≥ 3.5/5, no dimension ≤ 2), not just a green test. Every pitfall or verifier finding is written back via record-lesson.sh into the owning module's <module>-LEARNED.md, which the next agent in that module is required to read — so the same class of bug can't recur and the agents get better each wave. audit-docs.sh enforces every module carries a LEARNED file.
  8. The system must COMPOSE, not just unit-pass (integration spine) — defeats "294 features green, app won't start." The walking skeleton (composition root, boot, one e2e happy-path, e2e harnesses) is tagged spine:true, must be requires_live_wiring:true with a real boot/e2e live_verify (never unit-only), and is built FIRST. audit-spine.sh FAILs a spine feature with no live_verify; wave-gate.sh re-runs every spine live_verify each wave so a wiring regression can't be committed; the stop-gate blocks while any spine feature is not live_passes. Cross-tier contracts are SEAMS — producer + consumer must AGREE (seams.json + audit-seams.sh catch the ticks.<symbol> vs ticks class). (See LEARNINGS.md §9.)
  9. Reward-hacking blocked + no vacuous gatesmark.sh pass REFUSES (exit 6) a diff that weakens an already-passed feature's test (the #1 documented hack); audit-spine.sh FAILs a backlog with no spine (a guarantee bypassable by omission is none); an impossible feature is recorded mark.sh <id> blocked (surfaced for a spec decision) rather than force-greened; parallel waves are race-safe (a backlog write-lock). The two pickers share one selector so build order is reproducible. (See LEARNINGS.md §10–13.)
  10. Documentation is HARD-GATED, not honor-system — a feature can't go green without its docs: mark.sh pass REFUSES (exit 7) unless a changelog.jsonl entry records the verdict (verify:pass + SENIOR ≥ 3.5); audit-docs-produced.sh re-checks that + a progress.md line + lessons + (when flagged) an ADR / ENGINEERING_PRACTICES.md update every wave; audit-docs.sh FAILs a module doc gone stale vs its source.
  11. Wiring enforced beyond the compiler (4 layers) — type checker (signatures) · spine (main path) · seams (audit-seams.sh: producer/consumer agree, produces/consumesseams.json, orphan/dangling/drift) · wiring invariants (audit-wiring.sh: every defined unit is REGISTERED in its consumer — the orphan-handler class the type checker is blind to). The verifier flags a registry-bypassing verify. (rules/wiring-contracts.md)
  12. The gates can't be skipped (meta-gates) — the verifier verdict is required at mark-time (10); wave-gate.sh writes a tree-matched receipt that loop-stop-gate.sh (step 0.9) requires, and the optional Claude Code hooks (hooks/) block a git commit without a fresh receipt + run the stop-gate on done. So "I ran the gates" is proven, not claimed (OpenCode: the receipt + verdict enforce it without hooks).

Kit contents

loop-kit/
  README.md                     ← this file (architecture + mechanism + extraction audit)
  install.sh                    ← one-command install / --update (overwrites machinery, never clobbers your edits)
  SETUP.md                      ← choose your runner (Claude Code / OpenCode) + wire the agent layer — START HERE
  INSTALL.md                    ← step-by-step: the runner-agnostic mechanics (backlog · gates · drive the loop)
  MIGRATION.md                  ← upgrading an OLDER loop-kit setup: exact changes + what newly fails + opt-outs
  EVOLUTION-FROM-LIFEOS.md      ← the spec for the richer-metadata + cutover-closure upgrades (round history)
  CONTEXT.md.template           ← the canonical per-iteration read-order (5-layer knowledge manifest)
  loop-iteration.md             ← the generic per-iteration contract
  AUTHORING.md                  ← how to write the backlog + sequence the build (the author's responsibilities)
  SCAFFOLDING.md                ← how to set up the repo BEFORE the loop (walking skeleton, injected ports, guardrails)
  LEARNINGS.md                  ← hard-won failure modes + the mechanism that now defends each (READ THIS)
  rules/code-quality.md         ← the quality bar (reuse/cleanup/security/tests/docs)
  rules/security.md             ← the security bar (no secrets, sanitize input, parameterized queries, least privilege)
  rules/senior-review.md        ← the senior code-review lens (8-dim rubric; the verifier scores it)
  rules/test-isolation.md       ← autouse-reset pattern so the full suite is deterministic
  rules/schema-parity.md        ← no mock-only verify for cross-schema reads/writes (read the real schema)
  rules/integration-spine.md    ← the walking skeleton: the system must boot+compose end-to-end, live
  rules/seam-contracts.md       ← seam = contract parity (producer/consumer agree on a key; token == seam name)
  rules/wiring-contracts.md     ← wiring = registration completeness (every defined unit is registered) + the 4 wiring layers
  agents/implementer.md         ← generic mod-<module> template (+ metadata + LEARNED-file guidance)
  agents/integration-verifier.md← generic verifier (separate evaluator, adversarial + panel + mock-detect + SENIOR lens)
  agents/learned/MODULE-LEARNED.md.template ← per-module living-lessons file (agent self-improvement)
  hooks/                        ← optional Claude Code hooks: precommit (wave-gate receipt) + Stop (loop-stop-gate)
  opencode/opencode.json        ← OpenCode config template (instructions globs + permissions + agent block)
  opencode/agents/*.md          ← OpenCode-format agent templates (mode:subagent + permission map)
  backlog/features.schema.json  ← backlog JSON schema (incl. metadata, live-wiring, spine, risk, held_out fields)
  backlog/features.example.json ← a tiny working example backlog
  backlog/coverage-map.example.json ← seed for audit-coverage (anchors a backlog must cover)
  backlog/seams.example.json    ← seed for audit-seams (cross-tier contracts; token == seam name)
  backlog/wiring.example.json   ← seed for audit-wiring (registry/dispatch invariants: producer_glob → consumer)
  scripts/
    eligible-core.mjs           ← the SINGLE BUILD-arm selector (filter + spine→priority→depth→unblocks→id sort)
    next-feature.sh             ← thin wrapper over eligible-core (one id) — generic
    eligible-wave.mjs           ← thin wrapper over eligible-core (one feature per module, conflict-free wave)
    next-live.sh / eligible-live.mjs ← LIVE arm: next/all provisional→live cutovers runnable now
    mark.sh                     ← evidence-gated marker — pass|live|fail|blocked|reset (REFUSES vacuous + test-tamper + no-verdict; backlog-locked)
    wave-gate.sh                ← per-wave gate: verify.sh --quick + regression-due(auto-mark) + corruption guard + SPINE boot + writes a receipt
    audit-spine.sh              ← integration-spine gate (spine features must be live-wired; system must compose)
    audit-seams.sh              ← seam contract parity (produces/consumes ↔ seams.json; orphan/dangling/drift; no-op if none)
    audit-wiring.sh             ← wiring invariants: every defined unit is registered in its consumer (no-op if no wiring.json)
    audit-docs-produced.sh      ← per passed feature: changelog entry + verdict (verify:pass+SENIOR) + progress + lessons + flagged ADR/pattern
    regression-due.sh           ← enforced regression cadence (generic)
    merge-backlog.mjs           ← PRE-LOOP backlog validation (unique ids · deps resolve · DAG acyclic · mod-agent exists)
    audit-coverage.sh           ← prove the backlog covers the whole build (coverage-map anchors); no-op if no map
    loop-stop-gate.sh           ← exit gate — cutover + coverage + spine + seams + wiring + docs-produced + receipt + full gate + e2e
    cutover-debt.sh             ← mock-debt register; BLOCKING vs live_human_gated DEFERRED + external-input surfacing
    record-lesson.sh            ← append a deduped lesson to a module's LEARNED file (agent self-improvement)
    changelog.sh                ← per-feature code-evolution log (add|render|link|query) → changelog.jsonl + CHANGELOG.md
    audit-verifies.sh           ← verify-rigor auditor: existence-only + mock-only + DB-fake + unconditional-skip + weak live_verify
    audit-docs.sh               ← knowledge-layer auditor (CLAUDE.md + LEARNED + mod-agent per backlog module; @imports)
    loop-status.sh              ← one-line status incl. provisional (live-pending) debt
    init.sh.template            ← real proven-infra smoke (fill <YOUR ...> markers)
    verify.sh.template          ← your project's rules gate (lint+typecheck+unit = req fast slice; --quick lane)

Generic scripts read the backlog via LOOP_BACKLOG (default docs/build/features.json).

Extraction audit — where each kit file came from + what was genericized

Each kit file was extracted from a real running build-loop project, with all domain specifics replaced by <PLACEHOLDERS> / EDIT markers so the kit is project-agnostic. (The war-story examples in LEARNINGS.md and the rules are kept concrete for teaching value but are illustrations, not requirements.)

Kit file Source file Genericized
scripts/next-feature.sh scripts/next-feature.sh backlog path → LOOP_BACKLOG env (logic identical)
scripts/mark.sh scripts/mark.sh backlog path → env (evidence-gating + immutability identical)
scripts/regression-due.sh scripts/regression-due.sh backlog path → env; state file beside backlog
scripts/loop-stop-gate.sh scripts/loop-stop-gate.sh verify.sh + playwright → EDIT markers
scripts/audit-verifies.sh scripts/audit-verifies.sh code-module list + behavioral regex → EDIT (added pytest/go/cargo)
scripts/verify.sh.template scripts/verify.sh docker/postgres/npm checks → <YOUR CMD> placeholders (kept the per-check log + req/opt harness)
loop-iteration.md scripts/loop-iteration.md master.md / Gmail / domain rules → <DOMAIN INVARIANTS — EDIT>
rules/code-quality.md .claude/rules/code-quality.md removed job-search references
agents/implementer.md .claude/agents/mod-*.md (common shape) module/paths/guardrails → placeholders
agents/integration-verifier.md .claude/agents/integration-verifier.md stack-agnostic wording
backlog/features.schema.json docs/build/features.json meta.schema + field usage formalized as JSON Schema

Not extracted (project-specific, rebuild per project): scripts/init.sh (smoke — your stack's containers/deps), the concrete verify.sh checks, the per-module implementer files, the actual backlog.

About

A portable autonomous build loop: fresh context per iteration, builder separated from judge, and evidence-gated marking so a feature can't go green unless it re-proves green. Runs on Claude Code or OpenCode; your app can be any stack.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages