The Phase 4.5 amendment to the Independence hard rule crosses that rule
deliberately, and the crossing is defensible: bounded to Phase 4.5 and the
Phase 5 loop, the initial swarm stays blind, and it resolves a tension the
brief created itself by asking for exactly this mining of Phase-4 output.
The defect was the compensating-control claim. query-privacy-gate.mjs was
named as THE control for the crossing, but it inspects outbound query
CONTENT for paths, repo identifiers and secret-shaped strings. It cannot
prevent a local finding from steering an external agent's question. It
compensates the EGRESS risk; the BIAS risk was left with no control while
the text read as though it had one.
Both risks are now stated separately with the control that actually bears on
each:
Bias - structural, not a gate. The initial external swarm stays blind, so
an independent baseline exists BEFORE anything crosses; the
crossing only ADDS to that baseline; and at effort: high - the only
effort at which any of this runs - contrarian-researcher is forced
always-on, so the brief always carries an adversarial
counter-evidence pass over the result the crossed queries fed.
Egress - query-privacy-gate.mjs, unchanged, with its non-overridable
hard-block tier.
Fixed at both sites: the Hard rules entry and the Phase 4.5 paragraph that
repeated the same attribution.
A doc-consistency pin now fails if the amendment names the privacy gate as
the compensating control again, and requires it to name both
contrarian-researcher and the egress framing - so the two cannot quietly
re-merge.
Review finding 4888ae847240142933469d70bafb6a9c23b7b2eb (MINOR).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LuGhWAbWyRFBFeemfhxoVv
936 lines
44 KiB
Markdown
936 lines
44 KiB
Markdown
---
|
||
name: trekresearch
|
||
description: Deep research combining local codebase analysis with external knowledge, producing structured research briefs with triangulation and confidence ratings
|
||
argument-hint: "[--project <dir>] [--quick | --local | --external | --fg] [--engine swarm|deep-research] <research question>"
|
||
allowed-tools: Agent, Read, Glob, Grep, Write, Edit, Bash, AskUserQuestion, WebSearch, WebFetch, mcp__tavily__tavily_search, mcp__tavily__tavily_research
|
||
---
|
||
|
||
# Ultraresearch Local v1.0
|
||
|
||
Deep, multi-phase research that combines local codebase analysis with external
|
||
knowledge. Uses specialized agent swarms to investigate multiple dimensions in
|
||
parallel, then triangulates findings to produce insights that neither local nor
|
||
external research could provide alone.
|
||
|
||
**Design principle: Context Engineering** — build the right context by orchestrating
|
||
specialized agents, each seeing only what they need. The value is in triangulation
|
||
(cross-checking local vs. external) and synthesis (insights from combining both).
|
||
|
||
**Pipeline integration:** Research briefs feed into trekplan via `--research`:
|
||
```
|
||
/trekresearch <question> → brief → /trekplan --research <brief> <task>
|
||
```
|
||
|
||
## Phase 1 — Parse mode and validate input
|
||
|
||
Parse `$ARGUMENTS` for mode flags. Flags can appear in any order before the
|
||
research question. Collect all flags first, then treat the remainder as the
|
||
research question.
|
||
|
||
Supported flags:
|
||
|
||
1. `--quick` — lightweight research, no agent swarm. The command itself does
|
||
3-5 targeted searches inline. Set **mode = quick**.
|
||
|
||
2. `--local` — only codebase research. Skip external agents and gemini bridge.
|
||
Set **scope = local**.
|
||
|
||
3. `--external` — only external research. Skip codebase analysis agents.
|
||
Set **scope = external**.
|
||
|
||
4. `--fg` — accepted as a no-op alias for backwards compatibility. Execution
|
||
is always foreground as of v2.4.0. Set **execution = foreground** (the
|
||
only mode).
|
||
|
||
5. `--project <dir>` — attach this research to an trekbrief project folder.
|
||
The brief will be written to `{dir}/research/{NN}-{slug}.md` (auto-incremented
|
||
index) instead of the default `.claude/research/` path. Set **project_dir = {dir}**.
|
||
|
||
If `{dir}` does not exist:
|
||
```
|
||
Error: project directory not found: {dir}
|
||
Run /trekbrief first to create it.
|
||
```
|
||
Create `{dir}/research/` if it does not already exist.
|
||
|
||
When `{dir}/brief.md` exists, ALWAYS run the brief-validator (soft mode)
|
||
AND the composed phase-model resolver for this command's phase before
|
||
continuing. The resolver's JSON output `{effort, model, source}`
|
||
(brief signal > profile > default) is captured as `phase_signal_result`
|
||
and used at Agent-spawn sites in Phase 4 to inject the resolved model:
|
||
|
||
```bash
|
||
# When --min-brief-version was passed, append --min-version {min_brief_version}
|
||
# so an older brief raises BRIEF_VERSION_BELOW_MINIMUM (warn, never block).
|
||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/brief-validator.mjs --soft --json [--min-version {min_brief_version}] "{dir}/brief.md"
|
||
# v5.9 — composed resolver: ONE call returns {effort, model, source}.
|
||
# Append --profile {profile} when the operator passed --profile.
|
||
node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/resolver.mjs --resolve-phase-model --phase research --brief-path "{dir}/brief.md" [--profile {profile}] --json
|
||
```
|
||
|
||
6. `--gates` — autonomy control. When present, set `gates_mode = true`. The
|
||
research command will pause after each topic completes ("Topic N
|
||
complete. Proceed to topic N+1? (yes/no)"). Default `gates_mode = false`
|
||
means topics run continuously. The flag is consumed by the autonomy-gate
|
||
state machine via the CLI shim:
|
||
`node ${CLAUDE_PLUGIN_ROOT}/lib/util/autonomy-gate.mjs --state X --event Y --gates {true|false}`.
|
||
|
||
7. `--min-brief-version <ver>` — (optional) version floor for an attached
|
||
`--project` brief. Set **min_brief_version = {ver}** (e.g. `2.2`). Forwarded
|
||
to the brief-validator below as `--min-version {ver}`; an older brief emits a
|
||
`BRIEF_VERSION_BELOW_MINIMUM` **warning** (never blocks) because framing
|
||
enforcement only fires at `≥ 2.2`. Absent → no version check. See
|
||
`docs/HANDOVER-CONTRACTS.md` §Handover 1 for the pre-2.2 enforcement hole.
|
||
|
||
8. `--engine <name>` — opt-in external-research engine. Accepts `--engine <name>`
|
||
where `<name>` is `swarm` or `deep-research`. **Default: `swarm`** (unchanged
|
||
behavior). `swarm` runs Voyage's own external-research agent swarm;
|
||
`deep-research` delegates the external phase to Claude Code's built-in
|
||
`/deep-research` dynamic workflow and adapts its report into the research-brief
|
||
schema (requires Claude Code 2.1.154+ and dynamic workflows enabled; falls back
|
||
to `swarm` and notes the fallback if unavailable — never hard-fails). Orthogonal
|
||
to `--profile`/`phase_signals`; only affects the external phase. Set
|
||
**engine = {swarm|deep-research}** (the *requested* engine).
|
||
|
||
Flags can be combined:
|
||
- `--local` — local-only research
|
||
- `--external --quick` — external-only, lightweight
|
||
- `--project <dir> --external` — attach external research to a project
|
||
- `--quick` alone implies both local and external (lightweight)
|
||
|
||
Defaults: **scope = both**, **execution = foreground** (only mode as of
|
||
v2.4.0), **project_dir = none**, **engine = swarm**.
|
||
|
||
After stripping flags, the remaining text is the **research question**.
|
||
|
||
If no research question is provided, output usage and stop:
|
||
|
||
```
|
||
Usage: /trekresearch <research question>
|
||
/trekresearch --quick <research question>
|
||
/trekresearch --local <research question>
|
||
/trekresearch --external <research question>
|
||
/trekresearch --fg <research question>
|
||
/trekresearch --project <dir> [--external|--local|--quick|--fg] <research question>
|
||
|
||
Modes:
|
||
default Interview → foreground research (local + external) → brief
|
||
--quick Interview (short) → inline research (no agent swarm)
|
||
--local Only codebase analysis agents (skip external + Gemini)
|
||
--external Only external research agents (skip codebase analysis)
|
||
--fg No-op alias (foreground is the only mode as of v2.4.0)
|
||
--project Write brief into an trekbrief project folder (auto-indexed)
|
||
--engine Opt-in external-research engine: swarm (default) | deep-research
|
||
|
||
Flags can be combined: --local, --external --quick, --project <dir> --external
|
||
|
||
Examples:
|
||
/trekresearch Should we migrate from Express to Fastify?
|
||
/trekresearch --quick What auth libraries are popular for Node.js?
|
||
/trekresearch --local How is error handling structured in this codebase?
|
||
/trekresearch --external What are the security implications of using Redis for sessions?
|
||
/trekresearch --fg --local What patterns does this codebase use for database access?
|
||
/trekresearch --project .claude/projects/2026-04-18-jwt-auth --external What JWT library is best for Node.js?
|
||
/trekresearch --project <dir> --external --engine deep-research <research question>
|
||
```
|
||
|
||
Do not continue past this step if no question was provided.
|
||
|
||
Report the detected mode:
|
||
```
|
||
Mode: {default | quick}, Scope: {both | local | external}, Execution: foreground
|
||
Project: {project_dir or "-"}
|
||
Engine (requested): {swarm | deep-research}
|
||
Question: {research question}
|
||
```
|
||
|
||
### Compute brief destination
|
||
|
||
If **project_dir is set**:
|
||
- Scan `{project_dir}/research/` for existing files matching `NN-*.md`.
|
||
- Find the highest existing index; set `N = highest + 1`. If no files exist, `N = 1`.
|
||
- Zero-pad to 2 digits: `01`, `02`, ...
|
||
- Brief destination: `{project_dir}/research/{NN}-{slug}.md`
|
||
|
||
If **project_dir is not set**:
|
||
- Brief destination: `.claude/research/trekresearch-{YYYY-MM-DD}-{slug}.md`
|
||
|
||
Store as `brief_destination` for use in later phases.
|
||
|
||
## Phase 2 — Research interview
|
||
|
||
Use `AskUserQuestion` to clarify the research question. Ask **one question at a time**.
|
||
|
||
The interview is shorter than trekplan's (2-4 questions, not 3-8) because research
|
||
is more focused than planning.
|
||
|
||
### Interview flow
|
||
|
||
**Start with the research question itself.** If the user provided a clear, specific
|
||
question, you may skip directly to follow-ups.
|
||
|
||
**Core questions (pick 2-4 based on clarity of initial question):**
|
||
|
||
1. **Decision context:** "What decision does this research feed? Are you evaluating
|
||
options, investigating feasibility, or building understanding?"
|
||
*Skip if the question itself makes this obvious.*
|
||
|
||
2. **Dimensions:** "Are there specific aspects you care about most? (e.g., performance,
|
||
security, migration cost, team learning curve)"
|
||
*Skip if the question is narrow enough that dimensions are obvious.*
|
||
|
||
3. **Prior knowledge:** "What do you already know about this topic? What have you
|
||
tried or ruled out?"
|
||
*Always useful — prevents redundant research.*
|
||
|
||
4. **Constraints:** "Are there constraints that should guide the research?
|
||
(e.g., must be open-source, must support X, budget limitations)"
|
||
*Skip if no constraints are apparent.*
|
||
|
||
**Rules:**
|
||
- If the user says "just research it", "skip", or similar — stop interviewing.
|
||
Use the research question as-is.
|
||
- For `--quick` mode: ask 1-2 questions maximum.
|
||
- Never ask about things you can discover from the codebase.
|
||
|
||
### Determine research dimensions
|
||
|
||
Based on the interview, identify 3-8 research dimensions. These are the facets
|
||
of the question that will be investigated in parallel. Examples:
|
||
|
||
- "Should we use Redis?" → dimensions: performance, reliability, operational
|
||
complexity, security, cost, team familiarity
|
||
- "How should we handle auth?" → dimensions: standards compliance, implementation
|
||
complexity, library ecosystem, security posture, scalability
|
||
|
||
Report dimensions:
|
||
```
|
||
Research dimensions identified:
|
||
1. {Dimension 1}
|
||
2. {Dimension 2}
|
||
...
|
||
```
|
||
|
||
## Phase 3 — Slug and destination (foreground)
|
||
|
||
Generate a slug from the research question (first 3-4 meaningful words,
|
||
lowercase, hyphens). Confirm the `brief_destination` computed in Phase 1.
|
||
|
||
Report to the user:
|
||
|
||
```
|
||
Research pipeline running in foreground.
|
||
|
||
Question: {research question}
|
||
Dimensions: {N} identified
|
||
Scope: {both | local | external}
|
||
Project: {project_dir or "-"}
|
||
Brief: {brief_destination}
|
||
```
|
||
|
||
Then continue to the next phase inline.
|
||
|
||
> **Why foreground (for now)?** The research-orchestrator was moved out of
|
||
> background mode in v2.4.0 because, before Claude Code 2.1.172, the harness
|
||
> did not expose the Agent tool to sub-agents, so an orchestrator launched
|
||
> with `run_in_background: true` could not spawn the documented research
|
||
> swarm (`docs-researcher`, `community-researcher`, etc.) and silently
|
||
> degraded to single-context reasoning without WebSearch / Tavily / WebFetch
|
||
> / Gemini. As of CC 2.1.172 sub-agents can spawn sub-agents (up to 5 levels
|
||
> deep), so that block no longer holds — a delegated redesign is under
|
||
> evaluation (see `docs/cc-upgrade-2.1.181-decision-matrix.md`, W1/CC-26).
|
||
> Until then, running the phases inline in main context keeps the swarm
|
||
> intact. Use `claude -p` in a separate terminal window for long-running
|
||
> headless work.
|
||
|
||
---
|
||
|
||
**All remaining phases run inline in the main command context.**
|
||
|
||
---
|
||
|
||
## Phase 3.5 — Quick mode (inline research)
|
||
|
||
**Skip this phase entirely unless mode = quick.**
|
||
|
||
For quick mode, do NOT launch an agent swarm. Instead, do lightweight research
|
||
directly using available tools.
|
||
|
||
### Quick local research (if scope includes local)
|
||
|
||
- `Glob` for files matching key terms from the research question (up to 3 patterns)
|
||
- `Grep` for relevant definitions, patterns, or usage (up to 5 patterns)
|
||
- Read the 2-3 most relevant files found
|
||
|
||
### Quick external research (if scope includes external)
|
||
|
||
Use available search tools directly (in this priority order):
|
||
1. `mcp__tavily__tavily_search` — if available, use for 2-3 targeted queries
|
||
2. `WebSearch` — fallback for 2-3 targeted queries
|
||
3. `WebFetch` — fetch 1-2 specific pages if URLs were found
|
||
|
||
### Quick synthesis
|
||
|
||
Synthesize findings inline. Write a lightweight research brief to the destination
|
||
path, following the research-brief-template but with shorter sections and fewer
|
||
dimensions.
|
||
|
||
Skip to Phase 8 (stats tracking) after writing the brief.
|
||
|
||
## Phase 4 — Parallel research (agent swarm)
|
||
|
||
**Determine which agents to launch based on scope:**
|
||
|
||
### Local agents (scope = both or local)
|
||
|
||
Reuse existing plugin agents with research-focused prompts. These agents are
|
||
designed for planning, but work equally well for research when prompted differently.
|
||
|
||
| Agent | Purpose in research context |
|
||
|-------|----------------------------|
|
||
| `architecture-mapper` | How the architecture relates to the research question |
|
||
| `dependency-tracer` | Dependencies and integrations relevant to the topic |
|
||
| `task-finder` | Existing code that relates to the research question |
|
||
| `git-historian` | Recent changes and ownership relevant to the topic |
|
||
| `convention-scanner` | Coding patterns relevant to evaluating options |
|
||
|
||
For each local agent, prompt with the research question, NOT a task description:
|
||
|
||
- architecture-mapper: "Analyze the architecture relevant to this research question:
|
||
{question}. Focus on how {topic} relates to current patterns and constraints."
|
||
- dependency-tracer: "Trace dependencies relevant to this research question: {question}.
|
||
Identify which modules would be affected by {topic}."
|
||
- task-finder: "Find existing code relevant to this research question: {question}.
|
||
Look for prior implementations, patterns, or utilities related to {topic}."
|
||
- git-historian: "Analyze git history relevant to this research question: {question}.
|
||
Who owns the relevant code? What has changed recently in related areas?"
|
||
- convention-scanner: "Discover coding conventions relevant to evaluating {question}.
|
||
What patterns would a solution need to follow?"
|
||
|
||
### Engine selection (scope = both or external)
|
||
|
||
`--engine` affects ONLY the external portion of research. The local agents
|
||
(`### Local agents` above) and Phases 6–7 (triangulation, synthesis, brief
|
||
writing) are **engine-agnostic** — they run identically regardless of engine.
|
||
|
||
`--engine` is **moot** (treated as `swarm`) whenever the external phase does not
|
||
run at all: `--local`, `--quick`, `effort == 'low'`, or a profile with
|
||
`external_research_enabled == false` (the `economy`/`balanced` auto-disable — see
|
||
Profile below). The profile's on/off switch wins. Initialize
|
||
`effective_engine = {requested engine}`.
|
||
|
||
**engine = swarm (default):** run the `### External agents` + `### Bridge agent`
|
||
blocks below unchanged. This is byte-for-byte the current path, so `--engine swarm`
|
||
changes nothing (SC1). Keep the native-swarm anchors intact ("in parallel",
|
||
"single message", `model: "opus"`).
|
||
|
||
**engine = deep-research:**
|
||
|
||
1. **Coarse pre-gate (best-effort, NOT a trust signal).** `Bash: claude --version`;
|
||
parse the leading `X.Y.Z` (e.g. from `2.1.196 (Claude Code)`) and compare
|
||
numerically against `2.1.154` — split each on `.` and compare major, then minor,
|
||
then patch as integers (do NOT string-compare; lexical comparison mis-orders
|
||
multi-digit patch numbers). If the version is `< 2.1.154`, OR if
|
||
`disableWorkflows: true` / `CLAUDE_CODE_DISABLE_WORKFLOWS=1` is set, skip to the
|
||
fallback (step 4). **If `claude` is not on PATH inside the Bash tool (possible
|
||
under `claude -p`) or the version cannot be parsed, treat the pre-gate as
|
||
*indeterminate* and proceed to step 2 — do NOT hard-fail.** There is no positive
|
||
availability probe (research Dim 4), so a passing pre-gate does not guarantee the
|
||
workflow runs; the post-hoc check (step 3) is the authoritative guard.
|
||
|
||
2. **Run.** Instruct Claude (in prose, this turn) to run
|
||
`/deep-research <research question>` and request per-claim citations. Note:
|
||
interactive default/acceptEdits triggers a per-run approval prompt; `claude -p` /
|
||
SDK / bypass runs immediately.
|
||
|
||
3. **Post-hoc presence + provenance check (the real guard).** Verify a real, cited
|
||
`/deep-research` report actually landed in context — substantive findings with
|
||
citations, not an empty/denied/errored turn and not bare error text. This check
|
||
must be **robust to all failure manifestations** (workflow disabled, approval
|
||
denied, runtime error, empty output), because the disabled-headless behavior is
|
||
undocumented: no recognizable cited report in context → fall back, regardless of
|
||
how the failure surfaces.
|
||
|
||
4. **On no real report (fallback):** set `effective_engine = swarm`, run the swarm
|
||
blocks below, and **log the fallback at this decision point** — print
|
||
`Engine: deep-research → swarm (fallback: <reason>)` and carry the reason into the
|
||
Phase-8 Present summary and the brief's `## Executive Summary`. **NEVER fabricate
|
||
or synthesize a substitute report** — a structurally-valid-but-invented brief
|
||
passes the structure-only validator and silently poisons `/trekplan`; that is the
|
||
worst outcome of this feature.
|
||
|
||
5. **On a real report:** keep `effective_engine = deep-research`, log
|
||
`Engine: deep-research (active)`, and carry the report into Phase 6 triangulation
|
||
as the external-findings input (adapted in Phase 7 — see the Deep-research engine
|
||
adapter below).
|
||
|
||
### External agents (scope = both or external)
|
||
|
||
Launch the new research-specialized agents:
|
||
|
||
| Agent | Purpose |
|
||
|-------|---------|
|
||
| `docs-researcher` | Official documentation, RFCs, vendor docs |
|
||
| `community-researcher` | Real-world experience, issues, blog posts |
|
||
| `security-researcher` | CVEs, audit history, supply chain risks |
|
||
| `contrarian-researcher` | Counter-evidence, overlooked alternatives |
|
||
|
||
For each external agent, pass: the research question, specific dimensions to
|
||
investigate, and any context from the interview.
|
||
|
||
### Bridge agent (scope = both or external, if enabled)
|
||
|
||
Launch `gemini-bridge` with the research question. Do NOT include findings from
|
||
other agents — the value of Gemini is independence.
|
||
|
||
### Launch rules
|
||
|
||
- Launch ALL selected agents **in parallel** in a single message
|
||
- Use model: "opus" for all sub-agents (the orchestrator runs on Opus)
|
||
- Scale maxTurns by codebase size for local agents (same as trekplan):
|
||
small = halved, medium/large = default
|
||
- convention-scanner: medium+ codebases only (50+ files)
|
||
|
||
## Phase 4.5 — Dimension discovery
|
||
|
||
**Skip this phase entirely unless `phase_signal_result.effort == 'high'` AND
|
||
`VOYAGE_STORM_ENABLED=1`.** Both conditions, never either.
|
||
|
||
This phase never invokes `research-loop-cap.mjs`, so the cap's own flag check
|
||
does not cover it — the flag has to be read here. Gating on effort alone would
|
||
leave discovery mutating the dimension list at `effort: high` with the flag
|
||
unset, making `dimensions` diverge from `dimensions_baseline` and putting the
|
||
decline branch out of reach for half the mechanism. Doing nothing must leave
|
||
**both** STORM phases inert.
|
||
|
||
Phase 4 retrieves more than the interview knew to ask for. This phase mines
|
||
that surplus: findings that were **retrieved but unintegrated** — material an
|
||
agent surfaced that no interview dimension claims.
|
||
|
||
1. **Mine.** Walk the Phase-4 agent results and collect findings that map to
|
||
no existing dimension.
|
||
2. **Rerank.** Order candidates by relevance to the research question **and**
|
||
dissimilarity to the dimensions already on the list. A candidate that
|
||
restates an existing dimension is not a discovery.
|
||
3. **Augment under the existing ceiling.** Append candidates to the dimension
|
||
list only while the **whole** list (interview + discovered) stays at or
|
||
below `maxDimensions: 8` (`settings.json:16`). The ceiling is **not**
|
||
raised here, so the documented 3–8 dimension range stays true and the
|
||
README prose about it stays untouched. If the interview already produced 8
|
||
dimensions, this phase discovers nothing and says so.
|
||
|
||
**The ceiling has a reader — use it.** Once the final list is settled, run
|
||
the check below. Exit 1 means the list exceeded the ceiling: drop discovered
|
||
dimensions until it passes. Do not proceed to Phase 5 on a rejected list —
|
||
the turn budget is sized against this same ceiling, so a list over it spends
|
||
a budget that was never approved for it.
|
||
|
||
```bash
|
||
# Same VOYAGE_ROOT resolution as the per-turn protocol in Phase 5. Exit 0 =
|
||
# within the ceiling, exit 1 = rejected. JSON on stdout: {ok, count, ceiling, reason?}
|
||
node "$VOYAGE_ROOT/lib/util/research-loop-cap.mjs" --check-dimensions {final dimension count}
|
||
```
|
||
|
||
The ceiling constant is `MAX_TOTAL_DIMENSIONS` in
|
||
`lib/util/research-loop-cap.mjs` — deliberately the same constant that sizes
|
||
the Phase 5 turn budget, so the two axes of the bounded-cost NFR cannot end
|
||
up enforcing different numbers for one `settings.json:16` value.
|
||
4. **Record the baseline.** Keep the interview-derived count as
|
||
`dimensions_baseline` so the discovered delta is machine-readable against
|
||
the final `dimensions` (Phase 8 stats).
|
||
5. **Attest membership, not just the count.** Set
|
||
`dimensions_baseline_preserved: true` only if EVERY interview-derived
|
||
dimension is still on the final list — this phase appends, it never replaces.
|
||
Set it `false` if any was dropped, merged away, or rewritten. A count delta
|
||
cannot show this: dropping two interview dimensions and appending three
|
||
discovered ones is `+1` and still not a superset, which is exactly what the
|
||
Success Criterion forbids. `storm-measure.mjs --activation-check` reads the
|
||
field and fails when it is absent, so omitting it is not the silent default.
|
||
|
||
Every outbound query generated from a discovered dimension passes
|
||
`query-privacy-gate.mjs` before it leaves the machine — see the per-turn
|
||
protocol in Phase 5. That gate controls the **egress** risk this phase's
|
||
Independence crossing creates: local paths and identifiers travelling inside a
|
||
query. It does **not** control the **bias** risk — it inspects query content and
|
||
cannot stop a local finding from steering an external agent's question. The bias
|
||
controls are structural (blind initial swarm, append-only crossing, unconditional
|
||
`contrarian-researcher` at `effort: high`); see Hard rules → Independence.
|
||
|
||
## Phase 5 — Targeted follow-ups
|
||
|
||
Review all agent results. Identify knowledge gaps — dimensions where findings
|
||
are thin, contradictory, or missing (**under-illuminated dimensions**).
|
||
|
||
**Standard and low effort — unchanged single pass.** For each significant gap,
|
||
launch a targeted follow-up agent (model: "opus") with a narrow, specific
|
||
brief. Maximum 2 follow-ups. If no gaps exist, skip: "Initial research
|
||
sufficient — no follow-ups needed." Then go to Phase 6.
|
||
|
||
**The bounded loop below runs ONLY when `phase_signal_result.effort == 'high'`**
|
||
(resolved in Phase 1; see `### High-effort behavior (v5.1.1)`). At any other
|
||
effort this whole sub-section is inert — no loop, no cap ledger, no new
|
||
counters beyond zero.
|
||
|
||
### Loop bound
|
||
|
||
**Maximum 3 turns per under-illuminated dimension.** The bound is per
|
||
dimension, not per run: the worst case is 3 turns × the whole dimension list
|
||
under the `maxDimensions: 8` ceiling (`settings.json:16`), which is what
|
||
`research-loop-cap.mjs` sizes itself against. The cap counts itself from its
|
||
own append-only ledger — it never asks this prose how many turns it has used.
|
||
|
||
The loop is **default-off**: `research-loop-cap.mjs` grants a budget of 0
|
||
unless `VOYAGE_STORM_ENABLED=1`. Doing nothing leaves the mechanism off.
|
||
|
||
### Loop scope marker
|
||
|
||
`hooks/scripts/pre-agent-cap.mjs` (PreToolUse on `WebSearch|WebFetch|Task`)
|
||
enforces the same bound in the harness rather than trusting this prose — but it
|
||
enforces **only** for a session that carries a scope marker, and allows
|
||
unconditionally for every session that does not. That is what keeps a globally
|
||
wired PreToolUse hook from denying tool calls in unrelated sessions. Write the
|
||
marker once, immediately before the first turn:
|
||
|
||
`CLAUDE_PLUGIN_DATA` is **empty in the Bash tool's process env** even in a
|
||
plugin-enabled session, so the root is resolved with the same fallback
|
||
`research-loop-cap.mjs` uses — `~/.claude/voyage`. Reader and writer must
|
||
resolve identically; a marker written where the hook does not look leaves the
|
||
hook allowing unconditionally while the docs call it enforcing.
|
||
|
||
```bash
|
||
# Arms the PreToolUse cap for THIS session only.
|
||
# CLAUDE_CODE_SESSION_ID is the same id the hook reads as `session_id`.
|
||
DATA="${CLAUDE_PLUGIN_DATA:-$HOME/.claude/voyage}"
|
||
case "$DATA" in /*) SCOPE_DIR="$DATA/trekresearch-loop-scope" ;; *) SCOPE_DIR="" ;; esac
|
||
if [ -n "$SCOPE_DIR" ] && [ -n "${CLAUDE_CODE_SESSION_ID:-}" ] && mkdir -p "$SCOPE_DIR" 2>/dev/null; then
|
||
printf '{"runId":"%s","startedAt":"%s"}\n' "{run_id}" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||
> "$SCOPE_DIR/${CLAUDE_CODE_SESSION_ID}.json"
|
||
else
|
||
echo "[voyage] scope marker not written — harness cap stays inert for this run"
|
||
fi
|
||
```
|
||
|
||
An empty `CLAUDE_CODE_SESSION_ID` is checked before the path is composed, not
|
||
after: unset, the marker becomes `.json`, which no hook lookup matches and no
|
||
TTL sweep ever cleans up.
|
||
|
||
`runId` MUST be the same `{run_id}` passed to `research-loop-cap.mjs --run-id`.
|
||
The hook counts ledger lines carrying that id, so a marker written with any
|
||
other id counts zero turns and enforces nothing.
|
||
|
||
**A failed marker write is not a reason to stop.** The hook is defence in
|
||
depth; `research-loop-cap.mjs` is the gate and stays correct on its own. Report
|
||
the failure to the operator and run the loop. The reverse — skipping the budget
|
||
gate because a marker exists — is never allowed.
|
||
|
||
**Removal belongs to every exit below, especially the exhausted one.** The hook
|
||
denies `WebSearch`/`WebFetch`/`Task` once `research-loop-cap.mjs` has denied a
|
||
turn — the gate records its own denials, so the LAST granted turn still runs its
|
||
queries and exhaustion reaches you through exit 2 of the budget gate below, not
|
||
through a blocked tool call. Once denied, the hook keeps denying for as long as
|
||
the marker is there — including Phase 6, which spawns agents. A marker that outlives the loop turns a bound on this loop into a brick
|
||
on the rest of the session. Cleanup covers the three exits and nothing else: a
|
||
crashed session runs no cleanup at all, and is covered instead by the hook's
|
||
TTL (default 2h, `VOYAGE_CAP_SCOPE_TTL_MS`), which auto-resets a stale marker.
|
||
A crash mid-loop leaves no denial record, so a resumed session is not blocked by
|
||
it; only a crash AFTER the cap denied a turn hands the resume a deny window, and
|
||
every denial prints the marker path to delete.
|
||
|
||
```bash
|
||
# Removal — idempotent, safe to repeat. Same root, same absolute-path guard as
|
||
# the write: a remove that accepts a root the write rejected (or vice versa)
|
||
# leaves markers the loop believes it cleaned up.
|
||
DATA="${CLAUDE_PLUGIN_DATA:-$HOME/.claude/voyage}"
|
||
case "$DATA" in /*) SCOPE_DIR="$DATA/trekresearch-loop-scope" ;; *) SCOPE_DIR="" ;; esac
|
||
[ -n "$SCOPE_DIR" ] && [ -n "${CLAUDE_CODE_SESSION_ID:-}" ] && \
|
||
rm -f "$SCOPE_DIR/${CLAUDE_CODE_SESSION_ID}.json"
|
||
```
|
||
|
||
### Per-turn protocol
|
||
|
||
Each turn targets exactly one under-illuminated dimension, and runs two gates
|
||
before it spends anything:
|
||
|
||
```bash
|
||
# 0. Resolve the plugin root ONCE. ${CLAUDE_PLUGIN_ROOT} is substituted in this
|
||
# command's text but is EMPTY in the Bash tool's process env, and a bare
|
||
# `node ${CLAUDE_PLUGIN_ROOT}/lib/…` then runs `node /lib/…`, which exits 1 —
|
||
# indistinguishable from a gate that said no.
|
||
VOYAGE_ROOT="${CLAUDE_PLUGIN_ROOT:-}"
|
||
case "$VOYAGE_ROOT" in
|
||
/*) ;;
|
||
*) VOYAGE_ROOT="$(ls -d "$HOME"/.claude/plugins/cache/*/voyage 2>/dev/null | head -1)" ;;
|
||
esac
|
||
if [ ! -f "$VOYAGE_ROOT/lib/util/research-loop-cap.mjs" ]; then
|
||
echo "[voyage] gates could not run — plugin root unresolved (exit 2). NOT a denial:"
|
||
echo " stop the loop and report to the operator. Never proceed ungated."
|
||
exit 2
|
||
fi
|
||
|
||
# 1. Budget gate — per turn, per dimension. Exit 0 = granted, exit 1 = denied.
|
||
# JSON on stdout: {ok, used, budget, reason?}
|
||
node "$VOYAGE_ROOT/lib/util/research-loop-cap.mjs" \
|
||
--run-id {run_id} --dimension {dimension} --effort {phase_signal_result.effort}
|
||
|
||
# 2. Privacy gate — EVERY outbound query, before it leaves the machine.
|
||
# Exit 0 = send as-is; exit 1 = rewrite the query and re-gate. Never bypass.
|
||
node "$VOYAGE_ROOT/lib/validators/query-privacy-gate.mjs" "{query text}"
|
||
```
|
||
|
||
**Exit 2 is not exit 1.** A denied budget gate is an exit condition, not a
|
||
retry. A failed privacy gate is a rewrite: the hard-block tier (secret-shaped
|
||
strings) is never operator-overridable, so a query that trips it must be
|
||
reformulated, not forced through. A gate that *could not run* is neither — no
|
||
rewrite can clear it, so treat it as a hard stop and say so, rather than
|
||
rewriting a query that was never the problem.
|
||
|
||
**Empty turns.** A turn that returns no findings, or findings without
|
||
citations, is marked `empty`. An empty turn is counted in `empty_turns` and
|
||
does NOT re-target the same dimension — re-asking the same question of the
|
||
same silence is how a bounded loop turns into an unbounded one. Move to the
|
||
next under-illuminated dimension, or exit.
|
||
|
||
### Exits (all three, always one of them)
|
||
|
||
1. **Converged** — the dimension carries findings with citations and no
|
||
remaining contradiction. Stop turning on it. This is the normal exit. Once
|
||
the last dimension has converged, remove the scope marker.
|
||
2. **Cap exhausted** — `research-loop-cap.mjs` denies the turn. Print the
|
||
exhaustion **visibly** to the operator, never silently:
|
||
`Loop bound reached for dimension {dimension} after {N} turns — remaining
|
||
gaps are carried into the brief as open questions.` A silent cap is
|
||
indistinguishable from convergence, and that confusion is exactly what this
|
||
phase exists to prevent. Then remove the scope marker — leaving it here is
|
||
what would block Phase 6.
|
||
3. **Operator stop** — the operator interrupts. Remove the scope marker first,
|
||
then carry whatever has been gathered into Phase 6 and record the remaining
|
||
gaps as open questions. Do not re-enter the loop after a stop.
|
||
|
||
### When the loop does not apply
|
||
|
||
**No-brief default.** Without `--project` (or with a project whose `brief.md`
|
||
is absent), there are no `phase_signals` to resolve, so `effort = 'standard'`,
|
||
the loop does not run, and all new counters (`conv_turns`, `empty_turns`) are
|
||
emitted as `0`.
|
||
|
||
**Precedence matrix — each entry independently makes the loop moot**, the same
|
||
way `--engine` is moot when the external phase does not run (see the moot gate
|
||
in Phase 4):
|
||
|
||
| Condition | Effect on the loop |
|
||
|-----------|--------------------|
|
||
| `--quick` | Moot — Phase 3.5 skips to Phase 8; the swarm never runs |
|
||
| `--local` | Moot — no outbound queries to bound |
|
||
| `external_research_enabled: false` (profile) | Moot — the profile's on/off switch wins |
|
||
|
||
**Interaction rule.** A brief that carries `effort: high` **without** a
|
||
`model`, under a cheap profile (`economy`/`balanced`): the effort signal
|
||
governs orchestration shape, so the loop is armed, but the profile still
|
||
supplies the model — and if that profile disables external research, the
|
||
matrix above wins and the loop is moot regardless of effort.
|
||
|
||
**Honesty (hard rule, restated for this loop).** More turns do not make a
|
||
finding more credible. Turn count is a cost, not evidence: report what the
|
||
citations support, and let an exhausted cap show up as open questions rather
|
||
than as confidence.
|
||
|
||
## Phase 6 — Triangulation
|
||
|
||
This is the KEY phase that makes trekresearch more than aggregation.
|
||
|
||
For each research dimension:
|
||
|
||
1. **Collect** — gather relevant findings from local AND external agents
|
||
2. **Compare** — do local findings agree with external findings?
|
||
3. **Flag contradictions** — where they disagree, present both sides with evidence
|
||
4. **Cross-validate** — use codebase facts to validate external claims:
|
||
- External says "library X is fast" → local shows the codebase already uses
|
||
a similar pattern that could benchmark against
|
||
- External says "pattern Y is best practice" → local shows the codebase uses
|
||
pattern Z which conflicts
|
||
5. **Rate confidence** per dimension:
|
||
- **high** — multiple authoritative sources agree, local evidence confirms
|
||
- **medium** — good sources but limited cross-validation
|
||
- **low** — single source, limited evidence
|
||
- **contradictory** — credible sources actively disagree
|
||
|
||
Compute overall confidence as a weighted average (0.0-1.0) based on dimension
|
||
confidence levels and their relative importance.
|
||
|
||
## Phase 7 — Synthesis and brief writing
|
||
|
||
Read the research brief template:
|
||
@${CLAUDE_PLUGIN_ROOT}/templates/research-brief-template.md
|
||
|
||
Write the research brief following the template. Key rules:
|
||
|
||
1. **Executive Summary** — 3 sentences. Answer, confidence, key caveat.
|
||
2. **Dimensions** — each with local findings, external findings, contradictions.
|
||
3. **Synthesis** — NOT a summary. NEW insights from triangulation.
|
||
4. **Open Questions** — what remains unresolved and why.
|
||
5. **Recommendation** — only if decision-relevant. Omit for exploratory research.
|
||
6. **Sources** — every claim traced to URL or codebase path.
|
||
|
||
Generate the slug from the research question (first 3-4 meaningful words).
|
||
Write the brief to the `brief_destination` computed in Phase 1:
|
||
- With `--project`: `{project_dir}/research/{NN}-{slug}.md`
|
||
- Without `--project`: `.claude/research/trekresearch-{YYYY-MM-DD}-{slug}.md`
|
||
|
||
Create the parent directory if it does not exist.
|
||
|
||
### Deep-research engine adapter (engine = deep-research only)
|
||
|
||
**Only when `effective_engine == deep-research`.** The swarm path skips this
|
||
entirely — its findings already flow through Phases 6–7 unchanged (SC1).
|
||
|
||
Transform the in-context `/deep-research` report INTO
|
||
`@${CLAUDE_PLUGIN_ROOT}/templates/research-brief-template.md` — do NOT paste the
|
||
raw report. Specifically:
|
||
|
||
- Reduce the report to ≥ 1 `### {Dimension} -- Confidence: {high|medium|low}`
|
||
entry, each carrying **External findings** bullets with per-claim source URLs.
|
||
Local findings still come from the local agents (Phase 4) and are merged in per
|
||
dimension as usual.
|
||
- Emit a numeric `confidence ∈ [0,1]` in frontmatter and a 3-sentence
|
||
`## Executive Summary` (answer, confidence, key caveat).
|
||
- Populate `## Sources` from the report's citations.
|
||
- **If the report lacks per-claim URLs, lower the confidence and note the gap in
|
||
`## Open Questions` — do NOT fabricate URLs.** Provenance you cannot cite is not
|
||
provenance.
|
||
- If the report is large, bound the transform to the top dimensions to avoid
|
||
context truncation.
|
||
|
||
### Output self-check (engine = deep-research only)
|
||
|
||
**Only when `effective_engine == deep-research`.** After writing to
|
||
`brief_destination`, run the output validator and repair-or-fall-back. This mirrors
|
||
the trekplan Phase-8 write→validate→repair self-check; the swarm path does NOT run
|
||
it, so swarm behavior is unchanged (SC1):
|
||
|
||
```bash
|
||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/research-validator.mjs --json "{brief_destination}"
|
||
```
|
||
|
||
On `valid: false`, repair the brief to satisfy the reported errors and re-run the
|
||
validator. If it cannot be made valid (e.g. the report was too thin to yield even
|
||
one dimension), set `effective_engine = swarm`, fall back to the swarm engine for
|
||
this run (and log the fallback per the Engine selection step), rather than emit an
|
||
invalid brief.
|
||
|
||
## Phase 8 — Present and track
|
||
|
||
Present a summary to the user:
|
||
|
||
```
|
||
## Ultraresearch Complete
|
||
|
||
**Question:** {research question}
|
||
**Mode:** {default | quick}, Scope: {both | local | external}
|
||
**Brief:** {brief_destination}
|
||
**Project:** {project_dir or "-"}
|
||
**Engine (effective):** {swarm | deep-research}{, with fallback reason if it fell back}
|
||
**Confidence:** {overall confidence 0.0-1.0}
|
||
**Dimensions:** {N} researched
|
||
**Agents:** {N} local + {N} external + {gemini: used | unavailable | skipped}
|
||
|
||
### Key Findings
|
||
- {Finding 1}
|
||
- {Finding 2}
|
||
- {Finding 3}
|
||
|
||
### Contradictions Found
|
||
- {Contradiction 1, or "None — findings are consistent across sources."}
|
||
|
||
### Open Questions
|
||
- {Question 1, or "None — all dimensions adequately covered."}
|
||
|
||
You can:
|
||
- Read the full brief at {brief_destination}
|
||
- If `--project` was used: run `/trekplan --project {project_dir}` when all research topics are complete
|
||
- Otherwise: `/trekplan --research {brief_destination} --brief <your-brief.md>`
|
||
- Ask follow-up questions about specific findings
|
||
```
|
||
|
||
### Stats tracking
|
||
|
||
Write a session record to `${CLAUDE_PLUGIN_DATA}/trekresearch-stats.jsonl`
|
||
(create the file if it does not exist).
|
||
|
||
Record format (one JSON line):
|
||
```json
|
||
{
|
||
"ts": "{ISO-8601 timestamp}",
|
||
"question": "{research question (first 100 chars)}",
|
||
"mode": "{default|quick}",
|
||
"scope": "{both|local|external}",
|
||
"engine": "{effective engine: swarm|deep-research}",
|
||
"slug": "{brief slug}",
|
||
"project_dir": "{project_dir or null}",
|
||
"brief_path": "{brief_destination}",
|
||
"dimensions": {N},
|
||
"dimensions_baseline": {N},
|
||
"dimensions_baseline_preserved": {true|false},
|
||
"effort": "{low|standard|high}",
|
||
"conv_turns": {N},
|
||
"empty_turns": {N},
|
||
"unique_sources": {N},
|
||
"agents_local": {N},
|
||
"agents_external": {N},
|
||
"gemini_used": {true|false},
|
||
"confidence": {0.0-1.0},
|
||
"contradictions": {N},
|
||
"open_questions": {N}
|
||
}
|
||
```
|
||
|
||
**The six measurement fields (v5.10).** `effort` is the grouping key — the
|
||
resolved `phase_signal_result.effort` for the `research` phase, a
|
||
low-cardinality label (`low|standard|high`), and the only axis on which a
|
||
high-effort run can be compared against a standard one. Four are numeric:
|
||
`unique_sources` (distinct sources cited across the brief),
|
||
`dimensions_baseline` (the interview-derived dimension count, so the Phase 4.5
|
||
delta against `dimensions` is machine-readable), `conv_turns` (Phase 5 loop
|
||
turns actually spent), and `empty_turns` (loop turns that returned no findings
|
||
or no citations). The sixth is boolean: `dimensions_baseline_preserved`, the
|
||
Phase 4.5 attestation (step 5) that every interview dimension survived onto the
|
||
final list — the count delta cannot show membership, and the dimension NAMES
|
||
that could are prose the exporter allowlist denies. On a standard run the loop
|
||
never arms, so `dimensions_baseline == dimensions`, both turn counters are `0`,
|
||
and `dimensions_baseline_preserved` is `true` (nothing touched the list).
|
||
|
||
If `${CLAUDE_PLUGIN_DATA}` is not set or not writable, skip tracking silently.
|
||
|
||
## Profile (v4.1)
|
||
|
||
Accepts `--profile <name>` where `<name>` is `economy`, `balanced`, `premium`, `fable`,
|
||
or a custom profile under `voyage-profiles/`. Default: `premium`.
|
||
|
||
Resolution order (per `lib/profiles/resolver.mjs`):
|
||
1. `--profile` flag (source: `flag`)
|
||
2. `VOYAGE_PROFILE` env-var (source: `env`)
|
||
3. `premium` default (source: `default`)
|
||
|
||
The profile dictates `phase_models.research`, `parallel_agents`, and
|
||
`external_research_enabled`. `economy` and `balanced` profiles auto-disable
|
||
the external research swarm regardless of `--external` flag (operator
|
||
override deferred to v4.2).
|
||
|
||
Examples:
|
||
```
|
||
/trekresearch --profile economy --project .claude/projects/2026-05-09-add-auth
|
||
VOYAGE_PROFILE=balanced /trekresearch
|
||
```
|
||
|
||
Stats records emit `profile`, `phase_models`, `parallel_agents`,
|
||
`external_research_enabled`, `profile_source`, and `engine` so operators can
|
||
audit which profile and engine drove which session.
|
||
|
||
## Composition rule (v5.1)
|
||
|
||
Independent of the profile system. When `brief.md` carries
|
||
`phase_signals` (brief_version ≥ 2.1), each downstream phase resolves
|
||
effort + model as:
|
||
|
||
```
|
||
effort_for_phase = brief.phase_signals[<phase>]?.effort ?? 'standard'
|
||
model_for_phase = brief.phase_signals[<phase>]?.model ?? profile.phase_models[<phase>]
|
||
```
|
||
|
||
The brief signal wins per-phase when present; the profile fills any
|
||
gaps. Both fields are mechanically resolved by the single composed CLI
|
||
`node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/resolver.mjs --resolve-phase-model`
|
||
invoked in Phase 1; the resolved JSON `{effort, model, source}` is captured
|
||
as `phase_signal_result` and passed to `Agent` tool calls explicitly. The
|
||
resolver controls the `model` parameter at Agent-spawn sites only — the
|
||
orchestrator's own model is fixed at invocation time (command frontmatter
|
||
omits `model:`, so it follows the session model) and cannot be switched
|
||
mid-turn. Sub-agents fall back to `model:` in their own `agents/*.md`
|
||
frontmatter when no spawn-site injection happens.
|
||
|
||
For `/trekresearch` specifically: `effort == 'low'` activates the
|
||
existing `--quick`-equivalent code-path (inline research, no agent swarm).
|
||
`effort == 'standard'` (or absent) → no change. `effort == 'high'`
|
||
activates the high-effort behavior documented under `### High-effort
|
||
behavior (v5.1.1)` below.
|
||
|
||
### Sequencing gate surface
|
||
|
||
When `/trekresearch --project <dir>` is invoked and `{dir}/brief.md`
|
||
exists, ALWAYS run `brief-validator.mjs --soft --json` against it.
|
||
If `BRIEF_V51_MISSING_SIGNALS` appears in `errors` (brief_version ≥ 2.1
|
||
without `phase_signals` or `phase_signals_partial: true`), halt with:
|
||
`Brief is brief_version 2.1 but does not carry phase_signals — re-run
|
||
/trekbrief to commit them (Phase 3.5).` Enforcement is validator-only;
|
||
commands surface, don't re-enforce.
|
||
|
||
### High-effort behavior (v5.1.1)
|
||
|
||
When `phase_signal_result.effort == 'high'` for the `research` phase,
|
||
run the FULL swarm regardless of normal triggering rules: 5 local
|
||
agents + 4 external agents + 1 bridge agent, AND force
|
||
`contrarian-researcher` AND `gemini-bridge` to always-on. Normally
|
||
`contrarian-researcher` triggers conditionally when a leading
|
||
recommendation is emerging from initial agents; in high-effort mode it
|
||
runs unconditionally so the final brief always carries an adversarial
|
||
counter-evidence pass. Similarly, `gemini-bridge` normally activates on
|
||
significant architectural questions or when triangulation value is
|
||
high; in high-effort mode it runs unconditionally to provide an
|
||
independent second opinion.
|
||
|
||
High effort additionally arms the Phase 5 bounded follow-up loop (max 3
|
||
turns per under-illuminated dimension, budgeted by
|
||
`research-loop-cap.mjs`, every outbound query gated by
|
||
`query-privacy-gate.mjs`). The loop stays default-off until
|
||
`VOYAGE_STORM_ENABLED=1`, and the moot matrix in Phase 5 (`--quick`,
|
||
`--local`, `external_research_enabled: false`) overrides the effort
|
||
signal whenever the external phase does not run at all.
|
||
|
||
Standard effort (or absent): use the existing conditional triggers.
|
||
Low effort: inline research only, no agent swarm (existing
|
||
`--quick`-equivalent code-path).
|
||
|
||
## Hard rules
|
||
|
||
- **No planning:** This command produces research briefs, not implementation plans.
|
||
If the user asks to plan, direct them to `/trekplan --research <brief>`.
|
||
- **Sources required:** Every claim must cite a source. No unsourced findings.
|
||
- **Independence:** Do not pre-bias external agents with local findings or vice versa.
|
||
Triangulate AFTER independent research.
|
||
**Amended (v5.10) for Phase 4.5:** dimension discovery deliberately crosses this
|
||
rule. Its candidate dimensions are mined from the Phase-4 result set, which
|
||
contains output from the five local codebase agents, so a discovered dimension
|
||
can carry local context into an external query.
|
||
|
||
The crossing creates **two distinct risks**, and they do not share a control:
|
||
|
||
- **Bias** — a local finding shapes what an external agent is asked. Its
|
||
controls are structural, not a gate: the initial external swarm stays blind
|
||
to local findings, so an **independent baseline already exists** before
|
||
anything crosses; the crossing is confined to Phase 4.5 and the Phase 5 loop
|
||
it feeds, which only ADD to that baseline and never revise it; and at
|
||
`effort: high` — the only effort at which any of this runs —
|
||
`contrarian-researcher` is forced always-on, so the brief always carries an
|
||
adversarial counter-evidence pass over the result the crossed queries fed.
|
||
Triangulation still happens AFTER independent research.
|
||
- **Egress** — local paths, repo identifiers or secret-shaped strings leave the
|
||
machine inside a query. That is what `query-privacy-gate.mjs` controls: every
|
||
outbound query is inspected before it leaves, with a hard-block tier for
|
||
secret-shaped strings that no operator flag can override.
|
||
|
||
The privacy gate was previously named as the compensating control for the
|
||
crossing as a whole. It is not: it inspects query CONTENT and cannot stop a
|
||
local finding from steering an external agent's question. Attributing the bias
|
||
risk to it left that risk with no control while the text read as though it had
|
||
one.
|
||
- **Graceful degradation:** If MCP tools are unavailable (Tavily, Gemini, MS Learn),
|
||
proceed with available tools and note limitations in brief metadata.
|
||
- **Cost:** Model resolution at Agent-spawn sites is a three-layer fallback:
|
||
brief `phase_signals[<phase>].model` > `profile.phase_models[<phase>]` >
|
||
agent frontmatter `model:`. The composed resolver returns the first two
|
||
layers as `phase_signal_result.model`; spawn sites inject it, and agent
|
||
frontmatter is the fallback when no injection happens.
|
||
- **Privacy:** Never log secrets, tokens, or credentials.
|
||
- **Honesty:** If the question is trivially answerable, say so. Don't inflate research.
|
||
- **Scope of codebase:** Only analyze the current working directory for local research.
|
||
- **Research transparency:** Clearly distinguish local findings from external findings.
|
||
Never blend them without attribution.
|