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.
- package/CHANGELOG.md +569 -18
- package/CONTRIBUTING.md +249 -0
- package/GLOBAL.md +34 -5
- package/MIGRATION.md +151 -0
- package/README.md +354 -66
- package/SECURITY.md +30 -0
- package/bin/baseline.mjs +15 -7
- package/bin/git-hooks/pre-commit +41 -9
- package/bin/oc-harness.mjs +1268 -82
- package/dist/config.d.ts +127 -0
- package/dist/denylist.d.ts +33 -0
- package/dist/effort.d.ts +64 -0
- package/dist/guard.d.ts +23 -0
- package/dist/health.d.ts +43 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +532 -149
- package/dist/plugin.d.ts +15 -0
- package/dist/profile.d.ts +399 -0
- package/dist/project.d.ts +8 -0
- package/dist/prune.d.ts +6 -0
- package/dist/router.d.ts +3 -0
- package/dist/rules.d.ts +109 -0
- package/dist/safe.d.ts +7 -0
- package/dist/telemetry-store.d.ts +12 -0
- package/dist/telemetry.d.ts +3 -0
- package/docs/EXIT-CODES.md +83 -0
- package/docs/KNOWN-LIMITS.md +45 -21
- package/docs/RESEARCH.md +20 -5
- package/docs/ROUTING.md +191 -31
- package/eval/RESULTS.md +457 -0
- package/eval/ab.mjs +743 -0
- package/eval/attribute.mjs +170 -0
- package/eval/flags.mjs +88 -0
- package/eval/measure-install.mjs +331 -0
- package/eval/measure-prune.mjs +339 -0
- package/eval/metrics.js +79 -0
- package/eval/models.mjs +302 -0
- package/eval/premises.mjs +200 -0
- package/eval/prepare.mjs +233 -0
- package/eval/run.mjs +376 -0
- package/eval/tasks-self.json +81 -0
- package/eval/tasks-ts.json +50 -0
- package/eval/tasks.json +83 -0
- package/eval/thresholds.mjs +134 -0
- package/eval/verify.mjs +287 -0
- package/package.json +44 -17
- package/profiles/fleet-all.json +6 -1
- package/profiles/fleet-safe.json +7 -3
- package/profiles/schema.json +182 -51
- package/prompts/build.md +4 -2
- package/skills/space-build/SKILL.md +4 -2
- package/skills/space-finish/SKILL.md +6 -2
- package/skills/space-plan/reference/task-template.md +13 -5
- package/src/config.js +182 -32
- package/src/denylist.js +95 -0
- package/src/driver/run.mjs +81 -16
- package/src/effort.js +15 -4
- package/src/guard.ts +165 -13
- package/src/health.js +80 -0
- package/src/index.ts +15 -4
- package/src/lane-usage.js +72 -18
- package/src/plugin-type-check.ts +38 -0
- package/src/plugin.ts +39 -0
- package/src/profile.js +225 -26
- package/src/project.js +47 -0
- package/src/prune.ts +110 -31
- package/src/router.ts +32 -63
- package/src/rules.js +99 -7
- package/src/safe.ts +27 -0
- package/src/sqlite.js +144 -0
- package/src/tasks.js +112 -29
- package/src/telemetry-store.ts +62 -0
- package/src/telemetry.ts +60 -57
- package/src/trust.js +70 -9
package/eval/RESULTS.md
ADDED
|
@@ -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.
|