paperlint 10.0.0 → 10.1.0

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 (54) hide show
  1. package/CONTRIBUTING.md +16 -4
  2. package/dist/eslint-rules/js-needs-reason.d.ts +55 -0
  3. package/dist/eslint-rules/js-needs-reason.d.ts.map +1 -0
  4. package/dist/eslint-rules/js-needs-reason.js +35 -0
  5. package/dist/eslint-rules/js-needs-reason.js.map +1 -0
  6. package/dist/package-dirs.d.ts +6 -0
  7. package/dist/package-dirs.d.ts.map +1 -1
  8. package/dist/package-dirs.js +6 -0
  9. package/dist/package-dirs.js.map +1 -1
  10. package/dist/skills/label-agent-runs/scripts/labels-commands.d.ts +13 -0
  11. package/dist/skills/label-agent-runs/scripts/labels.d.ts +2 -0
  12. package/dist/skills/run-agent-grid/scripts/grid-guard-commands.d.ts +20 -0
  13. package/dist/skills/run-agent-grid/scripts/grid-guard.d.ts +2 -0
  14. package/docs/skills.md +2 -0
  15. package/docs/testing.md +13 -0
  16. package/eslint-rules/js-needs-reason.test.ts +141 -0
  17. package/eslint-rules/js-needs-reason.ts +77 -0
  18. package/eslint.config.mjs +22 -2
  19. package/lib/skill-trigger-cases.test.ts +211 -0
  20. package/package.json +8 -4
  21. package/scripts/coverage-config.test.ts +5 -3
  22. package/skills/build-benchmark/SKILL.md +4 -1
  23. package/skills/build-benchmark/SKILL.md.spec.ts +3 -0
  24. package/skills/build-label-page/SKILL.md +3 -1
  25. package/skills/build-label-page/SKILL.md.spec.ts +2 -0
  26. package/skills/label-agent-runs/SKILL.md +103 -0
  27. package/skills/label-agent-runs/SKILL.md.spec.ts +106 -0
  28. package/skills/label-agent-runs/label-agent-runs.eval.ts +55 -0
  29. package/skills/label-agent-runs/label-agent-runs.harness.ts +123 -0
  30. package/skills/label-agent-runs/references/formats.md +64 -0
  31. package/skills/label-agent-runs/scripts/labels-commands.js +13 -0
  32. package/skills/label-agent-runs/scripts/labels-commands.ts +13 -0
  33. package/skills/label-agent-runs/scripts/labels.js +396 -0
  34. package/skills/label-agent-runs/scripts/labels.test.ts +538 -0
  35. package/skills/label-agent-runs/scripts/labels.ts +501 -0
  36. package/skills/paper-pipeline/SKILL.md +8 -1
  37. package/skills/paper-pipeline/SKILL.md.spec.ts +7 -0
  38. package/skills/run-agent-grid/SKILL.md +181 -0
  39. package/skills/run-agent-grid/SKILL.md.spec.ts +184 -0
  40. package/skills/run-agent-grid/references/failure-catalog.md +36 -0
  41. package/skills/run-agent-grid/references/registry-and-ledger.md +142 -0
  42. package/skills/run-agent-grid/run-agent-grid.eval.ts +59 -0
  43. package/skills/run-agent-grid/run-agent-grid.harness.ts +115 -0
  44. package/skills/run-agent-grid/scripts/grid-guard-commands.js +18 -0
  45. package/skills/run-agent-grid/scripts/grid-guard-commands.ts +21 -0
  46. package/skills/run-agent-grid/scripts/grid-guard.js +700 -0
  47. package/skills/run-agent-grid/scripts/grid-guard.test.ts +1090 -0
  48. package/skills/run-agent-grid/scripts/grid-guard.ts +869 -0
  49. package/src/package-dirs.ts +8 -0
  50. package/tsconfig.base.json +22 -0
  51. package/tsconfig.json +6 -24
  52. package/tsconfig.pkg.json +3 -1
  53. package/tsconfig.skills.json +14 -0
  54. package/tsconfig.src.json +12 -0
@@ -0,0 +1,181 @@
1
+ ---
2
+ name: run-agent-grid
3
+ description: "Run a paid or counted agent experiment grid end to end — units × arms × seeds (× agent stacks), one agent CLI run per cell in a fresh working copy, scored by an outcome oracle — without the failures that silently spoil one: an unapproved or hand-picked sample, a timeout that kills a slower model, two drivers sharing a working copy, secrets in transcripts, a classifier that mislabels outcomes, a session cap that kills the run, results lost from scratch space. Use when a study design exists and you are about to spend money or count runs: the registry and approval gate, the sandbox and secret probe, the smoke per model, the locked detached driver, the overlap audit, hand labels and the commit of finished runs. Ships grid-guard.js (lock, gate, overlap audit, env probe, key scan, pre-commit). Composes with build-benchmark (the design, upstream), build-label-page (hand labels) and osf-artifact-upload (the artifact)."
4
+ allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, Agent]
5
+ ---
6
+
7
+ <!-- vigiles:sha256:aef4152e680a2ccd compiled from skills/run-agent-grid/SKILL.md.spec.ts -->
8
+
9
+ # run-agent-grid — running a counted agent grid without spoiling it
10
+
11
+ **What a grid is here.** Units under test × conditions (arms) × seeds, repeated per agent stack
12
+ (model + CLI). Each cell is one agent CLI run in a fresh working copy of the unit; a scorer reads
13
+ what the run left behind, and an outcome oracle decides the cell's result. `build-benchmark` designs
14
+ the study and the artifact; this skill is the **operational procedure for running it**, from the
15
+ first paid call to the last committed run.
16
+
17
+ Every rule below was paid for by a failure. Each is stated with that failure and with what HOLDS it:
18
+ a refusal in `skills/run-agent-grid/scripts/grid-guard.ts` (run as the compiled `scripts/grid-guard.js`), a commit hook, a probe — or, said plainly, **prose only**,
19
+ which means it holds only as long as someone reads this. The full catalog, one entry per failure:
20
+ [`references/failure-catalog.md`](references/failure-catalog.md). The registry, the attempt ledger
21
+ and a driver loop that calls the guard: [`references/registry-and-ledger.md`](references/registry-and-ledger.md).
22
+
23
+ **Exit codes of every guard command:** 0 allowed or clean · 1 refused or a finding · 2 usage ·
24
+ 3 (`gate` only) the cell is already counted, skip it.
25
+
26
+ ## The order — each gate is passed before the next phase starts
27
+
28
+ ### 1. The registry: what may be measured, written as data (catalog 1, 3, 14)
29
+
30
+ - Every measurement is a row in a **machine-readable registry** (`grid.json`): each unit with its
31
+ role (`counted`, `smoke`, `excluded`), the author's requirement on it **verbatim**, the arms, the
32
+ seeds, the models, the budget cap. A requirement that lives only in a chat or a design note is not
33
+ a requirement: a driver cannot read it, and a whole grid can run on one easy, hand-picked unit.
34
+ - **The frame is the whole population** of the measured kind. Never "where our instrument can
35
+ fire": that filter shrinks the frame to trivial units. Allowed exclusions are listed per unit, each
36
+ with a reason, and counted. The draw is seeded, its seed and script committed **before anyone looks
37
+ at the drawn units**; a stratified draw is proportional to the frame.
38
+ - **Only the committed registry allows a run.** An added model, seed or arm is a dated addendum,
39
+ committed before its first run. Report models as columns, never pooled.
40
+ - **Held by:** `gate` reads the registry from `HEAD` and refuses when the working copy differs from
41
+ it, when the cell is not declared, when an excluded unit has no reason, when a counted run has no
42
+ draw seed. **Prose only:** that the frame is the whole population, and that the draw is
43
+ proportional; the guard sees the registry, not the population behind it.
44
+
45
+ ### 2. The author's approval of the drawn units (catalog 2)
46
+
47
+ - The **author** approves the drawn units — in their own words, with the time and the channel
48
+ (chat, message, review comment) recorded in `approval`. The agent that designed or drew the sample
49
+ never fills this field, not even to "save a step".
50
+ - **Held by:** `gate` refuses a counted run unless `approval.by` equals the registry's `author` and
51
+ `at` and `channel` are filled. **Prose only:** who typed it — the guard cannot tell the author
52
+ from an agent writing the author's name. Make it reviewable: the approval lands in its own commit,
53
+ quoting the author's message.
54
+
55
+ ### 3. The sandbox: secrets out, tools in, per unit (catalog 8, 9, 10)
56
+
57
+ - The agent's environment **excludes secrets by name pattern**, and a names-only probe passes before
58
+ any model call. Transcripts capture environment dumps; a secret in the environment ends up in a
59
+ committed transcript.
60
+ - The same probe proves that **what the agent needs still works**: an exclude-by-pattern policy also
61
+ strips variables git needs (`GIT_CONFIG_KEY_n` matches "KEY"). Prove git commits inside the sandbox.
62
+ - **Per unit, with no model call:** the tools the agent is meant to use are visible inside the
63
+ sandbox, the instrument fires on the pinned state of the unit, and it accepts the reference fix.
64
+ A shared tools directory that is invisible inside the sandbox turns every run of an arm into a
65
+ run without the tool.
66
+ - **Held by:** run inside the sandbox, as the agent would run —
67
+
68
+ ```bash
69
+ node ${CLAUDE_SKILL_DIR}/scripts/grid-guard.js env-probe --git
70
+ ```
71
+
72
+ It prints secret-looking variable NAMES (never values) and exits 1; `--allow NAME` accepts one by
73
+ name after you have looked at it. `gate` refuses a counted unit without `proof` (`toolVisible`,
74
+ `firesOnPinned`, `acceptsReference`, all `true`). **Prose only:** how the proof is taken — the
75
+ three facts are recorded by your proof script; the guard only refuses their absence.
76
+
77
+ ### 4. The smoke, per agent stack (catalog 5, 13)
78
+
79
+ - Before each model's grid: **one run per arm**, each **read end to end** — the transcript, the diff,
80
+ the scorer's output — not only its exit code. A timeout fit for one model kills a slower one
81
+ mid-task, and a killed run reads as a failure of the arm.
82
+ - From the smoke, write into the registry, per model, `smoke.timeoutSec` and `smoke.costPerRun`
83
+ (measured, with reserve), and commit it **before the grid**. A model can cost a multiple of its
84
+ planned price; the cap and the stop rule are written down before the first counted run.
85
+ - **Held by:** `gate` refuses a counted run on a model without `smoke.timeoutSec` and
86
+ `smoke.costPerRun`, hands the driver `timeoutSec` in its output, and refuses when the spend in the
87
+ ledger plus the next run's cost would pass `budget.cap`. **Prose only:** that the smoke runs were
88
+ actually read.
89
+
90
+ ### 5. The run: locked, detached, resumable (catalog 6, 7, 12, 13)
91
+
92
+ - **One lock per working-copy root.** Two drivers on one scratch path delete each other's trees;
93
+ one crashes, the other's runs are silently contaminated. A second concurrent driver gets **its own
94
+ root**.
95
+
96
+ ```bash
97
+ node ${CLAUDE_SKILL_DIR}/scripts/grid-guard.js lock <root> --owner <driver-id> --pid $$
98
+ node ${CLAUDE_SKILL_DIR}/scripts/grid-guard.js release <root> --owner <driver-id>
99
+ ```
100
+
101
+ The lock is a file beside the root. A live holder → refused. A holder whose process is gone on
102
+ the same host → taken over, and said so. A holder on another host → refused (it cannot be judged).
103
+ The holder must be detached — see the next point.
104
+ - **Before every cell, ask the gate** and obey its exit code; 3 means the cell is already counted,
105
+ or reserved by another attempt — skip it. That makes a killed driver resumable: run it again and
106
+ it continues.
107
+
108
+ ```bash
109
+ node ${CLAUDE_SKILL_DIR}/scripts/grid-guard.js gate --registry grid.json --ledger attempts.jsonl --unit <u> --arm <a> --model <m> --seed <s> --attempt <id>
110
+ ```
111
+
112
+ An allowed cell is **reserved** for `<id>` in the ledger in the same step, under the ledger's
113
+ lock: two drivers asking at once cannot both get one cell, nor both fit under the cap, because a
114
+ reservation holds the cell and counts its estimated cost until the attempt's end record (same
115
+ id) replaces it, or until it lapses (`--reserve-sec`, default six hours).
116
+
117
+ - **Never delete shared inputs from a step.** Templates, fixtures and pinned states are read-only
118
+ (`chmod -R a-w` them); a packing step that deletes what it packed removes what another driver
119
+ still reads. **Prose only**, held by file permissions where you set them.
120
+ - **Run the driver detached** (`setsid nohup … &`, its log to a file), never as a task tracked by
121
+ the agent session: a session's time cap kills a long grid. A separate waiter (a scheduled wake-up,
122
+ a loop that polls the ledger) brings the session back. **Held by `lock`:** it refuses a holder
123
+ that is not the leader of its own session (Linux `/proc/<pid>/stat`; true after `setsid`) and
124
+ names the command to use instead; `--attached` allows a short run, said out loud; without
125
+ `/proc` it warns and passes.
126
+ - The driver appends a ledger line when an attempt starts and another when it ends (same `attempt`
127
+ id, `root`, `start`/`end`, `status`, `cost`). Format: `references/registry-and-ledger.md`.
128
+
129
+ ### 6. After any incident — and before reporting — the overlap audit (catalog 6)
130
+
131
+ ```bash
132
+ node ${CLAUDE_SKILL_DIR}/scripts/grid-guard.js overlap attempts.jsonl
133
+ ```
134
+
135
+ Lists every pair of attempts that shared a root at the same time (an attempt with no end overlaps
136
+ everything after its start), where at least one is counted. Each contaminated attempt is marked
137
+ `"status": "harness-error"` — the record is **kept**, not deleted, and not counted — and its cell
138
+ runs again as a **new** attempt. Run it after any crash, kill or harness change, and before the
139
+ numbers are reported.
140
+
141
+ ### 7. Scoring and labels (catalog 4, 11)
142
+
143
+ - **A check reads every stratum's expectation file.** When a new stratum's expected values live in a
144
+ second file, a proof or precondition check that reads only the first reports false FAILs. Load
145
+ expectations through one function that enumerates them, and fail when a unit's stratum has none.
146
+ A precondition failure is investigated before it is trusted — either way. **Prose only.**
147
+ - **Every run whose outcome is not mechanically certain is read in full and labelled by hand.** An
148
+ automatic outcome classifier misses classes (a deferral phrased without a question mark); its
149
+ label is a pre-sort, and the disagreements between it and the hand labels are reported. The
150
+ procedure, with a label-file validator and an agreement report: `label-agent-runs`.
151
+
152
+ ### 8. Commit finished runs, often (catalog 15, 8)
153
+
154
+ - Results in scratch space vanish. Commit **finished** runs — those with a completion record —
155
+ frequently; never a half-written run folder.
156
+ - Every committed artifact is scanned for key-shaped strings, by a pre-commit hook:
157
+
158
+ ```bash
159
+ node ${CLAUDE_SKILL_DIR}/scripts/grid-guard.js precommit --runs runs --done done.json
160
+ ```
161
+
162
+ It reads the **staged** content (the index is what commits), refuses key-shaped strings by file and
163
+ line without printing them, and refuses a staged run folder that has no completion record. Before
164
+ an artifact leaves the repository: `node ${CLAUDE_SKILL_DIR}/scripts/grid-guard.js scan-keys <dir>`.
165
+ - **Report the spend**: total, per model, against the cap.
166
+
167
+ ## When something goes wrong mid-grid
168
+
169
+ Stop the driver; do not fix forward on a running grid. Write the incident down (what, when, which
170
+ attempts), run the overlap audit, mark contaminated attempts, fix the harness, re-run the smoke for
171
+ any stack the fix touched, then resume. A harness change is an addendum like any other.
172
+
173
+ ## Compose with
174
+
175
+ - **build-benchmark** (upstream) — the design, the statistics and the artifact this run feeds.
176
+ - **label-agent-runs** (downstream) — the outcome labels: codebook first, hand labels for every
177
+ uncertain run, a blind second rater, labels frozen after the analysis.
178
+ - **build-label-page** — the page a person labels those runs on.
179
+ - **osf-artifact-upload** — the artifact, after `scan-keys` is clean.
180
+ - An **independent refutation pass** (a second agent framed to refute, not to confirm) on the
181
+ registry and the smoke before the first counted run.
@@ -0,0 +1,184 @@
1
+ import { experimental_skill, file, prose } from "vigiles/spec";
2
+ import { COMMANDS } from "./scripts/grid-guard-commands.ts";
3
+
4
+ export default experimental_skill({
5
+ name: "run-agent-grid",
6
+ description:
7
+ "Run a paid or counted agent experiment grid end to end — units × arms × seeds (× agent stacks), one agent CLI run per cell in a fresh working copy, scored by an outcome oracle — without the failures that silently spoil one: an unapproved or hand-picked sample, a timeout that kills a slower model, two drivers sharing a working copy, secrets in transcripts, a classifier that mislabels outcomes, a session cap that kills the run, results lost from scratch space. Use when a study design exists and you are about to spend money or count runs: the registry and approval gate, the sandbox and secret probe, the smoke per model, the locked detached driver, the overlap audit, hand labels and the commit of finished runs. Ships grid-guard.js (lock, gate, overlap audit, env probe, key scan, pre-commit). Composes with build-benchmark (the design, upstream), build-label-page (hand labels) and osf-artifact-upload (the artifact).",
8
+ tools: ["Read", "Write", "Edit", "Grep", "Glob", "Bash", "Agent"],
9
+ body: prose`
10
+ # run-agent-grid — running a counted agent grid without spoiling it
11
+
12
+ **What a grid is here.** Units under test × conditions (arms) × seeds, repeated per agent stack
13
+ (model + CLI). Each cell is one agent CLI run in a fresh working copy of the unit; a scorer reads
14
+ what the run left behind, and an outcome oracle decides the cell's result. \`build-benchmark\` designs
15
+ the study and the artifact; this skill is the **operational procedure for running it**, from the
16
+ first paid call to the last committed run.
17
+
18
+ Every rule below was paid for by a failure. Each is stated with that failure and with what HOLDS it:
19
+ a refusal in ${file("skills/run-agent-grid/scripts/grid-guard.ts")} (run as the compiled \`scripts/grid-guard.js\`), a commit hook, a probe — or, said plainly, **prose only**,
20
+ which means it holds only as long as someone reads this. The full catalog, one entry per failure:
21
+ [\`references/failure-catalog.md\`](references/failure-catalog.md). The registry, the attempt ledger
22
+ and a driver loop that calls the guard: [\`references/registry-and-ledger.md\`](references/registry-and-ledger.md).
23
+
24
+ **Exit codes of every guard command:** 0 allowed or clean · 1 refused or a finding · 2 usage ·
25
+ 3 (\`${COMMANDS.gate}\` only) the cell is already counted, skip it.
26
+
27
+ ## The order — each gate is passed before the next phase starts
28
+
29
+ ### 1. The registry: what may be measured, written as data (catalog 1, 3, 14)
30
+
31
+ - Every measurement is a row in a **machine-readable registry** (\`grid.json\`): each unit with its
32
+ role (\`counted\`, \`smoke\`, \`excluded\`), the author's requirement on it **verbatim**, the arms, the
33
+ seeds, the models, the budget cap. A requirement that lives only in a chat or a design note is not
34
+ a requirement: a driver cannot read it, and a whole grid can run on one easy, hand-picked unit.
35
+ - **The frame is the whole population** of the measured kind. Never "where our instrument can
36
+ fire": that filter shrinks the frame to trivial units. Allowed exclusions are listed per unit, each
37
+ with a reason, and counted. The draw is seeded, its seed and script committed **before anyone looks
38
+ at the drawn units**; a stratified draw is proportional to the frame.
39
+ - **Only the committed registry allows a run.** An added model, seed or arm is a dated addendum,
40
+ committed before its first run. Report models as columns, never pooled.
41
+ - **Held by:** \`${COMMANDS.gate}\` reads the registry from \`HEAD\` and refuses when the working copy differs from
42
+ it, when the cell is not declared, when an excluded unit has no reason, when a counted run has no
43
+ draw seed. **Prose only:** that the frame is the whole population, and that the draw is
44
+ proportional; the guard sees the registry, not the population behind it.
45
+
46
+ ### 2. The author's approval of the drawn units (catalog 2)
47
+
48
+ - The **author** approves the drawn units — in their own words, with the time and the channel
49
+ (chat, message, review comment) recorded in \`approval\`. The agent that designed or drew the sample
50
+ never fills this field, not even to "save a step".
51
+ - **Held by:** \`${COMMANDS.gate}\` refuses a counted run unless \`approval.by\` equals the registry's \`author\` and
52
+ \`at\` and \`channel\` are filled. **Prose only:** who typed it — the guard cannot tell the author
53
+ from an agent writing the author's name. Make it reviewable: the approval lands in its own commit,
54
+ quoting the author's message.
55
+
56
+ ### 3. The sandbox: secrets out, tools in, per unit (catalog 8, 9, 10)
57
+
58
+ - The agent's environment **excludes secrets by name pattern**, and a names-only probe passes before
59
+ any model call. Transcripts capture environment dumps; a secret in the environment ends up in a
60
+ committed transcript.
61
+ - The same probe proves that **what the agent needs still works**: an exclude-by-pattern policy also
62
+ strips variables git needs (\`GIT_CONFIG_KEY_n\` matches "KEY"). Prove git commits inside the sandbox.
63
+ - **Per unit, with no model call:** the tools the agent is meant to use are visible inside the
64
+ sandbox, the instrument fires on the pinned state of the unit, and it accepts the reference fix.
65
+ A shared tools directory that is invisible inside the sandbox turns every run of an arm into a
66
+ run without the tool.
67
+ - **Held by:** run inside the sandbox, as the agent would run —
68
+
69
+ \`\`\`bash
70
+ node \${CLAUDE_SKILL_DIR}/scripts/grid-guard.js ${COMMANDS.envProbe} --git
71
+ \`\`\`
72
+
73
+ It prints secret-looking variable NAMES (never values) and exits 1; \`--allow NAME\` accepts one by
74
+ name after you have looked at it. \`${COMMANDS.gate}\` refuses a counted unit without \`proof\` (\`toolVisible\`,
75
+ \`firesOnPinned\`, \`acceptsReference\`, all \`true\`). **Prose only:** how the proof is taken — the
76
+ three facts are recorded by your proof script; the guard only refuses their absence.
77
+
78
+ ### 4. The smoke, per agent stack (catalog 5, 13)
79
+
80
+ - Before each model's grid: **one run per arm**, each **read end to end** — the transcript, the diff,
81
+ the scorer's output — not only its exit code. A timeout fit for one model kills a slower one
82
+ mid-task, and a killed run reads as a failure of the arm.
83
+ - From the smoke, write into the registry, per model, \`smoke.timeoutSec\` and \`smoke.costPerRun\`
84
+ (measured, with reserve), and commit it **before the grid**. A model can cost a multiple of its
85
+ planned price; the cap and the stop rule are written down before the first counted run.
86
+ - **Held by:** \`${COMMANDS.gate}\` refuses a counted run on a model without \`smoke.timeoutSec\` and
87
+ \`smoke.costPerRun\`, hands the driver \`timeoutSec\` in its output, and refuses when the spend in the
88
+ ledger plus the next run's cost would pass \`budget.cap\`. **Prose only:** that the smoke runs were
89
+ actually read.
90
+
91
+ ### 5. The run: locked, detached, resumable (catalog 6, 7, 12, 13)
92
+
93
+ - **One lock per working-copy root.** Two drivers on one scratch path delete each other's trees;
94
+ one crashes, the other's runs are silently contaminated. A second concurrent driver gets **its own
95
+ root**.
96
+
97
+ \`\`\`bash
98
+ node \${CLAUDE_SKILL_DIR}/scripts/grid-guard.js ${COMMANDS.lock} <root> --owner <driver-id> --pid $$
99
+ node \${CLAUDE_SKILL_DIR}/scripts/grid-guard.js ${COMMANDS.release} <root> --owner <driver-id>
100
+ \`\`\`
101
+
102
+ The lock is a file beside the root. A live holder → refused. A holder whose process is gone on
103
+ the same host → taken over, and said so. A holder on another host → refused (it cannot be judged).
104
+ The holder must be detached — see the next point.
105
+ - **Before every cell, ask the gate** and obey its exit code; 3 means the cell is already counted,
106
+ or reserved by another attempt — skip it. That makes a killed driver resumable: run it again and
107
+ it continues.
108
+
109
+ \`\`\`bash
110
+ node \${CLAUDE_SKILL_DIR}/scripts/grid-guard.js ${COMMANDS.gate} --registry grid.json --ledger attempts.jsonl --unit <u> --arm <a> --model <m> --seed <s> --attempt <id>
111
+ \`\`\`
112
+
113
+ An allowed cell is **reserved** for \`<id>\` in the ledger in the same step, under the ledger's
114
+ lock: two drivers asking at once cannot both get one cell, nor both fit under the cap, because a
115
+ reservation holds the cell and counts its estimated cost until the attempt's end record (same
116
+ id) replaces it, or until it lapses (\`--reserve-sec\`, default six hours).
117
+
118
+ - **Never delete shared inputs from a step.** Templates, fixtures and pinned states are read-only
119
+ (\`chmod -R a-w\` them); a packing step that deletes what it packed removes what another driver
120
+ still reads. **Prose only**, held by file permissions where you set them.
121
+ - **Run the driver detached** (\`setsid nohup … &\`, its log to a file), never as a task tracked by
122
+ the agent session: a session's time cap kills a long grid. A separate waiter (a scheduled wake-up,
123
+ a loop that polls the ledger) brings the session back. **Held by \`${COMMANDS.lock}\`:** it refuses a holder
124
+ that is not the leader of its own session (Linux \`/proc/<pid>/stat\`; true after \`setsid\`) and
125
+ names the command to use instead; \`--attached\` allows a short run, said out loud; without
126
+ \`/proc\` it warns and passes.
127
+ - The driver appends a ledger line when an attempt starts and another when it ends (same \`attempt\`
128
+ id, \`root\`, \`start\`/\`end\`, \`status\`, \`cost\`). Format: \`references/registry-and-ledger.md\`.
129
+
130
+ ### 6. After any incident — and before reporting — the overlap audit (catalog 6)
131
+
132
+ \`\`\`bash
133
+ node \${CLAUDE_SKILL_DIR}/scripts/grid-guard.js ${COMMANDS.overlap} attempts.jsonl
134
+ \`\`\`
135
+
136
+ Lists every pair of attempts that shared a root at the same time (an attempt with no end overlaps
137
+ everything after its start), where at least one is counted. Each contaminated attempt is marked
138
+ \`"status": "harness-error"\` — the record is **kept**, not deleted, and not counted — and its cell
139
+ runs again as a **new** attempt. Run it after any crash, kill or harness change, and before the
140
+ numbers are reported.
141
+
142
+ ### 7. Scoring and labels (catalog 4, 11)
143
+
144
+ - **A check reads every stratum's expectation file.** When a new stratum's expected values live in a
145
+ second file, a proof or precondition check that reads only the first reports false FAILs. Load
146
+ expectations through one function that enumerates them, and fail when a unit's stratum has none.
147
+ A precondition failure is investigated before it is trusted — either way. **Prose only.**
148
+ - **Every run whose outcome is not mechanically certain is read in full and labelled by hand.** An
149
+ automatic outcome classifier misses classes (a deferral phrased without a question mark); its
150
+ label is a pre-sort, and the disagreements between it and the hand labels are reported. The
151
+ procedure, with a label-file validator and an agreement report: \`label-agent-runs\`.
152
+
153
+ ### 8. Commit finished runs, often (catalog 15, 8)
154
+
155
+ - Results in scratch space vanish. Commit **finished** runs — those with a completion record —
156
+ frequently; never a half-written run folder.
157
+ - Every committed artifact is scanned for key-shaped strings, by a pre-commit hook:
158
+
159
+ \`\`\`bash
160
+ node \${CLAUDE_SKILL_DIR}/scripts/grid-guard.js ${COMMANDS.precommit} --runs runs --done done.json
161
+ \`\`\`
162
+
163
+ It reads the **staged** content (the index is what commits), refuses key-shaped strings by file and
164
+ line without printing them, and refuses a staged run folder that has no completion record. Before
165
+ an artifact leaves the repository: \`node \${CLAUDE_SKILL_DIR}/scripts/grid-guard.js ${COMMANDS.scanKeys} <dir>\`.
166
+ - **Report the spend**: total, per model, against the cap.
167
+
168
+ ## When something goes wrong mid-grid
169
+
170
+ Stop the driver; do not fix forward on a running grid. Write the incident down (what, when, which
171
+ attempts), run the overlap audit, mark contaminated attempts, fix the harness, re-run the smoke for
172
+ any stack the fix touched, then resume. A harness change is an addendum like any other.
173
+
174
+ ## Compose with
175
+
176
+ - **build-benchmark** (upstream) — the design, the statistics and the artifact this run feeds.
177
+ - **label-agent-runs** (downstream) — the outcome labels: codebook first, hand labels for every
178
+ uncertain run, a blind second rater, labels frozen after the analysis.
179
+ - **build-label-page** — the page a person labels those runs on.
180
+ - **osf-artifact-upload** — the artifact, after \`${COMMANDS.scanKeys}\` is clean.
181
+ - An **independent refutation pass** (a second agent framed to refute, not to confirm) on the
182
+ registry and the smoke before the first counted run.
183
+ `,
184
+ });
@@ -0,0 +1,36 @@
1
+ # Failure catalog — what went wrong in real grids, the rule, and what holds it
2
+
3
+ Each entry: the failure as it happened (generic), the rule it produced, and what holds the rule.
4
+ "Prose only" means nothing refuses the mistake; the rule holds while someone reads it. A new failure
5
+ gets a new entry here, and the entry names its mechanism or says that it has none.
6
+
7
+ `grid-guard` is `scripts/grid-guard.js` in this skill.
8
+
9
+ | # | failure | rule | held by |
10
+ | --- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | 1 | The author's requirement on which units to test lived only in prose; a large batch of paid runs used one easy, hand-picked unit. | Every measurement is a row in a machine-readable registry (unit, role, requirement verbatim), checked before any paid run. | `grid-guard gate`: refuses a unit, arm, model or seed the committed registry does not declare. |
12
+ | 2 | The agent that designed a sample filled the author-approval field itself. | Only the author approves the drawn units, recorded with time and channel. | `gate` refuses a counted run unless `approval.by` = `author` with `at` and `channel`. **Prose only:** who typed it. |
13
+ | 3 | Units were filtered by "where our instrument can fire", which shrank the frame to trivial units. | The frame is the whole population of the measured kind; exclusions listed per unit with a reason and counted; seeded draw committed before anyone looks; stratified only in proportion. | `gate` refuses an excluded unit, an excluded unit without a reason, and a counted run without a recorded draw seed. **Prose only:** the frame and the proportion. |
14
+ | 4 | A new stratum's expected values lived in a second file; two proof/precondition checks read only the first and reported false FAILs. | Checks read every stratum's expectation file; a precondition failure is investigated before it is trusted, either way. | **Prose only.** Load expectations through one enumerating function that fails on a stratum with none. |
15
+ | 5 | A timeout fit for one model killed smoke runs of a slower model mid-task. | Smoke per stack — one run per arm, read end to end; timeout and cost reserve set per model from it and written down before the grid. | `gate` refuses a counted run on a model without `smoke.timeoutSec` / `smoke.costPerRun` and hands the driver the timeout. **Prose only:** the reading. |
16
+ | 6 | Two drivers ran at once on the same scratch path; one deleted the other's working tree: a crash, and several silently contaminated runs. | One lock per working-copy root; a second driver gets its own root; after any incident, audit every attempt's window for overlap; contaminated attempts marked `harness-error` (kept, not counted) and re-run as new attempts. | `grid-guard lock` / `release` (live holder refused, stale holder on this host taken over); `grid-guard overlap` over the ledger; `gate` re-runs a cell whose only attempts are harness errors. |
17
+ | 7 | A packing step that deleted shared templates after packing them would have removed templates another driver still needed. | Never delete shared inputs from a step; templates read-only. | **Prose only**, held by file permissions (`chmod -R a-w`) where set. |
18
+ | 8 | The agent's transcript captured environment secrets (an environment dump). | The sandbox's environment policy excludes secrets by name pattern; a names-only probe passes before any model run; every committed artifact is scanned for key patterns. | `grid-guard env-probe` (names, never values); `grid-guard precommit` as a pre-commit hook; `grid-guard scan-keys` before an artifact leaves. |
19
+ | 9 | The secret-excluding policy also stripped variables git needed, breaking git inside the sandbox. | The probe also proves that the tools the agent needs still work. | `grid-guard env-probe --git`: `git init`, `add` and `commit` in a scratch repository, inside the sandbox. |
20
+ | 10 | A shared tools directory was invisible inside the agent's sandbox. | Per-unit sandbox proof, no model call, before the grid: tool visible, instrument fires on the pinned state, accepts the reference fix. | `gate` refuses a counted unit without `proof` (three `true` facts). **Prose only:** how the proof is taken. |
21
+ | 11 | The automatic outcome classifier missed a class of outcomes (deferrals phrased without a question mark). | Every run whose outcome is not mechanically certain is read in full and hand-labelled; the auto label is a pre-sort; disagreements reported. | `label-agent-runs`: `labels.js validate --ledger` refuses a completed run without a label; `labels.js agree` lists every auto-vs-hand disagreement. **Prose only:** the reading. |
22
+ | 12 | A long grid was killed by the session's time cap on tracked background tasks. | Run the driver detached; a separate waiter wakes the session; the driver resumes, skipping counted cells. | `grid-guard lock` refuses a holder that is not its own session leader (`/proc/<pid>/stat` session = pid, as after `setsid`), naming `setsid nohup <cmd> &`; `--attached` for a short run; warns and passes without `/proc`. `gate` exit 3 = already counted, so a restarted driver resumes. |
23
+ | 13 | A model cost a multiple of its planned price. | Per-run cost measured in the smoke; hard cap in the driver; stop rule written before the grid; spend reported. | `gate` refuses when ledger spend + the next run's smoke cost passes `budget.cap`. **Prose only:** the report. |
24
+ | 14 | Adding a model or seed after seeing early results invites forking paths. | Any added model, seed or arm is a dated addendum committed before its first run; models reported as columns, never pooled. | `gate` reads the registry from `HEAD` and refuses while the working copy differs. **Prose only:** columns, not pooled. |
25
+ | 15 | Results lived in scratch space that vanishes. | Commit finished runs (only those with a completion record) frequently; never half-written run folders. | `grid-guard precommit --runs <dir> --done <file>` refuses a staged run folder without its completion record. **Prose only:** "frequently". |
26
+
27
+ ## What the guard does not see
28
+
29
+ - **Who** wrote a field. `approval.by` is compared with `author`; an agent that writes the author's
30
+ name passes. Keep the approval in its own commit, quoting the author's message.
31
+ - **The population** behind the registry. The guard checks the registry's own consistency, not that
32
+ the frame was the whole population.
33
+ - **Key shapes it does not know.** `scan-keys` matches forms (key blocks, token prefixes,
34
+ `NAME=value` with a secret-looking name). A credential in an unknown shape passes; the environment
35
+ probe, which works on names, is the first line.
36
+ - **Whether a run was read.** Nothing mechanical tells a read transcript from an unread one.
@@ -0,0 +1,142 @@
1
+ # The registry, the attempt ledger, and a driver that obeys the guard
2
+
3
+ `grid-guard` is `scripts/grid-guard.js` in this skill; every command below is run with
4
+ `node ${CLAUDE_SKILL_DIR}/scripts/grid-guard.js`. Exit codes: 0 allowed or clean, 1 refused or a
5
+ finding, 2 usage, 3 (`gate`) already counted.
6
+
7
+ ## The registry — `grid.json`, committed
8
+
9
+ ```json
10
+ {
11
+ "author": "the-author-handle",
12
+ "draw": {
13
+ "population": "every unit of the measured kind, and where the list came from",
14
+ "seed": 7349,
15
+ "script": "draw.py"
16
+ },
17
+ "arms": ["control", "treatment"],
18
+ "seeds": [1, 2, 3],
19
+ "budget": {
20
+ "cap": 300,
21
+ "currency": "USD",
22
+ "stopRule": "stop at the cap; report what ran"
23
+ },
24
+ "models": [
25
+ {
26
+ "id": "model-a",
27
+ "smoke": {
28
+ "timeoutSec": 2400,
29
+ "costPerRun": 1.8,
30
+ "readBy": "who read the smoke runs",
31
+ "at": "ISO time"
32
+ }
33
+ }
34
+ ],
35
+ "units": [
36
+ {
37
+ "id": "unit-017",
38
+ "role": "counted",
39
+ "stratum": "s2",
40
+ "requirement": "the author's requirement on this unit, verbatim",
41
+ "approval": {
42
+ "by": "the-author-handle",
43
+ "at": "ISO time",
44
+ "channel": "where the author said it"
45
+ },
46
+ "proof": {
47
+ "toolVisible": true,
48
+ "firesOnPinned": true,
49
+ "acceptsReference": true,
50
+ "at": "ISO time"
51
+ }
52
+ },
53
+ { "id": "unit-smoke", "role": "smoke" },
54
+ { "id": "unit-031", "role": "excluded", "reason": "why, in one line" }
55
+ ]
56
+ }
57
+ ```
58
+
59
+ What `gate` requires, field by field:
60
+
61
+ | field | for | refused when |
62
+ | -------------------------------------------- | ------------ | ------------------------------------------------------------------ |
63
+ | the file itself | every run | not in git, not committed, or the working copy differs from `HEAD` |
64
+ | `units[].id`, `arms`, `models[].id`, `seeds` | every run | the cell's unit, arm, model or seed is not declared |
65
+ | `units[].role` | every run | anything but `counted` or `smoke` |
66
+ | `units[].reason` | every run | an `excluded` unit has none |
67
+ | `budget.cap` | every run | missing, or ledger spend + next cost would pass it |
68
+ | `units[].approval` | counted runs | `by` ≠ `author`, or `at` / `channel` empty |
69
+ | `units[].proof` | counted runs | any of the three facts is not `true` |
70
+ | `draw.seed` | counted runs | missing |
71
+ | `models[].smoke` | counted runs | `timeoutSec` or `costPerRun` is not a number |
72
+
73
+ On success `gate` prints one JSON line: `{"allowed":true,"cell":{…},"attempt":"…","counted":…,"timeoutSec":…,"spent":…}`,
74
+ and has already appended a `reserved` record for that attempt to the ledger (read, check and append
75
+ under `<ledger>.lock`, so concurrent drivers cannot both take one cell or both fit under the cap).
76
+ The driver takes the run's timeout from `timeoutSec`.
77
+
78
+ ## The attempt ledger — `attempts.jsonl`, append-only
79
+
80
+ One JSON object per line. `gate` writes the first line of an attempt (a reservation); the driver
81
+ writes a line when the attempt **starts** and another when it **ends**, all with the same `attempt`
82
+ id; later lines add to earlier ones. A crashed attempt therefore stays on record with a start and
83
+ no end, and its reservation lapses at `expires`.
84
+
85
+ ```json
86
+ {"attempt":"a-0042","unit":"unit-017","arm":"control","model":"model-a","seed":"1","status":"reserved","cost":1.8,"start":"2026-01-01T09:59:58Z","expires":"2026-01-01T15:59:58Z"}
87
+ {"attempt":"a-0042","agent":"cli-a","model":"model-a","unit":"unit-017","arm":"control","seed":1,"root":"/scratch/w1","start":"2026-01-01T10:00:00Z"}
88
+ {"attempt":"a-0042","end":"2026-01-01T10:31:00Z","status":"complete","cost":1.62}
89
+ ```
90
+
91
+ | field | meaning |
92
+ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
93
+ | `status` | `reserved` — held by `gate` until the end record or `expires`; `complete` — counted, whatever the outcome; `harness-error` — kept on record, not counted, re-run |
94
+ | `root` | the working-copy root the attempt used: what `overlap` compares |
95
+ | `agent` | the agent CLI that ran; with `model` and `<unit>/<arm>/<seed>` it names the cell a label refers to |
96
+ | `cost` | what the attempt cost, counted toward the cap even for a harness error |
97
+
98
+ The outcome itself lives in the run folder (`runs/<attempt>/`), whose completion record
99
+ (`done.json` by default) is written **last**.
100
+
101
+ ## A driver loop
102
+
103
+ ```bash
104
+ #!/usr/bin/env bash
105
+ set -uo pipefail
106
+ G="node ${CLAUDE_SKILL_DIR}/scripts/grid-guard.js"
107
+ ROOT=/scratch/w1 # this driver's own working-copy root
108
+ $G lock "$ROOT" --owner "driver-w1" --pid $$ || exit 1
109
+ trap '$G release "$ROOT" --owner driver-w1' EXIT
110
+
111
+ while read -r unit arm model seed; do
112
+ id="a-$(date +%s%N)-$$-$unit-$arm-$model-$seed"
113
+ verdict=$($G gate --registry grid.json --ledger attempts.jsonl \
114
+ --unit "$unit" --arm "$arm" --model "$model" --seed "$seed" --attempt "$id")
115
+ rc=$?
116
+ [ $rc -eq 3 ] && continue # counted, or reserved by another driver: skip it
117
+ [ $rc -ne 0 ] && exit $rc # refused: stop the grid, read why
118
+ timeout_sec=$(node -e 'console.log(JSON.parse(process.argv[1]).timeoutSec ?? 3600)' "$verdict")
119
+ # 1. start record 2. fresh working copy of the unit in $ROOT 3. the agent CLI under
120
+ # `timeout $timeout_sec` 4. the scorer 5. runs/$id/done.json LAST 6. end record
121
+ # (status complete or harness-error, real cost) — it releases the reservation
122
+ done < cells.txt
123
+ ```
124
+
125
+ Start it detached so the agent session's limits cannot kill it, and let something else wake the
126
+ session:
127
+
128
+ ```bash
129
+ setsid nohup ./driver.sh > driver.log 2>&1 < /dev/null &
130
+ ```
131
+
132
+ ## The pre-commit hook
133
+
134
+ `.git/hooks/pre-commit` (or the hook manager the repository already uses):
135
+
136
+ ```bash
137
+ #!/usr/bin/env bash
138
+ exec node "<path to this skill>/scripts/grid-guard.js" precommit --runs runs --done done.json
139
+ ```
140
+
141
+ A hook in `.git/hooks/` is not versioned; record in the study's README that it is installed, and
142
+ run the same command in CI if the repository has CI.
@@ -0,0 +1,59 @@
1
+ /**
2
+ * run-agent-grid — the PAID tier: does this skill's description fire on its own requests, and
3
+ * stay quiet on a sibling's?
4
+ *
5
+ * Declared here, whole: the prompts it must fire on, the prompts it must not, and the
6
+ * measurement — no shared table, no kit. `vigiles eval` discovers this file by its name and runs
7
+ * it; nothing runs at import.
8
+ *
9
+ * The should-not-fire prompts are the neighbours' territory, written inline: `build-benchmark`
10
+ * (designing the study), `build-label-page` (a page a person labels on), `label-agent-runs`
11
+ * (labelling the outcomes) and `draft-paper`. They are inline rather than imported from
12
+ * `label-agent-runs.eval.ts` because that file imports this one's prompts, and two evals that
13
+ * import each other's constants deadlock at module load.
14
+ *
15
+ * The skills it competes with are every skill this package ships (the `skills/` directory this
16
+ * file sits in), the same set in a checkout and in `node_modules`.
17
+ *
18
+ * ⚠️ NEVER RUN. Written with the skill; its rates are UNKNOWN, not assumed good.
19
+ *
20
+ * Costs money; not CI.
21
+ * npx vigiles eval skills/run-agent-grid/run-agent-grid.eval.ts
22
+ */
23
+ import { dirname } from "node:path";
24
+ import { fileURLToPath } from "node:url";
25
+ import { defineEval, skillResolved } from "vigiles";
26
+
27
+ /** The namespace vigiles installs a loose skills directory under. */
28
+ const LOOSE_NS = "vigiles-loose-skills";
29
+
30
+ /** Requests this skill owns. Exported: `label-agent-runs.eval.ts` uses them as its should-not-fire set. */
31
+ export const TRIGGER_PROMPTS: readonly string[] = [
32
+ "запускаю платную сетку прогонов агента — что проверить до первого запуска",
33
+ "the grid driver died halfway, how do I resume without re-running the counted cells",
34
+ "два драйвера писали в одну рабочую папку, какие прогоны теперь испорчены",
35
+ "gate every agent run on the approved sample and a hard budget cap before we spend money",
36
+ ];
37
+
38
+ export default defineEval({
39
+ measureTriggerRate: {
40
+ name: "run-agent-grid-trigger",
41
+ skillsDir: dirname(dirname(fileURLToPath(import.meta.url))),
42
+ prompts: TRIGGER_PROMPTS,
43
+ irrelevantPrompts: [
44
+ "надо спроектировать сам замер, а не описывать его — как считать честно",
45
+ "build a gold-labelling page for these 40 items so I can answer on my phone",
46
+ "which of the finished runs does a person have to read, and which can the classifier label",
47
+ "числа есть, пора писать текст",
48
+ ],
49
+ fired: (trace) => skillResolved(trace, `${LOOSE_NS}:run-agent-grid`),
50
+ // Four named phrasings per side, deliberately, as for every skill of this package.
51
+ minPrompts: 4,
52
+ // A grid in progress, so "the driver died" and "the registry" have something to refer to.
53
+ fixture: {
54
+ "grid.json": '{ "arms": ["control", "treatment"], "seeds": [1, 2] }\n',
55
+ "attempts.jsonl":
56
+ '{"attempt":"a1","unit":"u1","arm":"control","seed":1,"start":"2026-01-01T00:00:00Z"}\n',
57
+ },
58
+ },
59
+ });