# 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/" # Treatment arm (loop live, effort: high in the brief's phase_signals) — n >= 5 VOYAGE_STORM_ENABLED=1 \ claude -p "/trekresearch --project .claude/projects/" # 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.