A two-agent generator–reviewer build loop, packaged as a Claude Code skill
(/loop-engine). One orchestrator drives two disjoint subagents — a generator that builds
a software target and a reviewer that grades it — and iterates until the result converges to a
quantitative quality bar. The bar, the rubric, and how each criterion is graded all come
from a frozen, pre-authored domain contract selected as a subskill. The orchestrator
decides continue / restart / stop; it never writes the target code or grades it.
An agent build loop needs three things settled before it can converge: what to build,
what "good" means, and when to stop. loop-engine fixes the last two up front by
making the rubric a first-class, reusable artifact — a loop contract — instead of something
a planner agent re-derives every run. A contract carries:
- a set of criteria, each scored 0–10 with calibrated
anchor_0/anchor_10; - a grading mechanism per criterion —
code-read,cli-exec,http-probe,screenshot,playwright, and so on — so the reviewer proves each score the way the domain demands; - the stopping bar (
stopping_target_fitness,asymptote_band).
Because the contract is authored and validated before the run and frozen into the workspace, every run of a given domain is graded against the identical bar, and the loop has only two build-time roles instead of the usual planner + generator + evaluator.
| Role | Does | Never |
|---|---|---|
| Orchestrator | resolves/authors the contract; freezes it; decides continue/restart/stop; manages the restart handoff | codes; grades; edits the frozen contract |
| Generator | builds everything to maximize fitness against the contract | grades its own work |
| Reviewer | grades 0–10 using each criterion's declared mechanism; logs the vector + fitness; classifies the trajectory; signals restart | writes target code; changes the contract |
Keeping build and grade apart is the point: the moment one agent does both, it turns sycophantic and the loop converges on slop.
Every criterion is scored on an empirical 0–10 scale. For a result with N criteria
scored X = (x₁, …, x_N):
fitness(X) = √(Σ xᵢ²) / (10·√N) = √( (1/N) · Σ (xᵢ/10)² )
This is the L2 norm of the score vector normalized by the all-tens vector — equivalently the
root-mean-square of the normalized scores. It is 1.0 when every criterion is a 10 and
0.0 when all are 0. RMS deliberately rewards peak excellence over uniform mediocrity in early
cycles, while the high convergence target (default 0.95) still forces near-perfect scores
across essentially every criterion. A per-category floor (category_floor, default 3) keeps
any category from being neglected. The math is implemented and enforced in scripts/fitness.py
— compute with its CLI, never by hand.
A session is a sequence of loops; a loop is one initialization plus its cycles; a cycle is one build → grade → classify iteration. There is no plan step and no negotiation step — the contract is already the specification.
resolve + validate contract → freeze into .loop-engine/<slug>/
└─ per loop: ┌─ generator (build) → reviewer (grade per mechanism) → classify ─┐
└──────────────── orchestrator: continue / restart / stop ─────────┘
The reviewer computes fitness and classifies the run's fitness trajectory, which drives the restart decision:
| Trajectory | Action | Meaning |
|---|---|---|
converged |
stop | at/above target, or a settled in-band plateau |
converging / plateau |
continue | still climbing or with headroom |
cyclic / diverging / strange / stuck_low |
restart | this loop's inputs can't reach target |
Because the contract is frozen, a restart changes only the enriched logs and small generator-prompt tweaks — never the rubric. If the loop reveals the contract itself is wrong (an unsatisfiable criterion, a coverage gap, a mechanism the environment can't run), that is a human escalation: fix the contract subskill and start a new run.
/loop-engine --<domain>[-loop-contract] [target…] run the loop using that contract
/loop-engine --<domain> --<domain2> [...] [target…] multi-contract run: merge those contracts, run one loop
/loop-engine --new-contract <specification> author a NEW contract, validate, register
/loop-engine --list list registered contracts and multicontracts
/loop-engine <freeform target description> auto-select a contract; ask if ambiguous
/loop-engine (bare) | --help help screen
--supervised composes with any run form (human gate after every cycle).
Each contract is a directory contracts/<domain>-loop-contract/ holding a contract.json.
Eight ship out of the box:
| Contract | Grades by | Weighted toward |
|---|---|---|
web-spa-loop-contract |
playwright + code-read |
functionality / design |
cli-tool-loop-contract |
cli-exec + code-read + unit-test |
functionality / robustness |
rest-api-loop-contract |
http-probe + unit-test + code-read |
functionality / scalability |
static-site-loop-contract |
screenshot + playwright + code-read |
design / craft |
seo-geo-loop-contract |
http-probe + code-read + playwright |
machine-access / content |
software-design-loop-contract |
code-read + unit-test + static-analysis |
correctness / system-fit |
website-marketing-optimization-loop-contract |
playwright + code-read + screenshot + http-probe |
messaging / funnel |
website-design-optimization-loop-contract |
screenshot + playwright + code-read |
typography / space-layout / mobile-adaptive / accessibility |
Add your own with /loop-engine --new-contract <spec>. Every contract — shipped or authored —
must satisfy the canonical template contracts/CONTRACT.schema.json, enforced by
scripts/contracts.py. A contract that does not validate cannot start a run.
Any set of registered contracts can be optimized together in one run: stack their flags
(/loop-engine --website-design-optimization --seo-geo <target>) and the orchestrator merges
their rubrics into ONE schema-valid contract — criterion ids and categories namespaced by
member domain, priorities round-robin interleaved so all members improve together, the
strictest stopping bar kept — validates it, and freezes it like any other contract. Because
the merged artifact is itself a valid contract, the loop downstream is byte-for-byte a normal
run. The merge is deterministic and lives in the same enforcer:
python scripts/contracts.py merge <name-or-domain-or-path>... [--name X] [--out FILE]A multicontract is a registered preset combination — a directory
contracts/<name>-multicontract/ holding a multicontract.json that names ≥ 2 member
contracts (template: contracts/MULTICONTRACT.schema.json). It dispatches exactly like any
contract subskill and expands to its members at merge time. One ships out of the box:
| Multicontract | Dispatch | Members |
|---|---|---|
website-optimization-multicontract |
--website-optimization |
website-marketing-optimization + website-design-optimization + seo-geo |
--website-optimization drives a website to best-in-class on marketing/conversion, visual and
interaction design, and SEO/GEO simultaneously. Full merge rules, the orchestrator's
semantic-dedupe pass, and preset authoring: references/multi-contract.md.
loop-engine/
├── SKILL.md orchestrator + dispatcher (the skill entry point)
├── README.md this file
├── pyproject.toml package metadata (standard library only; no runtime deps)
├── references/
│ ├── fitness-and-convergence.md fitness formula, convergence/stopping, trajectory heuristics
│ ├── roles-and-restart.md two-role contract, mechanism-directed grading, restart under a frozen contract
│ ├── state-and-run.md workspace + state schema, dispatch grammar, contract resolution, intake
│ ├── multi-contract.md multi-contract runs: merge rules, semantic dedupe, preset multicontracts
│ ├── contract-authoring.md how --new-contract writes a template-compliant contract (+ multicontracts)
│ └── prompts/ generator / reviewer system-prompt templates
├── scripts/
│ ├── fitness.py deterministic fitness + trajectory classifier (+ CLI)
│ ├── contracts.py load / list / validate / merge contracts + multicontracts (the template enforcer)
│ └── test_fitness.py, test_contracts.py pytest suites
├── contracts/
│ ├── CONTRACT.schema.json the canonical template every contract must satisfy
│ ├── MULTICONTRACT.schema.json the template every preset multicontract must satisfy
│ ├── <domain>-loop-contract/contract.json the shipped domain contracts (subskills)
│ └── <name>-multicontract/multicontract.json the shipped preset combinations
└── assets/templates/ fill-in run-state files (progress, log, evals, restart-brief)
The repository is the skill (SKILL.md lives at its root). Make it available as
/loop-engine by linking — or copying — it into the skills/ folder of your Claude Code
configuration directory (the dot-prefixed claude folder in your home directory, or
$CLAUDE_CONFIG_DIR where set), under the name loop-engine:
# run from inside the repo; <config-dir> is your Claude Code configuration directory
ln -s "$(pwd)" <config-dir>/skills/loop-engine
# or copy it: cp -r . <config-dir>/skills/loop-enginePaths inside SKILL.md (e.g. scripts/fitness.py) are relative to the skill directory, so
linking the repo root is all that's needed. Then invoke /loop-engine and either name a
contract (--web-spa) or describe the target and let it pick.
If you're an AI agent that just read this repo online, here's the whole path from zero to a running loop.
1 — Load it as a skill. loop-engine is a Claude Code skill, not a library: you invoke it
with /loop-engine, you don't import it. Link the repo into the skills directory (see
Install above for the location and the copy alternative):
ln -s "$(pwd)" <config-dir>/skills/loop-engine # from the repo root2 — Verify it's healthy (pure standard library, no install step):
python -m pytest # suites green
python scripts/contracts.py list # shipped contracts, all "valid": true3 — Use it properly.
- Choose the bar first. Name a contract by domain (
/loop-engine --rest-api <target>), stack several to optimize them together (--website-design-optimization --seo-geo), use a preset multicontract (--website-optimization), let it auto-select from a freeform description, or author a domain with/loop-engine --new-contract <spec>.--listshows what's available. - Let the orchestrator run the loop. Don't act as the generator or reviewer yourself — the design depends on those being separate agents. You pick the target and contract; the skill drives build → grade → classify → continue/restart/stop.
- The environment must support the contract's harness. A
playwrightcontract needs a browser;http-probe/unit-testneed the server or suite to come up. A mechanism that can't run is a sufficiency-gate failure to surface, not something to grade around. - Contracts freeze at run start. If one turns out wrong (an unsatisfiable criterion, a coverage gap), fix the contract subskill and start a new run — never hand-edit a frozen contract mid-loop.
fitness.py and contracts.py are pure standard library — no third-party dependencies. Run
the suites:
python -m pytestUse the CLIs directly:
python scripts/fitness.py score --vector 8,9,7,10
python scripts/fitness.py classify --evals path/to/evals.jsonl --target 0.95
python scripts/contracts.py validate contracts/web-spa-loop-contract/contract.json
python scripts/contracts.py list
python scripts/contracts.py merge website-optimization --out merged.json # preset expands to its members
python scripts/contracts.py validate merged.jsonImplements the "write the loop, not the prompt" pattern — generator/reviewer separation, state on disk, a deletable harness, a quantitative fitness function, and a trajectory-based restart rule. Its one distinguishing move is replacing the run-time planner and rubric negotiation with a frozen, validated contract that also declares how it is graded, so the rubric becomes a reusable, testable artifact rather than something re-derived each run.