119 lines
5.6 KiB
Markdown
119 lines
5.6 KiB
Markdown
# STORM adoption gate — pre-registered measurement protocol
|
||
|
||
**Status:** thresholds registered, **no measurement run yet.** This document is
|
||
committed *before* the first measurement by design: a threshold chosen after
|
||
seeing the numbers is not a threshold. **Harness:** `scripts/storm-measure.mjs`
|
||
(pinned by `tests/scripts/storm-measure.test.mjs`). **Decides:** whether the
|
||
bounded Phase 5 conversation loop (`commands/trekresearch.md` §Loop bound) is
|
||
worth turning on by default.
|
||
|
||
---
|
||
|
||
## 1. What this gate measures — and what it does not
|
||
|
||
The gate measures **source and coverage breadth**:
|
||
|
||
| Metric | Definition | Arm |
|
||
|---|---|---|
|
||
| `unique_sources` | distinct external sources cited by the run | between-arm median gain |
|
||
| dimensions over baseline | `(dimensions − dimensions_baseline) / dimensions_baseline` | within-run median across the treatment arm |
|
||
|
||
It does **not** measure outline quality, answer correctness, synthesis
|
||
usefulness, or operator satisfaction. A breadth win is a *necessary* condition
|
||
for adoption, never a sufficient one. If the loop widens coverage by 40% and
|
||
the resulting briefs read worse, the correct action is still decline — the
|
||
number does not overrule a reading of the artifacts.
|
||
|
||
The second metric is a within-run delta by construction (`dimensions_baseline`
|
||
is the interview dimension count, `dimensions` the post-Phase-4.5 list), so it
|
||
needs no control arm; the control arm's value is 0 because the loop is inert
|
||
below `effort: high`.
|
||
|
||
## 2. Pre-registered thresholds
|
||
|
||
Both metrics must clear the bar. A strong result on one axis does not carry a
|
||
weak result on the other — the loop's claim is breadth on both.
|
||
|
||
| Median gain (BOTH metrics) | Verdict | Action |
|
||
|---|---|---|
|
||
| ≥ 30% | **adopt** | Flip the `VOYAGE_STORM_ENABLED` default (see §5) |
|
||
| < 15% | **decline** | Leave the mechanism default-off. This is a **no-op**: nothing is rolled back |
|
||
| 15% – 30% | **inconclusive** | Keep default-off, gather more runs, re-measure |
|
||
| either arm empty | **insufficient-data** | Not a decline — measure more |
|
||
|
||
`ADOPT_THRESHOLD = 0.30` and `DECLINE_THRESHOLD = 0.15` are exported constants
|
||
in `scripts/storm-measure.mjs` and pinned by the test suite. Changing them is a
|
||
deliberate, reviewable act, not a tuning knob to be nudged toward a result.
|
||
|
||
## 3. Excluded runs (the honest denominator)
|
||
|
||
Runs with `empty_turns > 0` are **excluded from the gain and reported as a
|
||
count**. An empty turn is one that spent budget and returned no findings, or
|
||
findings without citations. Including those runs decides adoption on a broken
|
||
denominator — the loop looks cheap because its failures are averaged into its
|
||
successes. The harness prints the excluded count on every invocation; if that
|
||
count is a large fraction of the treatment arm, the finding is about the loop's
|
||
reliability, and it should be read before the gain figure is read at all.
|
||
|
||
Rows predating the Step 9 measurement fields carry no `effort` and are dropped
|
||
as `legacy` with a count. A stats file where *no* row carries `effort` is a hard
|
||
error, not an empty treatment group: a schema gap must never present itself as
|
||
"no gain".
|
||
|
||
## 4. The measurement runs (operator-run, outside this plan)
|
||
|
||
The measurement itself is **not** part of the implementation plan that built
|
||
this harness. It is an operator-run gate between that plan and any adopt commit.
|
||
|
||
Protocol:
|
||
|
||
- **n ≥ 5 runs per arm.** Fewer, and the median is an anecdote.
|
||
- **The same question set in both arms.** Two briefs, run as `--project` runs.
|
||
- The arms differ in exactly one thing: whether the loop is enabled.
|
||
- Both arms append to the same `trekresearch-stats.jsonl`; `effort` is the
|
||
grouping key that separates them.
|
||
|
||
```bash
|
||
# Control arm (loop inert) — n >= 5
|
||
claude -p "/trekresearch --project .claude/projects/<brief-standard-effort>"
|
||
|
||
# Treatment arm (loop live, effort: high in the brief's phase_signals) — n >= 5
|
||
VOYAGE_STORM_ENABLED=1 \
|
||
claude -p "/trekresearch --project .claude/projects/<brief-high-effort>"
|
||
|
||
# The gate
|
||
node scripts/storm-measure.mjs --stats "${CLAUDE_PLUGIN_DATA}/trekresearch-stats.jsonl"
|
||
|
||
# Machine-readable, for a decision record
|
||
node scripts/storm-measure.mjs --json --stats "${CLAUDE_PLUGIN_DATA}/trekresearch-stats.jsonl"
|
||
|
||
# SC activation check — did the latest high-effort run discover any dimension
|
||
# beyond its interview baseline? Exit 0 = yes.
|
||
node scripts/storm-measure.mjs --activation-check --stats "${CLAUDE_PLUGIN_DATA}/trekresearch-stats.jsonl"
|
||
```
|
||
|
||
## 5. What "adopt" concretely means
|
||
|
||
Adoption is **one constant**: `isStormEnabled()` in
|
||
`lib/util/research-loop-cap.mjs` currently requires `VOYAGE_STORM_ENABLED === '1'`.
|
||
Adopt = make the loop's budget non-zero without that opt-in, in a commit that
|
||
cites the measurement output.
|
||
|
||
This asymmetry is deliberate and was designed in before any code was written:
|
||
|
||
- **decline costs nothing** — the mechanism ships default-off, so declining is
|
||
doing nothing. No revert, no removal from a command file two other steps
|
||
already rewrote.
|
||
- **adopt costs one constant** — plus the enforcement hook
|
||
(`hooks/scripts/pre-agent-cap.mjs`) already in place to bound what gets turned
|
||
on, and the operator-visible cap-exhaustion message already required by
|
||
Phase 5's exit conditions.
|
||
|
||
## 6. Reading the result honestly
|
||
|
||
- The gate measures breadth. Say "breadth" in the decision record, not "quality".
|
||
- Report the excluded count alongside the gain, always. A 35% gain computed
|
||
after excluding 6 of 10 treatment runs is a finding about instability.
|
||
- `insufficient-data` is not a decline. Do not resolve it by lowering n.
|
||
- A verdict computed from a stats file mixing several question sets measures the
|
||
question sets, not the loop.
|