back
loading skill details...
Design the engineered loop for a medium/large (semi-)autonomous AI-coding task by decomposing it into gated sub-loops, emitted as a runnable .loop/ runbook with measured-not-guessed parameters. Use-when: "design an agent loop", "set up an autonomous / self-running agent workflow", "$loop-constructor". It DESIGNS the loop; it does NOT execute it.
---
name: loop-constructor
description: >-
Design the engineered loop for a medium/large (semi-)autonomous AI-coding task by
decomposing it into gated sub-loops, emitted as a runnable .loop/ runbook with
measured-not-guessed parameters. Use-when: "design an agent loop", "set up an
autonomous / self-running agent workflow", "$loop-constructor". It DESIGNS
the loop; it does NOT execute it.
metadata:
version: 0.5.0
---
# loop-constructor
Design the engineered loop for a medium/large (semi-)autonomous coding task by
running an **explicit selection procedure** that decomposes it into a **tree of
gated sub-loops**, then emit a **machine-checkable loop-design JSON** (which
`scripts/lint_loop_design.mjs` PASSes) plus a **decision log**, and persist it as
a runnable runbook under the project's `.loop/`.
**The anchor (read first):** a loop closes autonomously only when a fast,
machine-runnable check can answer "is it done?". So design **backward from the
check** — `principle.closed_loop_needs_a_check`. *No runnable check ⇒ it is not a
loop; the linter rejects it.*
This skill is the applied front-end over the **loop-principle KB**. It does not
restate theory — it RETRIEVES from the KB and REUSES its templates/checklists by
path. Resolve the KB path once (see "Grounding"), then cite node ids for design
judgments instead of asserting from memory.
## The mechanism: SELECT → NEGOTIATE → FILL → VERIFY → PERSIST
Five phases. SELECT derives the loop's *shape* from an ordered decision procedure.
NEGOTIATE turns that shape into a loop that won't converge on slop — separate the
roles, agree the contract (the LOOPS.md operating model, `references/loops-model.md`).
FILL writes the machine-checkable JSON; VERIFY + PERSIST gate and emit it. The whole
point — **write the loop, not the prompt** — every hard call becomes an ordered,
reviewable derivation, not judgment-by-vibes.
### 1. SELECT — run the decision procedure (`references/loop-selection.md`)
Answer **D0–D6 in order** (D7 closes after NEGOTIATE); each answer shapes the
loop and is recorded with a one-line justification (the **decision log**). The ordered
decisions: **D0** is-it-a-loop (name the runnable "done?" check or route away) ·
**D1** decompose (seam test → flat vs staged) · **D2** per-stage pattern + check
(+ `falsifiable_when`/`passing_but_wrong`) · **D3** autonomy (`in_the_loop` vs
`on_the_loop`) · **D4** parallelism (`large` fan-out vs `medium` sequential) ·
**D5** guards (caps + `on_failure` + risk guards) · **D6** iteration profile /
cadence (completeness-first vs iteration-first, a *dial* that re-tunes D2/D3/D5) ·
**D7** number provenance (closing sweep: each digit-bearing string classed
decision | definitional | empirical; empirical ⇒ `derived`, never hand-fixed).
Load `references/loop-selection.md` and run the full procedure — each D-item there
is the operational decision rule. Record the answers as the `selection_log` array.
### 2. NEGOTIATE — separate the roles + agree the contract (`references/loops-model.md`)
Two moves from the LOOPS.md operating model, both **linter-enforced for staged**:
- **Separate the roles (§II).** `roles.{planner,generator,evaluator}` = three
contexts: planner→spec+contract (no code), generator→writes all (never grades
itself), evaluator→*fresh, adversarial* (`separate_context:true`,`adversarial:true`),
told the artifact is broken and to prove it. Self-grading breeds sycophancy — the
#1 loop failure.
- **Negotiate the contract (§III).** Generator proposes "done", evaluator pushes back
→ agreed **testable assertions** (`contract.assertions[]`, each gradable +
stage-traced). **The contract, not the spec, is graded.** The per-surface numbers are
**lower bounds over machine-gradable assertions** (endpoint ≥8, module ≥12, app ≥20;
ceiling 3×) — a contract too thin to disagree with gets rubber-stamped.
### 3. FILL — write the loop-design JSON (`references/loop-design-shape.md`)
The decisions above mostly determine the spec. Fill the canonical **staged**
object (or **flat** if D1 found 0 seams): per-stage `definition_of_done` (a
machine-verifiable predicate, not prose — each tracing back to a contract
assertion) · `loop_pattern` · `feedback_signal` (`check` + `expect:"pass"` +
`falsifiable_when` + `passing_but_wrong`) · `stop_conditions` (per-stage cap +
`on_failure`, incl. `restart` where a build can become archaeology) · `depends_on`;
and design-level `loop_altitude` (+rationale) · `roles` · `contract` ·
`human_placement` · `maker_checker` · `harness_primitives` (name the durable
on-disk state so the loop survives compaction) · outer `stop_conditions` (with a
non-empty `success`) · `risk_guards` · the **`parameter_provenance` declaration**
(D7's output — FAIL/WARN rules in `references/loop-design-shape.md`). Include the
`selection_log`. Reuse KB templates by path (`references/loop-principle-map.md`).
### 4. VERIFY — linter + fresh-reader (eat the dogfood)
Run the linter on the produced JSON **before** returning it:
```
node scripts/lint_loop_design.mjs <produced-design.json>
```
It must print all `PASS` and exit 0. Any `FAIL <field>: <reason>` → fix and re-run.
For a **newly-emitted design, clean = zero `FAIL` and zero `WARN` lines** — a
`WARN parameter_provenance` means D7 was skipped (absence is legal only for
pre-0.4 designs; warns never change the exit code).
Then do the **fresh-reader pass** with the operational template
`assets/fresh-reader-checklist.md` — the linter checks structure, not *meaning*.
Re-read the design cold and confirm, per stage, that the `check` would actually
fail on a broken impl, `falsifiable_when` is a real break (not a goal
restatement), `passing_but_wrong` is an honest concrete false-pass, failure
branches are reachable, and `success` matches what the checks prove. It also
judges what the linter *structurally* can't: whether the **contract is actually
sufficient** (≈20 assertions for an app-sized task, not a rubber-stampable
handful), the **roles are genuinely separate** (the evaluator never saw the
impl), and — the **numbers-audit box** — that every digit-bearing string is
classified in `parameter_provenance` (an empty declaration above unclassified
empirical literals is a lie). A green linter on a hollow check — or a thin
contract — is the exact trap this pass exists to catch.
### 5. PERSIST — render the runbook
```
node scripts/render_loop_doc.mjs <design.json> # writes .loop/<slug>.loop.{md,json}
```
`render_loop_doc.mjs` re-validates first and **refuses to emit a runbook for a
design the linter rejects** — a written `.loop/` doc is itself proof the design
passed. A `REFUSED:` line → fix the design and re-run. Tell the user the two paths.
## Report
Hand back: the **decision log** (D0–D7), the **roles + negotiated contract**, the
loop-design JSON, the lint result (PASS), the fresh-reader verdict, a self-scored
rubric (`loop-principle/templates/loop_quality_rubric.template.json`), the
**current bottleneck** (where the weakest link is now — plan / verification / taste;
LOOPS.md §IX), and residual risks. State the **run-report contract** the designed loop
must honour when it stops: every success/autonomy metric paired with its
integrity/damage counterpart or tagged `not measured` (`references/loops-model.md`
§VII·b) — an unpaired success number is the shape of slop, not evidence.
## Grounding (KB path resolution)
The skill reads from the embedded **loop-principle KB** at `loop-principle/`
inside this skill folder. Installing `loop-constructor` installs the KB too. If
an operator deliberately overrides it with `$LOOP_PRINCIPLE`, use that absolute
path; otherwise default to `./loop-principle`. Do **not** hardcode-fail; if the
KB is absent, say so and degrade to the cited node ids in `references/`.
Retrieval recipe: `node <kb>/tools/query_kb.mjs "<topic>"`.
## Modules
| File | When to load |
|------|--------------|
| `references/loop-selection.md` | **Phase 1 (SELECT)** — the D0–D7 decision procedure that derives the loop shape + decision log. |
| `references/loop-design-shape.md` | **Phase 3 (FILL)** — the exact canonical loop-design JSON keys the linter validates (flat + staged, incl. `roles`/`contract`/`restart`, `parameter_provenance` + its FAIL/WARN rules, the persist contract). |
| `references/loops-model.md` | **Phase 2 (NEGOTIATE)** + judgment layer — the LOOPS.md operating model: separate roles, negotiate the contract, write-to-disk state, score-the-subjective, read-the-traces, delete-the-harness (time + information dimension), the moving bottleneck. |
| `references/loop-principle-map.md` | KB grounding: each decision/field → loop-principle node ids + docs + which templates/checklists to reuse, and the query_kb recipe. |
| `assets/fresh-reader-checklist.md` | **Phase 4 (VERIFY)** — the operational fresh-reader template (per-stage + design-level boxes the linter can't check, incl. the numbers-audit). |
| `scripts/lint_loop_design.mjs` | The deterministic verifier. Flat **or** staged. CLI or `import { validate }`. |
| `scripts/render_loop_doc.mjs` | Renders a linter-valid design into a runnable runbook; validates first, refuses invalid. |
| `assets/golden-loop-design.json` | A passing **flat** fixture (the atomic single-stage unit). |
| `assets/golden-loop-design-medium.json` | A passing **staged** fixture — copy as the starting point for a decomposed design. |
| `evals/run_all.mjs` | Re-runnable adversarial battery over the linter + renderer; `node evals/run_all.mjs`. |
## Controls
- **Design-only — persist, don't execute.** Writing the loop-design JSON + the
runbook to `.loop/` is producing the design *artifact*. Never run the designed
loop, run the target's code, or modify the loop-principle KB.
- **Run the selection procedure.** Don't pick a shape by vibes — derive it from
D0–D7 and emit the decision log. A design without a decision log is incomplete.
- **Don't write numbers nobody measured (D7).** Decision numbers stay
pre-registered; definitional ones are fixed with a red fixture; empirical
magnitudes are declared `derived` — formula at design time, value at run time.
- **Emit STAGED** unless D1 genuinely finds 0 seams. The flat shape stays valid
as the atomic single-stage unit (linter still accepts it).
- **Separate the roles; the evaluator is adversarial.** planner/generator/evaluator
are three contexts (§II); a model that grades its own work turns sycophantic, so
the evaluator is a fresh context told the artifact is broken. Required for staged.
- **Negotiate the contract; grade it, not the spec.** Agree the testable assertions
before building (§III); too few lets the evaluator rubber-stamp. Required for staged.
- **Route failures by what they accuse (§V):** escalate → re-plane → loopback →
restart, first hit wins. Restarting the stage's own stalled work stays autonomous
(no human). Escalate = stop for the owner on a wrong contract, an impossible or
blocked task (never sealed), or a fixer signature such as a P0/P1 inside the
previous fix; the owner first asks whether the judgment should be mechanized at
all — re-plane is theirs, never an `on_failure` the loop picks. Every trigger is a
**counter fixed before the run**, never an in-flight call.
- **Stop on both sides.** `stop_conditions` carries a zero-change gate ("N iterations
with zero new changes → stop", the anti-arms-race brake) *and* a minimum-progress
floor below which an early stop escalates instead of counting as done (D5).
- **Settle the harness both ways at each model release** (§VIII). Delete what the
model now does for free, add back that version's named failure modes, stamp each
change with `model_baseline`; match degrees-of-freedom to the task. A growing-only
harness is one you've stopped reading.
- **Reject-on-no-check (per stage).** A stage with no runnable feedback signal
FAILs the linter; the anchor holds for every stage.
- **Mandatory caps.** Every stage + the outer loop carry a finite
`max_iterations`, written inside `stop_conditions`; the stage graph must be acyclic.
A loop may trip a cap, never raise one — cap changes happen outside the loop.
- **Self-verification gate.** Return a design only after the linter PASSes and the
fresh-reader checklist is clean.
- **Cite, don't assert.** Back design choices with loop-principle node ids.
## Lifecycle
- **version** in frontmatter (`0.5.0`).
- **Breaking change** = any change to the loop-design JSON schema the linter binds
to (a new required field, a renamed key) — staged consumers must re-author. `0.2.0`
added `roles` + `contract` (required for staged) and the `restart` action; the
flat atomic shape stays back-compatible.
- **`0.5.0` — routing re-aligned (non-breaking).** Pre-0.5 runbooks still lint
green but may route "own fix → restart": re-review them with the fresh-reader §V
box. `0.4.0` (parameter provenance) and earlier: `CHANGELOG.md`.
- **Rollback** = `git restore` the skill dir; the skill only writes design artifacts
under the target's `.loop/` and never executes a loop, so a bad design is inert.
don't have the plugin yet? install it then click "run inline in claude" again.