loop-constructor-codex
Design the engineered loop for a medium/large (semi-)autonomous coding task run with
the OpenAI Codex CLI (single-agent, codex exec) 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.
Codex runtime mapping
The design vocabulary above (roles, contract, separate-context evaluator, restart,
gate, harness_primitives) is runtime-neutral — it names loop-engineering concepts,
not any runtime's primitives. How each concept lands on the Codex CLI lives in
references/codex-runtime.md — load it at NEGOTIATE, FILL and PERSIST (its phase map
says what each takes). The design JSON itself stays neutral.
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 — on
Codex, fan-out = concurrent codex exec processes, never in-process subagents) ·
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. On Codex these are three separatecodex execinvocations (references/codex-runtime.md§1). - 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 context loss — each codex exec is fresh; disk
is the only memory, see references/codex-runtime.md §2) · outer stop_conditions
(with a non-empty success) · risk_guards (map to Codex sandbox / approval
levers, codex-runtime.md §4) · 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. The emitted Markdown carries a "How to run this loop (Codex CLI)"
preamble (codex-runtime.md phase map). 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. If the
loop-principle KB was not found (see Grounding), say so out loud — the design ran
in KB-degraded mode.
Grounding (KB path resolution)
The skill reads from the loop-principle KB, which this Codex skill does not
embed (avoiding a duplicate copy). Default: resolve <kb> to the sibling skill's
copy — try, in order, ../loop-constructor/loop-principle then
../vince-loop-constructor/loop-principle (installed sibling dirs carry the
vince- prefix), relative to this skill folder. If an
operator overrides it with $LOOP_PRINCIPLE, use that absolute path. Do not
hardcode-fail; if the KB is absent (e.g. this skill is installed on a machine
without the sibling), say so out loud in the report and degrade to the cited
node ids in references/ — KB-degraded mode is acceptable.
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/codex-runtime.md |
Codex realization — how roles/state/parallelism/guards land on codex exec. Load during NEGOTIATE (roles), FILL (harness_primitives + D4), PERSIST (runbook preamble). |
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 (with the Codex how-to-run preamble); 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. |
assets/golden-loop-design-large.json |
A passing large-altitude (fan-out) fixture — exercises the concurrent-codex exec Orchestration preamble; copy for a parallel-shard 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. - One process per role. Never design a stage that requires in-process subagents
or a Task/Workflow tool; parallelism and fresh-evaluator contexts are realized as
separate
codex execOS processes (multi_agentexists on codex-cli 0.144.4, its isolation is unverified —references/codex-runtime.md). - 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) — three separate
codex execinvocations; a model that grades its own work turns sycophantic, so the evaluator is a fresh read-onlycodex exectold the artifact is broken. Read-only limits what it writes, not what it obeys: it runs where the generator cannot have editedAGENTS*.md/.codex//its prompts (codex-runtime.md §1), or independence isL-i incomplete. 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_failurethe loop picks. Every trigger is a counter fixed before the run, never an in-flight call. - Stop on both sides.
stop_conditionscarries 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 or codex-cli release (§VIII). Delete
what the model now does for free, add back that version's named failure modes, stamp
each change with
model_baseline(model id + effort + codex-cli version); 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 insidestop_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.3.0); tracks loop-constructor 0.5.0. - Shared schema + linter.
scripts/lint_loop_design.mjs= loop-constructor 0.5.0 (unchanged since 0.4.0), sha2561fec173225e5c671086da11fc6b85bb2183f6da636e0db7d25cdb16ae256fd36— designs are cross-compatible between the two skills; the shared references/assets differ only by the Codex hunks listed inCHANGELOG.md, and the renderer adds the Codex preamble. A change to the schema the linter binds to (a new required field, a renamed key) is the only kind of breaking change. 0.3.0— routing + evaluator surface (non-breaking). Pre-0.3 Codex runbooks still lint green but may route "own fix → restart" and leaveAGENTS.mdgenerator-writable: re-review them with the fresh-reader §V and evaluator-instruction-file boxes; never auto-rewrite. History:CHANGELOG.md.- Rollback =
git restorethe skill dir; the skill only writes design artifacts under the target's.loop/and never executes a loop, so a bad design is inert.
