Testing a phase of the plugin's harness
launch-llms-benchmark asks how good is this model's report. This asks
a different question: how much work does the package still make the
model compose before it can do anything — and it answers it for one
named phase at a time. Do not run the benchmark protocol for this; it
grades findings, costs a full run, and its row is not what moves when a
skill stops making the model write a script.
The rules a change here must satisfy are AGENTS.md's Plugin
harnessing section. This skill is how you prove one landed.
What you need before starting
- The phase, named by the caller:
preflight, drive,
observation, or whole. Ask if it is not named — measuring the
wrong phase wastes the run.
- The mission, the same on every sample, and by preference an
observation in drive mode on the local stack: the run generates
its own traffic, so every window holds the same requests and the
samples differ in how they worked, not in what there was to find. A
post-hoc window holds whatever landed that minute (a late burst, a
real error — measured 2026-09-09: one 502 gave three samples an extra
finding and 34 to 40 queries against 24 to 30): measure post-hoc only
when post-hoc is what changes, with one scripted burst per sample and
the stack's facts re-read per window; a remote stack only when the
change is that stack's, never by default.
- The baseline, which is the published row in
.llms-benchmark/README.md for the model you will use: its phase
durations, its turn count and its median turn. Read it from
origin/main, never from the working tree.
- A model whose median turn is small. The published median is the
instrument's precision: a model at 3 s per turn measures the harness,
one at 20 s measures the provider. Prefer the fastest row in the
table, whatever its findings score — this is not a quality test.
The procedure
Rebuild the generated tree. A CLI agent loads
marketplace/, not .apm/, so an unregenerated tree runs the
previous version of every prompt, agent and skill — the run then
measures the old package while looking exactly like it measures the
new one. bash scripts/build-marketplace.sh, then grep the change in
the generated file the run will read. This is the single most
expensive mistake available here; make it once and every number since
the change is void.
Deploy to every scope the host reads, and prove they match.
uvx --from 'apm-cli==0.29.1' apm install --target opencode for the
repository, and copy .apm/skills/* and .apm/agents/* over
~/.claude/skills / ~/.claude/agents when the host also reads a
user scope. Back the user scope up first and restore it at the end —
it is the user's install, not yours — or, when the user's home must
stay untouched, run the host under a fake HOME whose .claude
carries the deploy and whose every other entry is a symlink to the
real home (the credentials, the log and the session store stay
where the scripts read them). diff -rq the two scopes: a run
that finds them different spends turns comparing them.
Clean what the next run must not read. Any report a previous run
stored, and any leftover container, process or scratch directory. A
run that reads the last run's conclusions is not measuring anything.
Measure, with scripts/measure_phase.py:
python3 <this skill's directory>/scripts/measure_phase.py \
--model <openrouter id> --tag <short label> --phase <phase> \
--prompt-file <mission> --out <study dir>
A mission that opens with a slash command (/odd-observe ...) is
launched through the host's own expansion (--command), the way a
typed command is: passed as raw text, the run spends its first turns
hunting for the command file - globs, reads of the command and of
the agent it dispatches - a cost no host pays, folded into every
phase number (measured: 8 to 10 turns of a 75-turn run). The record
carries command so a number taken the old way is never compared
with one taken this way.
Its whole surface, so --help has nothing to add: --model,
--tag, --phase, one of --prompt / --prompt-file, --out,
plus --end-pattern (a regular expression over the run's own log
lines, for a mission with no k6 drive to mark the phase - a post-hoc
observation, a scenario the mission names), --variant (default
medium), --timeout (default 2700 s) and --keep-running to let
the run continue past the phase. It records
the run's own id at launch, stops at the phase's marker, and exits
non-zero rather than return a fast wrong number when the phase never
closed.
Analyse before concluding, with scripts/analyze_run.py:
python3 <this skill's directory>/scripts/analyze_run.py --record <study dir>/<tag>.record.json
Surface: --record, or --run-id; --gap (default 60 s) sets the
gap it reports; --json. It prints the
commands, the
turns, the generation time and the median turn, then the four
behaviours a harnessing change removes — scripts the run authored,
stack resets, machine questions already answered upstream, --help
calls on shipped scripts — and every silent gap.
Read the gaps before believing the clock. A gap with no command
in it is the model generating. One far above the run's median turn is
the provider, not the package: the script says so, and when it does,
the wall clock is not comparable to anything. Compare generation
time and commands instead, or measure again later.
Fix one lever, then measure again. One change per run, rebuilt
and redeployed per steps 1-2. Two changes in one run cannot be
attributed, and the run-to-run spread is wide enough to hide a small
effect either way.
Restore the machine. The user's skill and agent scopes from the
backup, the generated trees to origin/main (the release workflow
owns them), .gitignore and anything else apm install edited, the
containers down, the stray processes killed.
Judging what you measured
The baseline is main, measured just before the work starts, with
the same mission, the same machine and the same harness as the runs
that follow — never the published row alone, never a number taken on
another day. State it on four axes at once: turns, tokens (input with
the cached share, output), cost and wall clock — per phase, since the
report phase is a tenth of a run and a change there vanishes in the
investigation's spread. When a number looks like variance, replay
rather than argue: two samples of the same configuration settle what
one cannot.
A change goes to review only with a substantial gain on those axes
against that baseline — not a conformant output alone, not a
behaviour count alone. A change that moved the target phase and left
the totals level, or worse, is reworked, not argued: find where the
turns and the context went (per-phase accounting, the reads of every
file the change touched, the calls the new invocation caused), fix the
cause, and measure again.
Under review, measure once, at the end. The fix waves a reviewer
asks for are not measured one by one: apply them, run the suites, and
measure the branch as it will be merged once the reviewer is green —
two samples minimum, against the numbers that sent it to review. A
wave that changes what the run reads or runs (a line the run copies, a
shape it used as a source, a new invocation) is the exception: measure
it before the next round, because the measured 2026-09-09 case cost the
whole gain and only the numbers said so. A loss at the end reopens the
review with the mechanism named, never a re-run alone.
When the phase reaches the report, the findings are a metric too.
A harness change that cuts turns and loses findings moved the cost
onto the reader. For every sample that wrote a report, count what it
found - section 3's ranked findings by severity and confidence, section
5's gaps - on the baseline and on the change alike (the report script's
synthesis prints both lists), and review them before comparing:
re-run the query each finding cites, open what it accuses, and rule it
confirmed or not - the way launch-llms-benchmark grades a row, on
evidence, never on the report's own confidence label. State the
confirmed count next to the reported one, per side, in the study and
in the PR; a change that reports more but confirms less is worse.
Two samples minimum before claiming a wall-clock gain, and state
both. The spread between two runs of one configuration reached 17 s in
practice; a single sample below the baseline proves nothing.
A behaviour count is stronger evidence than a duration. Commands
before the phase closed, scripts authored, resets taken, redundant
questions — these are what the package controls, they do not move with
the provider's mood, and they are what the PR should quote.
When a run writes its own script, the shipped one is missing a shape
of the work. Read what it wrote before hardening any instruction: a
wrapper that only chains a shipped command with a wait is the
repository's own helper-file pattern and is not a defect; one that
rebuilds a command the package ships is a gap in that script.
Report what did not improve. A lever that cost a run and moved
nothing belongs in the PR body too — it is what stops the next person
from trying it again.
1---2name: test-plugin-harnessing3description: Measure and optimise one phase of an oddyssey run - preflight, drive, observation - against the published benchmark row for that model. Use when a phase is too slow or too expensive, when a harnessing change must be proven rather than asserted, or when a run is suspected of composing work the package should ship. Drives opencode headless, measures the phase, names where the time went, and separates what the package controls from provider latency. Never a substitute for launch-llms-benchmark, which grades findings; this grades the harness.4---56# Testing a phase of the plugin's harness78`launch-llms-benchmark` asks *how good is this model's report*. This asks9a different question: **how much work does the package still make the10model compose before it can do anything** — and it answers it for one11named phase at a time. Do not run the benchmark protocol for this; it12grades findings, costs a full run, and its row is not what moves when a13skill stops making the model write a script.1415The rules a change here must satisfy are `AGENTS.md`'s **Plugin16harnessing** section. This skill is how you prove one landed.1718## What you need before starting1920- **The phase**, named by the caller: `preflight`, `drive`,21 `observation`, or `whole`. Ask if it is not named — measuring the22 wrong phase wastes the run.23- **The mission**, the same on every sample, and by preference an24 observation in **drive mode on the local stack**: the run generates25 its own traffic, so every window holds the same requests and the26 samples differ in how they worked, not in what there was to find. A27 post-hoc window holds whatever landed that minute (a late burst, a28 real error — measured 2026-09-09: one 502 gave three samples an extra29 finding and 34 to 40 queries against 24 to 30): measure post-hoc only30 when post-hoc is what changes, with one scripted burst per sample and31 the stack's facts re-read per window; a remote stack only when the32 change is that stack's, never by default.33- **The baseline**, which is the published row in34 `.llms-benchmark/README.md` for the model you will use: its phase35 durations, its turn count and its **median turn**. Read it from36 `origin/main`, never from the working tree.37- **A model whose median turn is small.** The published median is the38 instrument's precision: a model at 3 s per turn measures the harness,39 one at 20 s measures the provider. Prefer the fastest row in the40 table, whatever its findings score — this is not a quality test.4142## The procedure43441. **Rebuild the generated tree.** A CLI agent loads45 `marketplace/`, not `.apm/`, so an unregenerated tree runs the46 *previous* version of every prompt, agent and skill — the run then47 measures the old package while looking exactly like it measures the48 new one. `bash scripts/build-marketplace.sh`, then grep the change in49 the generated file the run will read. This is the single most50 expensive mistake available here; make it once and every number since51 the change is void.52532. **Deploy to every scope the host reads, and prove they match.**54 `uvx --from 'apm-cli==0.29.1' apm install --target opencode` for the55 repository, and copy `.apm/skills/*` and `.apm/agents/*` over56 `~/.claude/skills` / `~/.claude/agents` when the host also reads a57 user scope. Back the user scope up first and restore it at the end —58 it is the user's install, not yours — or, when the user's home must59 stay untouched, run the host under a fake `HOME` whose `.claude`60 carries the deploy and whose every other entry is a symlink to the61 real home (the credentials, the log and the session store stay62 where the scripts read them). `diff -rq` the two scopes: a run63 that finds them different spends turns comparing them.64653. **Clean what the next run must not read.** Any report a previous run66 stored, and any leftover container, process or scratch directory. A67 run that reads the last run's conclusions is not measuring anything.68694. **Measure**, with `scripts/measure_phase.py`:7071 ```bash72 python3 <this skill's directory>/scripts/measure_phase.py \73 --model <openrouter id> --tag <short label> --phase <phase> \74 --prompt-file <mission> --out <study dir>75 ```7677 A mission that opens with a slash command (`/odd-observe ...`) is78 launched through the host's own expansion (`--command`), the way a79 typed command is: passed as raw text, the run spends its first turns80 hunting for the command file - globs, reads of the command and of81 the agent it dispatches - a cost no host pays, folded into every82 phase number (measured: 8 to 10 turns of a 75-turn run). The record83 carries `command` so a number taken the old way is never compared84 with one taken this way.8586 Its whole surface, so `--help` has nothing to add: `--model`,87 `--tag`, `--phase`, one of `--prompt` / `--prompt-file`, `--out`,88 plus `--end-pattern` (a regular expression over the run's own log89 lines, for a mission with no k6 drive to mark the phase - a post-hoc90 observation, a scenario the mission names), `--variant` (default91 `medium`), `--timeout` (default 2700 s) and `--keep-running` to let92 the run continue past the phase. It records93 the run's own id at launch, stops at the phase's marker, and exits94 non-zero rather than return a fast wrong number when the phase never95 closed.96975. **Analyse before concluding**, with `scripts/analyze_run.py`:9899 ```bash100 python3 <this skill's directory>/scripts/analyze_run.py --record <study dir>/<tag>.record.json101 ```102103 Surface: `--record`, or `--run-id`; `--gap` (default 60 s) sets the104 gap it reports; `--json`. It prints the105 commands, the106 turns, the generation time and the median turn, then the four107 behaviours a harnessing change removes — scripts the run authored,108 stack resets, machine questions already answered upstream, `--help`109 calls on shipped scripts — and every silent gap.1101116. **Read the gaps before believing the clock.** A gap with no command112 in it is the model generating. One far above the run's median turn is113 the provider, not the package: the script says so, and when it does,114 the wall clock is not comparable to anything. Compare **generation115 time** and **commands** instead, or measure again later.1161177. **Fix one lever, then measure again.** One change per run, rebuilt118 and redeployed per steps 1-2. Two changes in one run cannot be119 attributed, and the run-to-run spread is wide enough to hide a small120 effect either way.1211228. **Restore the machine.** The user's skill and agent scopes from the123 backup, the generated trees to `origin/main` (the release workflow124 owns them), `.gitignore` and anything else `apm install` edited, the125 containers down, the stray processes killed.126127## Judging what you measured128129**The baseline is main, measured just before the work starts**, with130the same mission, the same machine and the same harness as the runs131that follow — never the published row alone, never a number taken on132another day. State it on four axes at once: turns, tokens (input with133the cached share, output), cost and wall clock — per phase, since the134report phase is a tenth of a run and a change there vanishes in the135investigation's spread. When a number looks like variance, replay136rather than argue: two samples of the same configuration settle what137one cannot.138139**A change goes to review only with a substantial gain on those axes140against that baseline** — not a conformant output alone, not a141behaviour count alone. A change that moved the target phase and left142the totals level, or worse, is reworked, not argued: find where the143turns and the context went (per-phase accounting, the reads of every144file the change touched, the calls the new invocation caused), fix the145cause, and measure again.146147**Under review, measure once, at the end.** The fix waves a reviewer148asks for are not measured one by one: apply them, run the suites, and149measure the branch as it will be merged once the reviewer is green —150two samples minimum, against the numbers that sent it to review. A151wave that changes what the run reads or runs (a line the run copies, a152shape it used as a source, a new invocation) is the exception: measure153it before the next round, because the measured 2026-09-09 case cost the154whole gain and only the numbers said so. A loss at the end reopens the155review with the mechanism named, never a re-run alone.156157**When the phase reaches the report, the findings are a metric too.**158A harness change that cuts turns and loses findings moved the cost159onto the reader. For every sample that wrote a report, count what it160found - section 3's ranked findings by severity and confidence, section1615's gaps - on the baseline and on the change alike (the report script's162`synthesis` prints both lists), and **review them before comparing**:163re-run the query each finding cites, open what it accuses, and rule it164confirmed or not - the way `launch-llms-benchmark` grades a row, on165evidence, never on the report's own confidence label. State the166confirmed count next to the reported one, per side, in the study and167in the PR; a change that reports more but confirms less is worse.168169**Two samples minimum before claiming a wall-clock gain**, and state170both. The spread between two runs of one configuration reached 17 s in171practice; a single sample below the baseline proves nothing.172173**A behaviour count is stronger evidence than a duration.** Commands174before the phase closed, scripts authored, resets taken, redundant175questions — these are what the package controls, they do not move with176the provider's mood, and they are what the PR should quote.177178**When a run writes its own script, the shipped one is missing a shape179of the work.** Read what it wrote before hardening any instruction: a180wrapper that only chains a shipped command with a wait is the181repository's own helper-file pattern and is not a defect; one that182rebuilds a command the package ships is a gap in that script.183184**Report what did not improve.** A lever that cost a run and moved185nothing belongs in the PR body too — it is what stops the next person186from trying it again.