experiment-loop
A bounded change → commit → evaluate → keep-or-revert cycle against a
scalar metric, where the decision input is an
evaluator-output verdict and the
loop's entire state lives in an append-only register on disk.
This file routes. The protocol, the register format, and the pivot ladder are
reference files loaded on demand — a loop doctrine read in full on every session
that merely mentions optimization is a payload nobody asked for.
| Need |
Load |
| The iteration protocol and its two exit conditions |
references/protocol.md |
| The register's row shape and write ordering |
references/register-format.md |
| What to do when the metric stops moving |
references/pivot-ladder.md |
When to use
- The goal is expressible as "minimize X" or "maximize X" against a
mechanical verifier — bundle size, violation count, query count, runtime.
- A verifier already emits, or can be wrapped to emit, the evaluator-output
contract. Wrapping is usually free: three of this repo's verifiers were wrapped
with zero changes to any of them.
Do NOT use when:
- The target is a known end state, not a number — "make the tests pass" has a
destination, so use
verify-repair-loop,
which converges toward it. This skill has no destination; it has a direction.
- The verdict is subjective craft — no scalar, nothing to optimize.
- The metric is a proxy nobody agreed on. Optimizing an unowned proxy is how
a loop makes a system worse while its number improves.
The Iron Law
THE METRIC IS NEVER SOVEREIGN. `pass` IS.
KEEP ONLY ON `pass && score > baseline`. A CHANGE THAT IMPROVES THE NUMBER
AND BREAKS BEHAVIOUR IS REVERTED — MEASURED, NOT ASSUMED.
COMMIT BEFORE YOU EVALUATE, SO EVERY REVERT IS A GIT OPERATION.
THE REGISTER IS WRITTEN BEFORE THE ACTION IT RECORDS, NEVER AFTER.
STATE LIVES ON DISK AND IS RE-READ EACH CYCLE — NEVER IN THE CONVERSATION.
Each line is a measured failure, not a preference. Spike s01 ran a change that
improved its metric by 67 % and broke the behaviour it measured; only pass
reverted it. The same run crashed between its commit and its register append,
twice, leaving the branch one iteration ahead of its own record.
Procedure
- Inspect the verifier before trusting it. Run the evaluator once on the
unchanged tree and check its verdict:
pass must be true and metric_state
must be present. A baseline taken from an evaluator that is already red, or
whose metric is unreadable, is not a baseline — every later comparison
inherits the defect silently.
- Fix the metric and the verifier before the first change. Name the
evaluator, its direction, and the baseline score.
- Declare the bound — a maximum iteration count, written into the register's
first row. An unbounded loop is not this skill.
- Iterate per
references/protocol.md: one focused
change, commit, evaluate, keep or revert, append the outcome.
- Exit on either condition (both machine-checkable, never prose): the bound
is reached, or the explicit exit signal fires.
- Report from the register, not from memory. If the register cannot
reconstruct the run without the transcript, the run is not reportable.
Validation
- The register reconstructs the run with the transcript discarded — every kept
change traceable to a row, every row to a commit.
- Both branches ran at least once. A loop whose revert branch never executed
is a one-branch loop with a comment; spike s01 found its revert broken on
first use after four green iterations.
- The final tree passes the evaluator with
pass: true.
Output format
- The register path, and the iteration count actually run against the bound.
- A metric line: baseline → final, and the number of keeps versus reverts.
- The exit condition that fired, named — bound reached, or exit signal.
- Every reverted iteration listed with its reason. A revert is a result the
next run needs, not noise to summarise away.
Gotcha
- A metric that improves while the system degrades. The reason
pass is
sovereign. Never widen a verifier to make an iteration keep.
- A revert path that has never run. Exercise it deliberately once.
- A register appended after the mutation. A crash in between leaves the
branch ahead of the record; write the row first, then update its outcome.
- Loop state in the conversation. It decays silently and the loop keeps
comparing against a baseline it no longer holds.
- Iterating past the bound because the number is still moving. The bound is
the contract; extending it is a new run with a new baseline.
Do NOT
- Do NOT keep a change on
score alone.
- Do NOT edit the verifier to move the metric.
- Do NOT batch several changes into one iteration — a keep you cannot attribute
is a keep you cannot revert.
- Do NOT delete a register row. Rows are append-only, including the embarrassing
ones.
1---2name: experiment-loop3description: Use to drive a scalar metric down or up across bounded iterations — keep on strict improvement, revert otherwise, state on disk. Triggers 'minimize X', 'optimize until it stops improving'.4---56# experiment-loop78> A bounded **change → commit → evaluate → keep-or-revert** cycle against a9> **scalar metric**, where the decision input is an10> [evaluator-output](../../../docs/contracts/evaluator-output.md) verdict and the11> loop's entire state lives in an append-only register on disk.1213This file **routes**. The protocol, the register format, and the pivot ladder are14reference files loaded on demand — a loop doctrine read in full on every session15that merely mentions optimization is a payload nobody asked for.1617| Need | Load |18|---|---|19| The iteration protocol and its two exit conditions | [`references/protocol.md`](references/protocol.md) |20| The register's row shape and write ordering | [`references/register-format.md`](references/register-format.md) |21| What to do when the metric stops moving | [`references/pivot-ladder.md`](references/pivot-ladder.md) |2223## When to use2425* The goal is expressible as **"minimize X"** or **"maximize X"** against a26 mechanical verifier — bundle size, violation count, query count, runtime.27* A verifier already emits, or can be wrapped to emit, the evaluator-output28 contract. Wrapping is usually free: three of this repo's verifiers were wrapped29 with zero changes to any of them.3031Do **NOT** use when:3233* The target is a **known end state**, not a number — "make the tests pass" has a34 destination, so use [`verify-repair-loop`](../verify-repair-loop/SKILL.md),35 which converges toward it. This skill has no destination; it has a direction.36* The verdict is **subjective craft** — no scalar, nothing to optimize.37* The metric is a **proxy nobody agreed on**. Optimizing an unowned proxy is how38 a loop makes a system worse while its number improves.3940## The Iron Law4142```43THE METRIC IS NEVER SOVEREIGN. `pass` IS.44KEEP ONLY ON `pass && score > baseline`. A CHANGE THAT IMPROVES THE NUMBER45AND BREAKS BEHAVIOUR IS REVERTED — MEASURED, NOT ASSUMED.46COMMIT BEFORE YOU EVALUATE, SO EVERY REVERT IS A GIT OPERATION.47THE REGISTER IS WRITTEN BEFORE THE ACTION IT RECORDS, NEVER AFTER.48STATE LIVES ON DISK AND IS RE-READ EACH CYCLE — NEVER IN THE CONVERSATION.49```5051Each line is a measured failure, not a preference. Spike s01 ran a change that52improved its metric by 67 % and broke the behaviour it measured; only `pass`53reverted it. The same run crashed between its commit and its register append,54twice, leaving the branch one iteration ahead of its own record.5556## Procedure57581. **Inspect the verifier before trusting it.** Run the evaluator once on the59 unchanged tree and check its verdict: `pass` must be true and `metric_state`60 must be `present`. A baseline taken from an evaluator that is already red, or61 whose metric is unreadable, is not a baseline — every later comparison62 inherits the defect silently.632. **Fix the metric and the verifier** before the first change. Name the64 evaluator, its direction, and the baseline score.653. **Declare the bound** — a maximum iteration count, written into the register's66 first row. An unbounded loop is not this skill.674. **Iterate** per [`references/protocol.md`](references/protocol.md): one focused68 change, commit, evaluate, keep or revert, append the outcome.695. **Exit** on either condition (both machine-checkable, never prose): the bound70 is reached, or the explicit exit signal fires.716. **Report** from the register, not from memory. If the register cannot72 reconstruct the run without the transcript, the run is not reportable.7374## Validation7576* The register reconstructs the run with the transcript discarded — every kept77 change traceable to a row, every row to a commit.78* Both branches ran at least once. **A loop whose revert branch never executed79 is a one-branch loop with a comment**; spike s01 found its revert broken on80 first use after four green iterations.81* The final tree passes the evaluator with `pass: true`.8283## Output format84851. **The register path**, and the iteration count actually run against the bound.862. **A metric line**: baseline → final, and the number of keeps versus reverts.873. **The exit condition that fired**, named — bound reached, or exit signal.884. **Every reverted iteration listed** with its reason. A revert is a result the89 next run needs, not noise to summarise away.9091## Gotcha9293* **A metric that improves while the system degrades.** The reason `pass` is94 sovereign. Never widen a verifier to make an iteration keep.95* **A revert path that has never run.** Exercise it deliberately once.96* **A register appended after the mutation.** A crash in between leaves the97 branch ahead of the record; write the row first, then update its outcome.98* **Loop state in the conversation.** It decays silently and the loop keeps99 comparing against a baseline it no longer holds.100* **Iterating past the bound because the number is still moving.** The bound is101 the contract; extending it is a new run with a new baseline.102103## Do NOT104105* Do NOT keep a change on `score` alone.106* Do NOT edit the verifier to move the metric.107* Do NOT batch several changes into one iteration — a keep you cannot attribute108 is a keep you cannot revert.109* Do NOT delete a register row. Rows are append-only, including the embarrassing110 ones.