docs(trekresearch): document STORM mechanisms across four surfaces

This commit is contained in:
Kjell Tore Guttormsen 2026-08-12 20:28:26 +02:00
commit 9c91211fc0
5 changed files with 104 additions and 0 deletions

View file

@ -24,6 +24,19 @@ Voyage — a contract-driven Claude Code pipeline: brief, research, plan, execut
Full flag reference for each command (modes, `--gates`, `--profile`, breaking changes): see `docs/command-modes.md`.
> **STORM bounded loop — default-off, env-gated.** `/trekresearch` Phase 4.5
> (dimension discovery, under the existing `maxDimensions: 8` ceiling) and
> Phase 5 (bounded multi-turn follow-up) run only at `effort: high` **and** only
> when `VOYAGE_STORM_ENABLED=1`; unset, `lib/util/research-loop-cap.mjs` grants
> a budget of 0 and both are inert. `TREKRESEARCH_MAX_CONV_TURNS` (default `3`,
> invalid values fall back to `3`) sets turns per dimension; the budget is that
> × `maxDimensions`. `VOYAGE_DISABLE_CAP_HOOK=1` switches off
> `hooks/scripts/pre-agent-cap.mjs`, the `PreToolUse` gate that enforces the
> budget in the harness rather than trusting prose. The cap counts turns from
> its own append-only ledger. Adoption as a default is gated on the
> pre-registered measurement in `docs/storm-measurement.md` — decline is a
> no-op, adopt is one constant.
## Agents
| Agent | Model | Role |

View file

@ -216,6 +216,24 @@ Output:
Flags combine: `--project <dir> --external`.
#### Bounded conversation loop (default-off)
At `effort: high`, research can discover additional dimensions (Phase 4.5) and
run a bounded multi-turn follow-up loop on under-illuminated ones (Phase 5).
Both are **default-off** and env-gated rather than flag-gated, because enabling
them costs turns:
| Env-var | Default | Behavior |
|---------|---------|----------|
| `VOYAGE_STORM_ENABLED` | _(unset — default-off)_ | `=1` gives the loop a non-zero turn budget. Unset, the budget is 0 and the loop is inert. |
| `TREKRESEARCH_MAX_CONV_TURNS` | `3` | Turns per under-illuminated dimension. Invalid values fall back to `3`, never to unbounded. |
| `VOYAGE_DISABLE_CAP_HOOK` | _(unset)_ | `=1` disables the `PreToolUse` hook that enforces the turn budget. |
The cap counts turns itself from an append-only ledger — it never asks the loop
how many turns it has used. Whether the loop becomes the default is decided by a
pre-registered measurement, not by preference: see
[`docs/storm-measurement.md`](docs/storm-measurement.md).
Research uses up to 5 local agents (architecture-mapper, dependency-tracer, task-finder, git-historian, convention-scanner) and 4 external agents (docs-researcher, community-researcher, security-researcher, contrarian-researcher) plus the optional Gemini bridge for an independent second opinion. Per-agent details in [`agents/`](agents/).
---

View file

@ -12,6 +12,8 @@ Imported from `CLAUDE.md` via pointer.
- `lib/stats/event-emit.mjs` — single-source stats event emitter for autonomy-gate transitions and main-merge-gate (v3.4.0)
- `lib/validators/{brief,research,plan,progress,session-state}-validator.mjs` — schema validators with CLI shims (`node lib/validators/X.mjs --json <path>`)
- `lib/validators/architecture-discovery.mjs` — drift-WARN external-contract discovery for `architecture/overview.md`
- `lib/util/research-loop-cap.mjs` — stateful, **default-off** turn budget for the `/trekresearch` bounded conversation loop. `allowTurn()` derives the used-turn count from its own append-only JSONL ledger; it never asks the caller how many turns it has spent, because a cap that does is not a cap. Budget = `TREKRESEARCH_MAX_CONV_TURNS` (default `3`, invalid values fall back to `3`) × `maxDimensions` (8, `settings.json:16`). Grants 0 unless `VOYAGE_STORM_ENABLED=1`; missing `CLAUDE_PLUGIN_DATA` denies (fail-closed — the opposite of `event-emit.mjs`, which is telemetry and must never block). CLI shim: `node lib/util/research-loop-cap.mjs --run-id ID --dimension D --effort E`
- `lib/validators/query-privacy-gate.mjs` — gates **every** outbound research query before it leaves the machine; the hard-block tier (secret-shaped strings) is not operator-overridable, so a query that trips it must be reformulated rather than forced through. CLI shim: `node lib/validators/query-privacy-gate.mjs "<query>"`
Wiring points (replaces previous prose-grep instructions):
- `/trekbrief` Phase 4g → `brief-validator` (post-write sanity check)

View file

@ -30,6 +30,22 @@ Always interactive. Phase 3 is a section-driven completeness loop (no hard cap o
Flags combine: `--project <dir> --local`, `--external --quick`.
### Bounded conversation loop (Phase 4.5 + Phase 5) — env-vars
Dimension discovery and the multi-turn follow-up loop are **default-off** and
have no flag; they are environment-gated, because turning them on costs turns.
They run only at `effort: high` (resolved from the brief's `phase_signals`).
| Env-var | Default | Behavior |
|---------|---------|----------|
| `VOYAGE_STORM_ENABLED` | _(unset — default-off)_ | `=1` grants the loop a non-zero turn budget. Unset, `research-loop-cap.mjs` grants 0 turns and the loop is inert: doing nothing keeps the mechanism off. |
| `TREKRESEARCH_MAX_CONV_TURNS` | `3` | Max turns per under-illuminated dimension. Budget = this × `maxDimensions` (8, `settings.json:16`). Empty, non-numeric, zero, or negative values fall back to `3` — never to unbounded. |
| `VOYAGE_DISABLE_CAP_HOOK` | _(unset)_ | `=1` disables `hooks/scripts/pre-agent-cap.mjs`, the `PreToolUse` enforcement of the turn budget. The cap primitive still applies; only the second gate is switched off. |
Adoption of the loop as a default is gated on a pre-registered measurement —
protocol, thresholds, and the exact commands in
[`docs/storm-measurement.md`](storm-measurement.md).
## /trekplan modes
| Flag | Behavior |

View file

@ -1117,6 +1117,61 @@ test('deep-research-engine: --engine is documented + consistent across surfaces'
);
});
// ── STORM bounded loop — env-vars documented across the four surfaces ──────
// Same cross-doc shape as the --engine pin above. An operator-facing switch
// documented on one surface is a switch most operators never find; and the
// three below decide cost, so they are the ones worth pinning.
const STORM_SURFACES = ['docs/command-modes.md', 'CLAUDE.md', 'README.md', 'docs/architecture.md'];
const STORM_ENV_VARS = ['VOYAGE_STORM_ENABLED', 'TREKRESEARCH_MAX_CONV_TURNS', 'VOYAGE_DISABLE_CAP_HOOK'];
for (const envVar of STORM_ENV_VARS) {
test(`STORM: ${envVar} is documented on all four reference surfaces`, () => {
for (const f of STORM_SURFACES) {
assert.ok(read(f).includes(envVar), `${f} must document ${envVar} (STORM bounded-loop env-vars)`);
}
});
}
test('STORM: VOYAGE_STORM_ENABLED is documented WITH its default-off contract', () => {
// Naming a switch without its default is how a default-on mechanism ships by
// accident. Default-off is also the decline branch: declining costs nothing.
for (const f of STORM_SURFACES) {
const t = read(f);
const i = t.indexOf('VOYAGE_STORM_ENABLED');
const window = t.slice(Math.max(0, i - 400), i + 400);
assert.match(
window,
/default-off|default off|opt-in/i,
`${f}: VOYAGE_STORM_ENABLED must be documented together with its default-off contract`,
);
}
});
test('STORM: README research-dimension prose stays at the existing 38 ceiling', () => {
// Phase 4.5 discovers dimensions UNDER settings.json:16's maxDimensions: 8.
// Rewriting this prose upward would raise a ceiling the brief asked us to hold.
assert.ok(
read('README.md').includes('38 research dimensions'),
'README.md must keep the "38 research dimensions" prose — augmentation happens under the existing cap, it does not raise it',
);
});
test('STORM: no banned Sonnet-swarm phrase introduced on any STORM surface', () => {
const BANNED = [
'Sonnet exploration',
'Sonnet runs the exploration',
'front-loads cheap Sonnet',
'exploration agents stay on Sonnet',
];
for (const f of STORM_SURFACES) {
const t = read(f);
for (const phrase of BANNED) {
assert.ok(!t.includes(phrase), `${f} must not claim "${phrase}" — sub-agents are opus-pinned`);
}
}
});
test('S18: HANDOVER-CONTRACTS documents the pre-2.2 zero-framing-enforcement hole', () => {
// The framing defense is producer-elective: a brief declaring ≤ 2.1 sidesteps
// it entirely. Handover 1 (PUBLIC CONTRACT) must disclose this and name the remedy.