opencode-harness-kit 0.4.3 → 0.4.5

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 (74) hide show
  1. package/CHANGELOG.md +569 -18
  2. package/CONTRIBUTING.md +249 -0
  3. package/GLOBAL.md +34 -5
  4. package/MIGRATION.md +151 -0
  5. package/README.md +354 -66
  6. package/SECURITY.md +30 -0
  7. package/bin/baseline.mjs +15 -7
  8. package/bin/git-hooks/pre-commit +41 -9
  9. package/bin/oc-harness.mjs +1268 -82
  10. package/dist/config.d.ts +127 -0
  11. package/dist/denylist.d.ts +33 -0
  12. package/dist/effort.d.ts +64 -0
  13. package/dist/guard.d.ts +23 -0
  14. package/dist/health.d.ts +43 -0
  15. package/dist/index.d.ts +9 -0
  16. package/dist/index.js +532 -149
  17. package/dist/plugin.d.ts +15 -0
  18. package/dist/profile.d.ts +399 -0
  19. package/dist/project.d.ts +8 -0
  20. package/dist/prune.d.ts +6 -0
  21. package/dist/router.d.ts +3 -0
  22. package/dist/rules.d.ts +109 -0
  23. package/dist/safe.d.ts +7 -0
  24. package/dist/telemetry-store.d.ts +12 -0
  25. package/dist/telemetry.d.ts +3 -0
  26. package/docs/EXIT-CODES.md +83 -0
  27. package/docs/KNOWN-LIMITS.md +45 -21
  28. package/docs/RESEARCH.md +20 -5
  29. package/docs/ROUTING.md +191 -31
  30. package/eval/RESULTS.md +457 -0
  31. package/eval/ab.mjs +743 -0
  32. package/eval/attribute.mjs +170 -0
  33. package/eval/flags.mjs +88 -0
  34. package/eval/measure-install.mjs +331 -0
  35. package/eval/measure-prune.mjs +339 -0
  36. package/eval/metrics.js +79 -0
  37. package/eval/models.mjs +302 -0
  38. package/eval/premises.mjs +200 -0
  39. package/eval/prepare.mjs +233 -0
  40. package/eval/run.mjs +376 -0
  41. package/eval/tasks-self.json +81 -0
  42. package/eval/tasks-ts.json +50 -0
  43. package/eval/tasks.json +83 -0
  44. package/eval/thresholds.mjs +134 -0
  45. package/eval/verify.mjs +287 -0
  46. package/package.json +44 -17
  47. package/profiles/fleet-all.json +6 -1
  48. package/profiles/fleet-safe.json +7 -3
  49. package/profiles/schema.json +182 -51
  50. package/prompts/build.md +4 -2
  51. package/skills/space-build/SKILL.md +4 -2
  52. package/skills/space-finish/SKILL.md +6 -2
  53. package/skills/space-plan/reference/task-template.md +13 -5
  54. package/src/config.js +182 -32
  55. package/src/denylist.js +95 -0
  56. package/src/driver/run.mjs +81 -16
  57. package/src/effort.js +15 -4
  58. package/src/guard.ts +165 -13
  59. package/src/health.js +80 -0
  60. package/src/index.ts +15 -4
  61. package/src/lane-usage.js +72 -18
  62. package/src/plugin-type-check.ts +38 -0
  63. package/src/plugin.ts +39 -0
  64. package/src/profile.js +225 -26
  65. package/src/project.js +47 -0
  66. package/src/prune.ts +110 -31
  67. package/src/router.ts +32 -63
  68. package/src/rules.js +99 -7
  69. package/src/safe.ts +27 -0
  70. package/src/sqlite.js +144 -0
  71. package/src/tasks.js +112 -29
  72. package/src/telemetry-store.ts +62 -0
  73. package/src/telemetry.ts +60 -57
  74. package/src/trust.js +70 -9
@@ -0,0 +1,457 @@
1
+ # Measured results
2
+
3
+ Numbers the kit actually produced on a real install, and how far to trust them.
4
+ Regenerate with:
5
+
6
+ npm run measure # human-readable table
7
+ npm run measure -- --json # machine-readable
8
+ npm run measure:prune # the pruner, on real sessions
9
+ npm run measure:check # the gate
10
+ npm run attribute # where the context actually went
11
+
12
+ The source is the install database, opened read-only. Nothing here is estimated
13
+ and nothing is estimated from a synthetic transcript: `eval/run.mjs` covers the
14
+ pruner and the allowlist, and cannot say anything about how the agent reads.
15
+
16
+ Everything in this file comes from the commands above. The A/B section at the
17
+ bottom runs the same task with and without the kit, ten times each, on both
18
+ arms at once - 120 agent runs, which is enough to be worth reading and not
19
+ enough to be worth trusting.
20
+
21
+ ## Two measurements, and the difference between them
22
+
23
+ **`npm run measure` is an OBSERVATION about one install.** It compares the work
24
+ before the kit landed with the work after, and the work is never the same. It
25
+ reported the context per request going UP by 16% on one install, with twice as
26
+ many reads and twice as many messages in the second window - a number about
27
+ the jobs, not about the kit. Its windows are now fixed and equal around the
28
+ split, and the report says how long they were; the context drop is skipped
29
+ with a note when the two sides did wildly different amounts of work, because a
30
+ gate that fails for reasons nobody can act on is a gate people switch off.
31
+
32
+ **`npm run measure:prune` is ATTRIBUTABLE.** opencode stores the message
33
+ parts unpruned - the pruner rewrites the outgoing request, not the history -
34
+ so the stored parts of a real session are exactly what the pruner was given.
35
+ Replaying the real pruner over them measures what it removed, on the same
36
+ messages, with and without.
37
+
38
+ npm run measure:prune
39
+
40
+ | | |
41
+ |---|---|
42
+ | sessions | 60 (last 30d, at least 6 assistant messages each) |
43
+ | context before | 15,130,845 characters |
44
+ | context after | 2,953,140 characters |
45
+ | **removed** | **80.5%** |
46
+ | parts stubbed | 6,608 |
47
+
48
+ **`eval/ab.mjs` is the CONTROLLED comparison** - the same task with and
49
+ without the kit, graded on the file after the run. It is at the bottom.
50
+
51
+ ## Method
52
+
53
+ Three edges divide the database into two windows:
54
+
55
+ - **split** - when the kit was installed, taken from the mtime of
56
+ `~/.config/opencode/agents`, which `oc-harness install` creates and nothing
57
+ else does.
58
+ - **before** / **after** - the outer bounds of the two windows.
59
+
60
+ `npm run measure` defaults BOTH edges to seven days around the split, so the
61
+ two windows are the same length. `--before` takes an ISO timestamp, so
62
+ `--before 2026-09-17T15:38:46.850Z` is how the original baseline in
63
+ `README.md` was reproduced. `--days` changes the window length, and `--since`
64
+ puts the unbounded "everything since the install" window back.
65
+
66
+ A read counts as **narrow** when the caller bounded it with `offset` or
67
+ `limit`. `README.md` counted `offset` alone; that is the 423 figure below, and
68
+ this script's 501 is the same reads plus the ones bounded only by `limit`.
69
+
70
+ **Results 1 and 2 are a snapshot, not a live reading.** They were taken from
71
+ this install on 2026-10-20, and they describe the windows of that date. The
72
+ commands above reproduce them against the same database; against a later one
73
+ they show later windows, because the windows are anchored on the install, not
74
+ on the day of writing. The pruner number in the table near the top is the
75
+ one that moves with the data, because it replays the real pruner over real
76
+ sessions and holds everything else still.
77
+
78
+ ## Result 1 - a week before the install against a week after
79
+
80
+ npm run measure
81
+
82
+ | Metric | before (Oct 1 - Oct 8) | after (Oct 8 - Oct 10) | change |
83
+ | --- | --- | --- | --- |
84
+ | read calls | 767 | 280 | -63% |
85
+ | narrow reads | 229 (29.9%) | 184 (**65.7%**) | +35.8 pt |
86
+ | grep/glob calls | 61 | 66 | +8% |
87
+ | reads per search | 12.6 | **4.2** | -67% |
88
+ | assistant messages | 5,616 | 1,562 | |
89
+ | avg context per request | 158,917 | 67,642 | **-57.4%** |
90
+ | avg reasoning tokens | 151 | 63 | -58% |
91
+
92
+ Searching more and reading three times less per search is the intended shape of
93
+ the change. The instruction file is what moved it: it tells the agent to locate
94
+ before reading, and the narrow-read share is the direct trace of that.
95
+
96
+ ## Result 2 - the full history, reproducing the published baseline
97
+
98
+ npm run measure -- --before 2026-09-17T15:38:46.850Z
99
+
100
+ | Metric | before (Sep 17 - Oct 8) | after | change |
101
+ | --- | --- | --- | --- |
102
+ | read calls | 2,966 | 280 | |
103
+ | narrow reads | 501 (16.9%) | 184 (65.7%) | |
104
+ | grep/glob calls | 249 | 66 | |
105
+ | reads per search | 11.9 | 4.2 | -65% |
106
+ | avg context per request | 123,776 | 67,642 | **-45.4%** |
107
+
108
+ This reproduces the `2,966 reads / 249 searches / 11.9` baseline published in
109
+ `README.md` exactly, which is the check that the measurement is reading the same
110
+ things the earlier one did. The before-window here starts at the beginning of
111
+ the database, so it is wider than the default and the before-numbers differ.
112
+
113
+ ## Result 3 - why the context fell, decomposed
114
+
115
+ npm run attribute
116
+
117
+ Result 2 shows context per request down 45%. It does not say which part of the
118
+ context moved. There are two, and they behave independently:
119
+
120
+ - **fixed overhead** - the system prompt and tool definitions, resent on every
121
+ request. Paid on turn one, before any work happens.
122
+ - **history** - what the conversation has accumulated since: tool output, files
123
+ read, the agent's own earlier messages.
124
+
125
+ Per session:
126
+
127
+ | | before | after | delta |
128
+ | --- | --- | --- | --- |
129
+ | sessions | 154 | 48 | |
130
+ | turns per session | 79.1 | 35.2 | |
131
+ | fixed overhead | 8,236 | 8,705 | **+469** |
132
+ | context at end | 68,648 | 18,285 | |
133
+ | history | 60,412 | 9,580 | **-50,832** |
134
+ | history per turn | 763.4 | 272.4 | **-491** |
135
+
136
+ The kit's own cost is visible and it is positive: **+469 tokens on every single
137
+ request**, which is `GLOBAL.md` plus the agent definitions plus the plugin
138
+ prompts. Nothing about the kit made the system prompt shorter.
139
+
140
+ Everything it bought is in the other column. History per session fell by 84%, and
141
+ per turn by 64%. The per-turn column is the one that matters, because sessions
142
+ also got shorter - 79 turns to 35 - and shorter sessions accumulate less history
143
+ for free. Normalising for turns, the kit is still responsible for a 64% drop.
144
+
145
+ That is the mechanism the whole kit rests on, and it is the one thing the
146
+ correlation in Results 1 and 2 could not show. The kit does not shrink what it
147
+ costs; it shrinks what the agent drags along behind it.
148
+
149
+ ## The A/B on TypeScript
150
+
151
+ node eval/ab.mjs --target <dir> --suite eval/tasks-ts.json --reps 2 --sanitize
152
+
153
+ **A pilot, not a result.** The kit is written in TypeScript and the Python suite
154
+ above is Python, which is the wrong language for judging the code. This closes
155
+ the gap just enough to say whether the harness works there.
156
+
157
+ **And not a checkable one.** The subject is a private repository, so nobody
158
+ can repeat this run - which is the same reason the self-suite below exists
159
+ and the same thing this suite cannot be. It is here because the question
160
+ was worth asking once, not because the number is a result. The repeatable
161
+ measurement is the self-suite.
162
+
163
+ Subject: `vendor/gibwork-sentinel` from `gibwork-agent-harness` - 692 lines of
164
+ TypeScript in seven modules, shipped beside compiled JavaScript in `dist/`. Every
165
+ answer occurs in `src/` *and again* in `dist/`, so each task says "in the
166
+ TypeScript source only". An agent that greps and stops at the build output is
167
+ wrong, which is the whole reason this subject is worth having. All three answers
168
+ were checked to occur in exactly one file under `src/` before the tasks were
169
+ written, because two earlier tasks were wrong for failing that check.
170
+
171
+ | Metric (median of 8 runs per arm) | kit on | kit off |
172
+ | --- | --- | --- |
173
+ | wall seconds | 26.1 | **19.4** |
174
+ | tool calls | 5 | 3.5 |
175
+ | narrow reads | 100% | 16.7% |
176
+ | final context | 10,815 | 9,702 |
177
+ | correct | 8 / 8 | 8 / 8 |
178
+ | paths outside target | 0 | 0 |
179
+
180
+ | per task | kit on | kit off |
181
+ | --- | --- | --- |
182
+ | ts-rpc-default | 2/2 | 2/2 |
183
+ | ts-rest-client | 2/2 | 2/2 |
184
+ | ts-pr-template | 2/2 | 2/2 |
185
+ | ts-edit-rpc | 2/2 | 2/2 |
186
+
187
+ Both arms got all four, including the edit. One kit-on answer is worth reading
188
+ for its own sake: it cited `src/scaffold.ts:77` and then said the `dist/` grep
189
+ results were not the answer, because `dist/` is compiled from `src/` and the task
190
+ asked for the source. The trap in this subject is real and the agent saw it.
191
+
192
+ ### The one task that is real work
193
+
194
+ Every task above is a lookup or a one-line edit. `edit-multi-profile` in
195
+ `eval/tasks.json` is the exception and it is the closest thing here to the work
196
+ the kit claims to help with: six stack profiles, each holding a weights
197
+ dictionary, all six have to be edited, and an agent that stops after the first
198
+ two fails.
199
+
200
+ | | kit on | kit off |
201
+ | --- | --- | --- |
202
+ | correct | 2/2 | 1/2 |
203
+ | tools | 8 and 11 | 3 and 9 |
204
+ | narrow reads | 100% | 0% |
205
+
206
+ The failing kit-off run made three tool calls, read nothing narrowly, and left
207
+ the file untouched. **Two runs per arm is not evidence of anything**, and this is
208
+ recorded as the first place the kit came out ahead rather than as a finding. It
209
+ is the one result here worth spending more runs on.
210
+
211
+ Subjects are built with `eval/prepare.mjs`, which pins a git ref through
212
+ `git archive` and does not call a tree ready until it has checked that tree for
213
+ absolute paths pointing at a real checkout.
214
+
215
+ ### What this does not establish
216
+
217
+ - **Eight runs per arm, and every task is a lookup or a one-line edit.** Not the
218
+ kit's own codebase, and nothing here measures a long multi-step refactor.
219
+ - **The speed gap still points the wrong way.** 26.1s against 19.4s, the same
220
+ direction as the five-task Python suite. See the note below.
221
+ - **Wall clock is uncontrolled for load.** Sixteen runs share one model route.
222
+
223
+ ### The one pattern that holds across suites
224
+
225
+ Two suites that contain an edit task show the kit **slower**: Python 25.4s
226
+ against 17.4s, TypeScript 26.1s against 19.4s. The one suite that showed it
227
+ faster - 18.4s against 32.7s - had no edit task and was the smaller of the
228
+ three.
229
+
230
+ That is suggestive and it is the opposite of what the kit is built for. Reading
231
+ narrowly costs an extra tool call per run, 5 against 3.5 here, and on a suite of
232
+ three or four-step lookups that cost does not buy anything back because the bare
233
+ agent was already finding the file. Whether it buys anything back on real work
234
+ is exactly what this suite cannot show, since none of the tasks is real work.
235
+
236
+ **Correctness is a tie in every suite run so far**: 8/8 against 8/8, 8/8 against
237
+ 8/8, and 9/10 against 9/10 before two task ambiguities were fixed. Thirty-eight
238
+ agent runs have not produced a single point of accuracy difference in either
239
+ direction.
240
+
241
+ ## Where the gate thresholds come from
242
+
243
+ `npm run measure:check` fails below 45% narrow reads or a 20% fall in context per
244
+ request. The 45 is derived rather than picked:
245
+
246
+ | day since the install | read calls | narrow |
247
+ | --- | --- | --- |
248
+ | Oct 8 | 29 | 69.0% |
249
+ | Oct 9 | 207 | 60.9% |
250
+ | Oct 10 | 239 | 74.9% |
251
+
252
+ Lowest observed day is 60.9%, and the spread across those three days is 14
253
+ points. A floor one whole spread below the lowest day lands near 45%, so ordinary
254
+ day-to-day movement cannot trip it and anything under it means every day since
255
+ the install was better than the failure. The previous value, 50, sat above that
256
+ margin and would have failed on a day like Oct 9.
257
+
258
+ **Three days is not a confidence interval.** The margin is one observed spread,
259
+ not a standard deviation, and the floor should be re-derived as days accumulate.
260
+ The context floor of 20% is not derived at all: it sits far below the measured
261
+ 45-57% drop, which is the conservative choice, and tightening it would need
262
+ enough days to see how that figure moves.
263
+
264
+ A per-session distribution was also computed and deliberately not used for this.
265
+ Sessions run bimodally - four of them read 0% narrow, mostly three or four read
266
+ calls each - so a per-session floor would land near zero and catch nothing.
267
+
268
+ ## What these numbers do not prove
269
+
270
+ **The two windows are not the same work.** The before-window is dominated by
271
+ building this kit - reading source is what that job was. The after-window is
272
+ ordinary use. Part of the drop is the kit working; an unknown part is simply a
273
+ different mix of tasks. Nothing here separates the two.
274
+
275
+ **The after-window is thin.** 280 reads over roughly two days, and the script
276
+ flags this itself as `thin_sample`. One quiet day moves these numbers more than
277
+ the kit does. Treat the direction as a hint, not a result.
278
+
279
+ **Narrow reads are a proxy.** A bounded read that then has to be re-read with a
280
+ second bounded read is not cheaper than one whole-file read. This counts the
281
+ first read and ignores the follow-up.
282
+
283
+ **Nothing above is an A/B.** The windows in Results 1 and 2 are the same install
284
+ observed over time, and they compare different work. The section below runs one
285
+ fixed task twice - kit on, kit off - which is the only comparison here that can
286
+ say anything about cause.
287
+
288
+ ## The A/B on this repository, 10 reps per arm
289
+
290
+ node eval/prepare.mjs --repo . --ref <sha> --name self
291
+ node eval/ab.mjs --target <scratch>/self --suite eval/tasks-self.json --reps 10
292
+
293
+ **The subject is this repository at a pinned commit**, so the run is
294
+ reproducible: `git clone && git checkout <sha>` and the same command. The
295
+ previous suite ran against a private repository, which meant a result nobody
296
+ else could check.
297
+
298
+ Six tasks, ten reps per arm, 120 agent runs: four lookups and two edits. The
299
+ lookups are graded on the `file:line` the agent cited, read out of the
300
+ snapshot; the edits are graded on the file after the run.
301
+
302
+ **Both arms ran on the same model**, `opencode/minimax-m3.1-flash-free`, and
303
+ the report prints it. The kit-off arm used to run with an empty config, so
304
+ opencode picked its own default: not the model the kit-on arm was on, and
305
+ not a model that answered - four kit-off runs hit the 600-second ceiling
306
+ with no output at all, and a timed-out run has zero tools and zero context,
307
+ so it entered the medians as a fast, empty, wrong answer.
308
+
309
+ That is why the older result below is quoted as "52/60 against **59/59**":
310
+ one of the sixty kit-off runs produced no answer at all and is excluded
311
+ from every number. Four of the kit-off runs timed out in the later run
312
+ described below, which is why the same suite cannot be compared run to
313
+ run.
314
+
315
+ | metric (median) | kit on | kit off |
316
+ | --- | --- | --- |
317
+ | **correct** | **54/60** | **55/60** |
318
+ | tool calls | 12.5 | 8 |
319
+ | read calls | 3 | 2 |
320
+ | narrow reads | 66.7% | 66.7% |
321
+ | grep/glob calls | 3 | 2 |
322
+ | steps | 9 | 6 |
323
+ | input tokens | 56,205 | 10,540 |
324
+ | final context | 39,928 | 12,718 |
325
+ | wall seconds | 43 | 21 |
326
+ | paths written outside the target | 0 | 0 |
327
+
328
+ | per task | kit on | kit off |
329
+ | --- | --- | --- |
330
+ | `read-control` | 10/10 | 10/10 |
331
+ | `read-multi-denylist` | 8/10 | 8/10 |
332
+ | `read-multi-gate` | 6/10 | 7/10 |
333
+ | `read-two-files` | 10/10 | 10/10 |
334
+ | `edit-rename` | 10/10 | 10/10 |
335
+ | `edit-multi` | 10/10 | 10/10 |
336
+
337
+ ### What this says
338
+
339
+ **On correctness, the kit is a tie.** 54 against 55, on sixty runs a side.
340
+ That is inside the noise of a single task flipping, and it is the opposite
341
+ of what the previous version of this file said.
342
+
343
+ A second run of the same 120 runs - same subject, same suite, same model,
344
+ a different day of model traffic - gave **55/60 against 54/60**, the kit
345
+ ahead by one. Two independent measurements put the tie on either side of
346
+ zero, which is what a tie looks like from both ends.
347
+
348
+ **On everything else, the kit is a cost.** It spends **5.3x the input
349
+ tokens** and takes **twice as long**, and it makes **more** tool calls.
350
+ Those are the same numbers as before, and they are the honest ones. The
351
+ narrow-read share is the same in both arms - 66.7% - so the kit's rules do
352
+ not make the agent read more carefully; they make it read more, and the
353
+ context that follows is the price.
354
+
355
+ **The previous conclusion was the grader's fault, and it is worth writing
356
+ down.** The earlier run said 52/60 against 59/59 - the kit looked clearly
357
+ worse - and the reason was that the grader could not read a citation
358
+ written as two markdown fields:
359
+
360
+ **File:** `src/profile.js`
361
+ **Line:** 105
362
+
363
+ The old pattern stopped at the newline, so the file and the number were
364
+ never paired, the citation was lost, and the answer was scored wrong. The
365
+ kit's own instructions ask the agent to name the file and the line, and
366
+ some of the answers took the two-field markdown shape - the shape the
367
+ grader could not read. The kit-off arm had no such instructions, mostly
368
+ answered in prose, and was graded by a pattern that handled prose.
369
+
370
+ Re-scoring the EARLIER run with the fixed grader - the same 120 agent runs,
371
+ the same answers, only the grader changed - gives **34/40 against 30/40** on
372
+ the four lookup tasks, the kit ahead. The run in the table above, with the
373
+ same grader, scores 34/40 against 35/40 on the same four tasks.
374
+
375
+ The re-score is reproducible: the raw streams of a run are kept, and
376
+
377
+ node eval/ab.mjs --rescore <the stream dir> --target <subject> --suite eval/tasks-self.json
378
+
379
+ grades them again without spending a token. A harness that measures the
380
+ harness is worse than one that measures nothing.
381
+
382
+ ### What this does not say
383
+
384
+ It does not say the kit is not worth it. It says the kit's value is **not in
385
+ short lookups**, and this suite only contains short lookups and two small
386
+ edits. The value it does have is in two places this suite cannot see:
387
+
388
+ - **Long sessions.** The pruner removes **80.5% of the context** across real
389
+ sessions (`npm run measure:prune`), and a task that fits in one read never
390
+ gets near the point where pruning matters.
391
+ - **Process.** A `reviewer` that reviews and does not edit, a `verify` that
392
+ runs the checks and does not fix them, a `space-finish` that hands the
393
+ publish decision to a human. Those are behaviours, not accuracy, and a
394
+ grader that counts correct answers cannot see them.
395
+
396
+ The honest position is a trade, and it is now a trade with a number on it:
397
+ **the kit costs about five times the input tokens on a small task, gets the
398
+ answer right about as often, and buys process discipline and an 80% smaller
399
+ context on a long one.** Whether that is worth it depends on whether the
400
+ work is long. A repo of one-file lookups should not install this.
401
+
402
+ A fleet that routes each job to the model built for it is the other half of
403
+ what the kit offers, and this suite cannot see that half: it runs one model
404
+ on both sides, deliberately, so that the only variable is the kit. The
405
+ routing is measured separately, in `docs/ROUTING.md`.
406
+
407
+ ## The gate
408
+
409
+ `npm run measure:check` fails when either the narrow-read share drops below 45%
410
+ or average context per request stops falling by at least 20%. Both hold today
411
+ with room to spare (65.7% and 45.4%). The thresholds are set well below the
412
+ current values on purpose: the point is to catch a regression, not to re-litigate
413
+ whether the last change helped.
414
+
415
+ It reports but never gates on `thin_sample`, so it cannot fail a run for being
416
+ small - only the floor is enforced.
417
+
418
+ ## Not measured here
419
+
420
+ - Whether the agent's *answers* got better in general. Six tasks, 120 runs,
421
+ and a tie on correctness - which is not the same as a measurement of it.
422
+ - Whether the numbers in Results 1 and 2 were caused by the kit. Result 3 shows
423
+ a mechanism that would produce them; the A/B points the same way on a
424
+ different task. Neither window separates the kit from the work.
425
+ - Which part of the kit does the work. Result 3 localises the effect to history
426
+ accumulation, not to any individual rule or plugin.
427
+ - Pruner characters saved, reported from this machine's own `.space/prune.jsonl`
428
+ below rather than from the eval run. It describes one profile on one set of sessions,
429
+ not a population.
430
+ - Whether the free model route is healthy. `oc-harness doctor` answers that and
431
+ needs the network, so it is deliberately outside these scripts.
432
+
433
+ ## Pruner, as recorded by this install
434
+
435
+ `.space/prune.jsonl` is appended by the plugin on every prune. Summarised over
436
+ the 240 records it holds, all on profile `space-bunny-free`:
437
+
438
+ | | |
439
+ |---|---|
440
+ | window | 2026-10-08T01:06Z to 2026-10-11T00:57Z |
441
+ | prunes | 240 |
442
+ | tool outputs stubbed | 3,170 of 7,641 (41.5%) |
443
+ | tool outputs kept | 4,471 |
444
+ | characters saved | 15,437,346 |
445
+ | median / max per prune | 23,388 / 418,510 |
446
+
447
+ Two caveats, because these are not comparable to the A/B numbers above. The
448
+ records come from ordinary working sessions on this machine, not from a fixed
449
+ task, so they describe what the pruner did to real traffic rather than what it
450
+ would do to a controlled workload. And the split timestamp in `measure-install.mjs`
451
+ falls inside this window: 208 of the 240 records and 13,009,568 of those bytes
452
+ post-date the install, so the totals include prunes that ran before the kit was
453
+ installed.
454
+
455
+ Nothing here is regenerated by `npm run measure`, which reads only the session
456
+ database. Rerun with `node -e` over the JSONL, or delete it and let the plugin
457
+ rebuild it.