lastlight 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/cli/cli-config.d.ts +7 -0
  2. package/dist/cli/cli-config.js +15 -0
  3. package/dist/cli/cli-config.js.map +1 -1
  4. package/dist/cli/cli-server.d.ts +7 -0
  5. package/dist/cli/cli-server.js +60 -15
  6. package/dist/cli/cli-server.js.map +1 -1
  7. package/dist/cli/cli.js +4 -2
  8. package/dist/cli/cli.js.map +1 -1
  9. package/dist/cli/setup.d.ts +14 -4
  10. package/dist/cli/setup.js +91 -30
  11. package/dist/cli/setup.js.map +1 -1
  12. package/dist/config/config.d.ts +24 -0
  13. package/dist/config/config.js +12 -0
  14. package/dist/config/config.js.map +1 -1
  15. package/dist/connectors/github-webhook.d.ts +11 -1
  16. package/dist/connectors/github-webhook.js +10 -17
  17. package/dist/connectors/github-webhook.js.map +1 -1
  18. package/dist/engine/agent-executor.js +16 -0
  19. package/dist/engine/agent-executor.js.map +1 -1
  20. package/dist/engine/chat/chat.d.ts +14 -0
  21. package/dist/engine/chat/chat.js +38 -0
  22. package/dist/engine/chat/chat.js.map +1 -1
  23. package/dist/engine/github/github-tools.d.ts +8 -2
  24. package/dist/engine/github/github-tools.js +4 -2
  25. package/dist/engine/github/github-tools.js.map +1 -1
  26. package/dist/index.js +71 -26
  27. package/dist/index.js.map +1 -1
  28. package/docker-compose.yml +5 -0
  29. package/package.json +1 -1
  30. package/plugins/lastlight/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lastlight/skills/lastlight-evals-loop/SKILL.md +93 -42
  32. package/plugins/lastlight/skills/lastlight-evals-loop/references/approach.md +47 -0
  33. package/plugins/lastlight/skills/lastlight-evals-loop/references/guardrails.md +16 -2
  34. package/plugins/lastlight/skills/lastlight-evals-loop/references/journal-format.md +21 -10
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin.json",
3
3
  "name": "lastlight",
4
4
  "displayName": "Last Light",
5
- "version": "0.9.0",
5
+ "version": "0.10.1",
6
6
  "description": "Install, configure and operate Last Light (GitHub maintenance agent) — its server, CLI client, deployment overlay, and the Last Light Evals harness.",
7
7
  "author": {
8
8
  "name": "Clifton Cunningham"
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: lastlight-evals-loop
3
- description: Drive a Last Light EVAL toward a target score with a disciplined, anti-gaming improvement loop — run → diagnose → propose ONE generic fix → re-measure → keep or revert → repeat. Use when the user wants to "improve / raise the pr-review F1", "make the reviewer better against the eval", "close the loop on evals", "iterate on prompts/skills to pass more cases", or "tune the workflow to hit a score target". The loop diagnoses on a TRAIN split and validates on a BLIND held-out split so fixes must generalize, not overfit; it prefers generic overlay prompt/skill edits, and stops for human sign-off before editing any gold answer. For a one-off run/compare use lastlight-evals; to hand-fork a workflow/prompt use lastlight-overlay. Needs an already-scaffolded evals workspace (lastlight-evals) with a pr-review dataset.
4
- version: 1.0.0
3
+ description: Drive a Last Light EVAL toward a target score with a disciplined, anti-gaming improvement loop — run → mine failures → propose candidate fix(es) → re-measure → keep the best or revert → repeat. Use when the user wants to "improve / raise the pr-review F1", "make the reviewer better against the eval", "close the loop on evals", "iterate on prompts/skills to pass more cases", or "tune the workflow to hit a score target". The loop diagnoses on a TRAIN split and validates on a BLIND held-out split so fixes must generalize, not overfit; it prefers generic overlay prompt/skill edits, and stops for human sign-off before editing any gold answer. For a one-off run/compare use lastlight-evals; to hand-fork a workflow/prompt use lastlight-overlay. Needs an already-scaffolded evals workspace (lastlight-evals) with a pr-review dataset.
4
+ version: 1.1.0
5
5
  tags: [lastlight, evals, loop, improvement, overfitting, pr-review]
6
6
  ---
7
7
 
@@ -16,6 +16,11 @@ That lifts the number and ruins the reviewer. Every guardrail here exists to for
16
16
  Scope: the **pr-review** tier (its judge trace gives you the agent-vs-gold detail
17
17
  diagnosis needs). The pattern extends to triage/code-fix, but start here.
18
18
 
19
+ The method — **weakness-mine → propose a few minimal candidates → keep the one
20
+ that survives a blind held-out gate** — follows *Self-Harness: Harnesses That
21
+ Improve Themselves* ([arXiv:2606.09498](https://arxiv.org/abs/2606.09498)), adapted
22
+ to keep our anti-gaming discipline. See **references/approach.md** for the mapping.
23
+
19
24
  ## Prerequisites
20
25
 
21
26
  - A scaffolded evals workspace with a **pr-review** dataset and a working provider
@@ -23,22 +28,25 @@ diagnosis needs). The pattern extends to triage/code-fix, but start here.
23
28
  bare `lastlight-evals run pr-review --limit 1 --no-open` grades a case.
24
29
  - An **`instance/` overlay** (the deployment overlay) to receive generic edits —
25
30
  see **`lastlight-overlay`**. The loop edits copies here, never core.
26
- - The `diff-runs.ts` helper ships with the evals package
27
- (`scripts/diff-runs.ts`) it compares two runs and calls keep/revert.
31
+ - Two helpers ship with the evals package: **`scripts/mine-failures.ts`** (ranks
32
+ the TRAIN failure signatures into an evidence bundle — the diagnosis input) and
33
+ **`scripts/diff-runs.ts`** (compares two runs and calls keep/revert).
28
34
 
29
35
  ## The one rule
30
36
 
31
- **One change per iteration.** Propose a single hypothesis, measure its effect in
32
- isolation, keep or revert, then move on. No batchingbatching hides which edit
33
- moved the number and invites scattershot overfitting.
37
+ **One change *kept* per round.** A round may *explore* a few minimal candidates,
38
+ but at most one is ever kept and committed to `instance/` the rest are reverted.
39
+ So which edit moved the number is always attributable, and no overfit edit rides
40
+ along with a good one. Never keep two edits from one round without re-measuring
41
+ each in isolation.
34
42
 
35
43
  ## Start here
36
44
 
37
45
  | You have… | Go to |
38
46
  |---|---|
39
47
  | Never run this loop on this dataset | **§1 Set up the split** then §2 |
40
- | A split + baseline already recorded | **§3 Diagnose** |
41
- | A proposed change ready to test | **§5 Audit → §6 Apply → §7 Measure → §8 Decide** |
48
+ | A split + baseline already recorded | **§3 Diagnose (mine failures)** |
49
+ | Candidate change(s) ready to test | **§5 Audit → §6 Apply → §7 Measure → §8 Decide** |
42
50
 
43
51
  ## 1. Set up the split (once per dataset)
44
52
 
@@ -54,7 +62,7 @@ lastlight-evals run pr-review --instance <all-ids> --no-open # or omit --insta
54
62
  ```
55
63
 
56
64
  Set a **target**: an F1 goal (e.g. 0.55) or "improve until plateau" (N=3
57
- consecutive no-keep iterations).
65
+ consecutive no-keep rounds).
58
66
 
59
67
  ## 2. Baseline
60
68
 
@@ -69,23 +77,41 @@ lastlight-evals run pr-review --overlay instance --instance <heldout-ids> --no-o
69
77
 
70
78
  Each writes `eval-results/pr-review/<runId>/scorecard.json`. Note the two runIds.
71
79
 
72
- ## 3. Diagnose (TRAIN only)
73
-
74
- Read the TRAIN scorecard's `results[].review` and `review.trace`. **Do not open
75
- held-out traces.** For each failing case:
76
- - `falseNegatives` (with `severity`) = **recall** loss — real issues the agent missed.
77
- - `falsePositives` = **precision** loss — noise the agent posted that matched no gold.
80
+ ## 3. Diagnose (TRAIN only) — mine the failure signatures
78
81
 
79
- Cluster across cases and name the **systematic pattern** — not a one-off. Good
80
- patterns generalize: *"misses security-relevant findings"*, *"posts style nits the
81
- rubric says to suppress"*, *"confidence bar too low → noise"*, *"no awareness of the
82
- repo's conventions → wrong-fit findings"*. See **references/levers.md** for how
83
- each pattern maps to an edit.
82
+ Mine the TRAIN scorecard into a ranked **evidence bundle** — recall-loss and
83
+ precision-loss signatures ordered by impact instead of reading traces by hand:
84
84
 
85
- ## 4. Hypothesis — ONE change, lowest lever first
85
+ ```bash
86
+ npx tsx scripts/mine-failures.ts <train-scorecard.json> --train <train-ids> --keywords
87
+ ```
86
88
 
87
- Pick the **lowest** lever that could move the whole cluster (full detail in
88
- **references/levers.md**):
89
+ It reads only the TRAIN split (never held-out) and prints two ranked blocks:
90
+ - **RECALL LOSS** — missed gold (`falseNegatives`, weighted by `severity`): the
91
+ real issues the agent missed. Biggest F1 headroom is at the top.
92
+ - **PRECISION LOSS** — noise (`falsePositives`): findings that matched no gold.
93
+
94
+ Each signature is `axis·severity·area` with a frequency, case count, example ids,
95
+ and (with `--keywords`) a heuristic category guess. Take the **top systematic
96
+ pattern** — not a one-off — and name it: *"misses security-relevant findings"*,
97
+ *"posts style nits the rubric says to suppress"*, *"confidence bar too low →
98
+ noise"*, *"no awareness of the repo's conventions → wrong-fit findings"*. See
99
+ **references/levers.md** for how each pattern maps to an edit. (You can still open
100
+ individual TRAIN `review.trace`s to read a signature's underlying findings — just
101
+ never a held-out trace.)
102
+
103
+ ## 4. Propose candidates — a few minimal, diverse edits
104
+
105
+ For the top signature, draft **K = 2–4 candidate edits**, each **minimal** (touches
106
+ only what the pattern needs) and each targeting a *different* mechanism or a
107
+ different formulation of the same one — e.g. one tightens the severity bar in the
108
+ `code-review` skill, another adds a confidence gate in the reviewer prompt. K stays
109
+ small on purpose: selecting the best of many candidates optimistically inflates the
110
+ train number, so keep the field narrow and let the held-out gate (§7b) be the check.
111
+ Drafting one candidate is fine — best-of-K is an option, not an obligation.
112
+
113
+ Every candidate uses the **lowest** lever that could move the cluster (full detail
114
+ in **references/levers.md**):
89
115
 
90
116
  - **(a) generic — auto.** Edit an overlay prompt/skill/persona, or add a generic
91
117
  `instance/repo-context/AGENTS.md` injected into every repo. Must be general.
@@ -95,55 +121,80 @@ Pick the **lowest** lever that could move the whole cluster (full detail in
95
121
  - **(c) the eval itself — rare, signed-off.** Edit `review_gold[]` ONLY when the
96
122
  gold is demonstrably wrong/incomplete; name the evidence. Human approves.
97
123
 
98
- Escalate only when a lower lever genuinely can't address the pattern, and write
99
- down why.
124
+ Escalate a candidate only when a lower lever genuinely can't address the pattern,
125
+ and write down why.
100
126
 
101
- ## 5. Audit the change (adversarial sub-agent)
127
+ ## 5. Audit each candidate (adversarial sub-agent)
102
128
 
103
- Before applying, spawn a **generality + leak auditor** sub-agent (prompt template
104
- in **references/guardrails.md**). It **REJECTS** if the change:
129
+ Before measuring, run the **generality + leak auditor** sub-agent (prompt template
130
+ in **references/guardrails.md**) on **every** candidate. It **REJECTS** if the change:
105
131
  - names a specific repo / `instance_id` / file path (lever a must be generic), or
106
132
  - **encodes the gold answer** — injected repo context that describes the specific
107
133
  findings is cheating. It must read as plausible maintainer guidance written
108
134
  *without* knowledge of this PR's bug (conventions, architecture, review
109
135
  priorities), never "look for bug X".
110
136
 
111
- Rejected back to §4.
137
+ Rejected candidates are dropped (not counted); survivors go to §6. If none survive,
138
+ back to §4.
112
139
 
113
140
  ## 6. Apply (hybrid autonomy)
114
141
 
142
+ Apply each surviving candidate on its **own throwaway branch** (or stash between
143
+ candidates) so their §7a measurements don't cross-contaminate — only the winner is
144
+ committed in §8.
145
+
115
146
  - **Lever (a)** (incl. generic `repo-context/AGENTS.md`): apply automatically to
116
147
  `instance/…` once the auditor passes.
117
148
  - **Lever (b)/(c)**: **STOP.** Present the diff + the written justification and ask
118
149
  the human to approve before writing to the dataset.
119
150
 
120
- ## 7. Re-measure
151
+ ## 7. Re-measure — train selects, held-out confirms **once**
152
+
153
+ The blind held-out set must be consumed **once per round**, not once per candidate —
154
+ gating every candidate on held-out and keeping the best would inflate it (max-of-K
155
+ selection bias). So split measurement in two:
156
+
157
+ **7a. Rank candidates on TRAIN only.** Run each survivor on the TRAIN split and diff
158
+ *without* `--heldout` (the verdict prints `REVIEW — no held-out`, expected here):
159
+
160
+ ```bash
161
+ npx tsx scripts/diff-runs.ts <baseline-train.json> <candidate-train.json> --train <train-ids>
162
+ ```
163
+
164
+ Pick **one winner**: highest train Δ → tie-break lowest lever → smallest diff →
165
+ fewest per-case train regressions. **Do not touch held-out in 7a.**
121
166
 
122
- Re-run both splits (§2) with the change, then diff against the baseline:
167
+ **7b. Confirm the winner on HELD-OUT, once.** Run the winner on both splits and diff
168
+ with the full gate:
123
169
 
124
170
  ```bash
125
- npx tsx scripts/diff-runs.ts <baseline-train.json> <candidate-train.json> \
171
+ npx tsx scripts/diff-runs.ts <baseline.json> <winner.json> \
126
172
  --train <train-ids> --heldout <heldout-ids>
127
173
  ```
128
174
 
129
- Point it at the run pair that covers both splits (or run the two splits in one
130
- `--instance <all-ids>` run so a single scorecard holds both). It prints per-case
131
- F1 deltas, the arm-summary delta, and a **KEEP / REVERT verdict**.
175
+ (Run the two splits in one `--instance <all-ids>` run so a single scorecard holds
176
+ both.) If a candidate may legitimately be held-out-driven with flat train, add
177
+ `--symmetric` for the paper's non-regressive gate (KEEP iff neither split regresses
178
+ and one improves); record which gate you used. With a single candidate, 7a and 7b
179
+ collapse into one gated run.
132
180
 
133
181
  ## 8. Decide
134
182
 
135
- - **KEEP** iff train F1 improved **and** held-out did **not** regress (the helper's
136
- verdict). Train ↑ with held-out ↓ = **overfit → revert**.
183
+ - **KEEP** the winner iff its train F1 improved **and** held-out did **not** regress
184
+ (the helper's verdict). Train ↑ with held-out ↓ = **overfit → revert**.
137
185
  - **Lever (b)** per-repo edits are case-scoped: keep iff that case improved, the
138
186
  auditor passed, and the human approved — recorded as a per-repo recommendation,
139
187
  not a workflow change (no held-out claim is made for it).
140
- - Else **REVERT**: `git checkout` the overlay file, or remove the sidecar.
188
+ - **Revert the K−1 losers** and, on a REVERT verdict, the winner too: `git checkout`
189
+ the overlay file, or remove the sidecar. Exactly one edit (or none) survives the
190
+ round — that is the "one change kept per round" rule.
141
191
 
142
192
  ## 9. Journal, then repeat
143
193
 
144
- Append one entry per iteration (schema in **references/journal-format.md**):
145
- pattern, lever, the diff/context, auditor verdict, train Δ, held-out Δ, decision,
146
- running best. Then return to §3. Stop at the target or the plateau.
194
+ Append one entry per **round** (schema in **references/journal-format.md**): the
195
+ mined pattern, the K candidates with each one's train Δ, which won, the winner's
196
+ single held-out Δ, the decision, and the running best. Then return to §3. Stop at
197
+ the target or the plateau (N=3 consecutive no-keep rounds).
147
198
 
148
199
  Run the loop on a cadence with the built-in `/loop` if you want it to keep going
149
200
  unattended between your check-ins — but the lever-(b)/(c) sign-off stops still
@@ -0,0 +1,47 @@
1
+ # Approach & prior art — why the loop looks like this
2
+
3
+ This loop is a **self-improving harness**: it edits the reviewing agent's harness
4
+ (prompts, skills, persona, injected repo-context) — never its weights and never the
5
+ core workflow — and keeps only edits that a blind held-out split confirms.
6
+
7
+ ## Prior art: *Self-Harness*
8
+
9
+ The structure follows ***Self-Harness: Harnesses That Improve Themselves***
10
+ ([arXiv:2606.09498](https://arxiv.org/abs/2606.09498)), which runs a three-stage
11
+ cycle on a fixed model: **mine weaknesses → propose K minimal candidate edits →
12
+ validate on held-in *and* held-out under a non-regressive gate**, keeping only edits
13
+ that don't degrade either split. Its notable result: the retained edits are
14
+ concrete and evidence-grounded, "not generic instruction padding."
15
+
16
+ We adopt the parts that make our loop better-grounded and better-explored, and
17
+ deliberately *diverge* where our anti-gaming constraints are stricter than the
18
+ paper's.
19
+
20
+ | Self-Harness stage | Here | Where |
21
+ |---|---|---|
22
+ | Weakness mining (cluster failing traces by signature, ranked by impact) | `scripts/mine-failures.ts` → ranked recall/precision signature bundle from TRAIN traces | SKILL §3 |
23
+ | K diverse minimal candidate edits | Draft K=2–4 minimal candidates, each a different mechanism/formulation | SKILL §4 |
24
+ | Validate on both splits, non-regressive gate | **TRAIN selects, HELD-OUT confirms once**; default asymmetric gate, opt-in `--symmetric` for the paper's rule | SKILL §7, `diff-runs.ts` |
25
+ | Document each transition | The per-round journal (all candidates + winner + held-out Δ) | `journal-format.md` |
26
+
27
+ ## Where we diverge from the paper (on purpose)
28
+
29
+ - **Held-out is consumed once per round, not once per candidate.** The paper gates
30
+ each candidate on held-out. Because our loop can *see* the gold set, screening K
31
+ candidates on the blind split and keeping the best would inflate it (max-of-K
32
+ selection bias). So we rank on TRAIN only and give the single winner one held-out
33
+ confirmation. See guardrails §2.
34
+ - **Generic-first, not model-specific.** The paper found value in *model-specific*
35
+ harness edits. Our default lever must generalize across **repos** (the held-out
36
+ split is a repo split), so we prefer generic overlay edits and gate them on
37
+ held-out. A model-specific-but-repo-generic lever is a possible future escalation,
38
+ but it is **not** part of this loop today — don't reach for it.
39
+ - **A leak/generality auditor gates every candidate.** The paper relies on the
40
+ held-out gate alone; we add an adversarial auditor (guardrails §3) because a loop
41
+ that can read the gold answer can encode it — a failure mode the paper's setup
42
+ doesn't face.
43
+ - **Human sign-off on the game-able levers.** Per-repo context (b) and gold edits
44
+ (c) are case-scoped with no held-out claim, so a human ratifies them.
45
+
46
+ The net: same mine → propose → validate skeleton, hardened for a loop that can see
47
+ the answers it is being graded against.
@@ -15,10 +15,18 @@ not override it by eyeballing train alone.
15
15
  The split ids are fixed for the whole loop. If you re-split (e.g. to add cases),
16
16
  say so in the journal and treat prior held-out results as no longer comparable.
17
17
 
18
- ## 2. One change per iteration
18
+ ## 2. One change *kept* per round
19
19
 
20
20
  Isolates causal effect. If two edits ship together and the number moves, you can't
21
- attribute it — and you can't tell an overfit edit riding along with a good one.
21
+ attribute it — and you can't tell an overfit edit riding along with a good one. A
22
+ round may *explore* K=2–4 minimal candidates, but each is measured on its own
23
+ branch and at most one is kept.
24
+
25
+ **Best-of-K must not consume the blind split K times.** Selecting the best of many
26
+ candidates on held-out inflates it (max-of-K selection bias). So candidates are
27
+ ranked on **TRAIN only**; the single winner gets **one** held-out confirmation.
28
+ Keep K small — the more candidates you screen on train, the more optimistic the
29
+ winner's train number, and only the one held-out run keeps it honest.
22
30
 
23
31
  ## 3. The generality + leak auditor (adversarial sub-agent)
24
32
 
@@ -72,8 +80,14 @@ and why the number moved — no silent wins.
72
80
  ## The keep/revert decision, precisely
73
81
 
74
82
  ```
83
+ best-of-K selection (per round, before the gate below):
84
+ rank surviving candidates on TRAIN ONLY -> winner = highest trainΔ
85
+ (tie-break: lowest lever, smallest diff, fewest train per-case regressions)
86
+ then apply the gate below to the WINNER, consuming held-out ONCE.
75
87
  lever (a) generic:
76
88
  KEEP iff trainΔ > epsilon AND heldoutΔ >= -epsilon (else REVERT)
89
+ (--symmetric variant, for a held-out-driven candidate:
90
+ KEEP iff trainΔ >= -epsilon AND heldoutΔ >= -epsilon AND max(both) > epsilon)
77
91
  lever (b) per-repo:
78
92
  KEEP iff that case improved AND auditor ACCEPTED AND human approved
79
93
  (recorded as a per-repo recommendation; no held-out claim)
@@ -19,31 +19,42 @@ moved — keep it complete even for reverted iterations (a revert is a result).
19
19
  - Baseline: train F1 = 0.33, heldout F1 = 0.31 (runIds: <train>, <heldout>)
20
20
  ```
21
21
 
22
- ## Per-iteration entry
22
+ ## Per-round entry
23
+
24
+ A round mines one pattern, explores K candidates (ranked on TRAIN), and keeps at
25
+ most one (confirmed on HELD-OUT once). A single-candidate round is just K=1.
23
26
 
24
27
  ```markdown
25
- ## Iteration <N> — <one-line hypothesis>
28
+ ## Round <N> — <one-line hypothesis for the top pattern>
26
29
 
27
- - Pattern: <the systematic failure named in §3, e.g. "posts style nits the rubric suppresses (precision loss across 4 train cases)">
28
- - Lever: (a) generic | (b) per-repo | (c) gold — <why this lever; why not lower>
29
- - Change:
30
+ - Pattern: <the systematic failure named in §3, e.g. "misses security-relevant findings (recall·high, 4 train cases)"; from mine-failures.ts>
31
+ - Candidates (ranked on TRAIN only):
32
+ | # | lever | change (file) | trainΔ | auditor |
33
+ |---|-------|---------------|--------|---------|
34
+ | 1 | (a) | instance/skills/code-review/SKILL.md | +0.08 | ACCEPT |
35
+ | 2 | (a) | instance/workflows/prompts/reviewer.md | +0.03 | ACCEPT |
36
+ | 3 | (a) | instance/repo-context/AGENTS.md | -0.01 | REJECT (leak) |
37
+ - Winner: #1 — <why (highest trainΔ; tie-break rule if used)>
38
+ - Winner change:
30
39
  file: instance/skills/code-review/SKILL.md
31
40
  diff: |
32
41
  <the actual diff, or the injected-context text>
33
- - Auditor: ACCEPT | REJECT — <one-line reason>
34
42
  - Applied: auto | human-approved (<who/when>) | not applied (auditor rejected)
35
- - Result (diff-runs.ts):
43
+ - Held-out confirmation (diff-runs.ts, winner only — held-out consumed ONCE):
44
+ gate: default | --symmetric
36
45
  train: 0.33 -> 0.41 (Δ +0.08)
37
46
  heldout: 0.31 -> 0.31 (Δ +0.00)
38
- per-case regressions: 0
47
+ per-case regressions: 0 (REGRESSED(train): none)
39
48
  - Decision: KEEP | REVERT | (b) recorded as per-repo recommendation
49
+ - Reverted losers: #2, #3
40
50
  - Running best: train F1 = 0.41, heldout F1 = 0.31
41
51
  ```
42
52
 
43
53
  ## Rules
44
54
 
45
- - **One entry per iteration**, including reverts and auditor rejections — the tail
46
- of no-keep iterations is the plateau signal.
55
+ - **One entry per round**, including all-revert rounds and auditor rejections — the
56
+ tail of no-keep rounds is the plateau signal. Record every candidate's train Δ,
57
+ not just the winner's, so the best-of-K selection is auditable.
47
58
  - Paste the **actual diff / context text**, not a paraphrase — the point is
48
59
  reproducibility.
49
60
  - Always record **both** train and held-out deltas. A missing held-out number