---
name: research
description: Research the web and wire findings as typed graph evidence
help_jtbd: "Run context-aware research that files findings as typed EvidenceClaim graph nodes."
body_shape: C
layer: "loop"
hitl_shape: "F.8"
hitl_why: "Research subquestions fan out independently and are verified as an any-order basket."
# Phase 267.3-07, ruled in 267.3-CLASSIFICATION.md (Row 10, navigator-ruled): first delivery at commands/research.md:256, the top-5 web findings framed and percent match-scored against the room's own existing claim graph.
interactive_first_reward: methodology_reframe
argument-hint: "[topic | url]"
serves_jtbd: ["explore", "understand-market"]
teaching: "When you need fresh evidence from the web cross-referenced with the Brain methodology graph, /mos:research runs the dual-source pull. Public signal plus calibrated framework. Now it also extracts your room context first, surfaces each finding with a candidate filing location, and wires accepted findings as typed graph data other commands can consume."
# --- Phase 122 workflow-layer frontmatter ---
kind: methodology
frameworks: ["Hypothesis-Driven Problem Solving"]
produces: "room/**/research/*"
inputs: []
autonomous_safe: true
# --- Phase 131 source-lens pilot frontmatter ---
# A calling methodology declares requires_evidence: to auto-dispatch /mos:research
# (the inbound called-by handle). See "Invocation modes" below.
emits_evidence_claims: true
allowed-tools:
  - Read
  - Bash
  - Agent
  - WebSearch
  - WebFetch
  - AskUserQuestion
# --- Phase 143.3 connector frontmatter ---
connector:
  connects_to_spine: true
  sensor_triggers: [SENS-04, SENS-15]
  reach_id: deep_research
  sub_mode: hat-scoped-research
  framework: "Hypothesis-Driven Problem Solving"   # MUST match the existing frameworks: value
  posture: hold
  hierarchy_rank: 6
  filing: fileEvidenceWithReadback
  plan_gated: true                 # the sanctioned deep_research exception
  web_scope: green
  surface: F.1
---

# /mos:research [topic | url]

You are Larry. This command is the canonical research workflow step. It is a THIN
orchestrator: the pipeline logic belongs to four shipped modules (Phase 131 Plans
02-04), and this command invokes them in a fixed 7-stage sequence. It adds NO new
core logic and NO fetcher of its own. The fetch + the Canon Part 8 pre-egress
audit live INSIDE the Phase 130.5 shared corpus, reached through the driver.

`/mos:research` extracts context from the room (via `navigation.cjs`), understands
WHY it was called, surfaces findings with computed candidate target sections (an
F.1 selector per Canon Part 3), and WIRES accepted findings as typed `EvidenceClaim`
nodes with `INFORMS / CONTRADICTS / SUPERSEDES / REJECTED_BECAUSE` cascade edges.

## The pipeline modules (what this command invokes)

All four are invoked via `node ${CLAUDE_PLUGIN_ROOT}/...` (the established
command-invokes-cjs idiom). Every room.db read and write routes through
`lib/core/navigation.cjs` inside these modules; this command never touches room.db
directly and never sends LOCAL data to the Brain (Canon Part 8 + Part 9).

| Stage(s) | Module | Entry point |
|----------|--------|-------------|
| 1+2+3 PRE-FLIGHT + PLAN | `lib/core/research-context-extractor.cjs` | `extractContext({ roomDir, sessionId, topic, db })` |
| 4 EXECUTION | `lib/lens-engine/source-lens-driver.cjs` | `runSourceLens({ roomDir, topic, lensSet, preflight, stage, db, sessionId })` |
| 6 F.1 FILING SELECTOR | `lib/core/research-filing-selector.cjs` | `buildFilingSelector(finding, candidateSections, opts)` |
| 7 WIRING | `lib/core/findings-wirer.cjs` | `wireAccept / wireReject / wireDefer (db, {...})` |

The 8-stage spec from 131-CONTEXT collapses to 7 here per the 4.8 re-baseline:
Stage 1 is ONE batched pre-flight read, and Stages 2+3 (context summary + lens-set
computation) merge into a single reasoning pass inside `extractContext`.

## Invocation modes (the load-bearing pilot capability)

`/mos:research` supports BOTH invocation modes:

- **Called BY another methodology** (the inbound called-by handle). A calling
  command (for example `/mos:build-thesis` declaring `requires_evidence:`)
  dispatches `/mos:research` when room evidence is thin. On this path, after
  wiring, `/mos:research` RETURNS the accepted `EvidenceClaim` node IDs so the
  caller resumes with exactly the evidence it needed. It returns ONLY node-id
  handles, never finding prose (Canon Part 8: the prose lives on the LOCAL node).
- **Called STANDALONE** (a user runs `/mos:research <topic>` directly). On this
  path, after wiring, `/mos:research` surfaces an F.1 next-move selector naming the
  methodologies that can now consume the freshly-wired claims (for example "now
  /mos:build-thesis can consume these claims").

**Auto-dispatch rule (open-decision 1, RESOLVED per 4.8):** a calling methodology
NEVER auto-fires material research. When evidence is below its declared threshold,
it ASKS via the F.1 selector with a pre-computed confident recommendation
("evidence is thin here -- run /mos:research?"). This honors the GUIDED-default
Brain rule (Canon Part 9 role 5): Larry proposes, the human decides.

## URL mode (Phase 220: same command, second argument shape)

A bare http(s) URL argument routes HERE; anything else (a topic, `--broad`, the
called-by handle) keeps the existing deep_research fan-out below byte-unchanged
(Part 7 reuse ruling - no new command). Two doors reach this mode:

- **Explicit:** the navigator runs `/mos:research <url>` directly.
- **Contextual:** the SENS-15 pasted-URL sensor (`lib/core/sensors/sensor-url-ingest.cjs`)
  detects a bare URL in conversation (outside code fences and quotes, deduped
  against `.mindrian/url-ingest-ledger.json`) and fires the FROZEN deep_research
  reach with dispatch `url-ingest-offer` - a standing offer, never an auto-open.
  Both doors end at the SAME readback gate below; nothing files without a
  navigator verb (Canon Part 3).

### 1. Readback gate (F.1, BEFORE any fetch)

Render the F.1 card through the `lib/hmi/selector-dispatcher.cjs` archetype path
(the SEED-020 single AskUserQuestion door - never a bespoke payload; user verbs
are capped and Free-Text is auto-appended last, the `shape-f1-renderer.cjs`
invariant). The card names the URL host and the target section (`research/`):

- **[Ingest]** - fetch the page and file it as a cited `research/` artifact
- **[Ingest+Explore]** - ingest, then OFFER the explore chain on the result
- **[Skip]** - do nothing (the offer dissolves; nothing is fetched or filed)

No fetch, no file, no ledger write happens before a verb (T-220-16: the gate
sits BEFORE the first network byte).

### 2. On [Ingest]: run the pipeline and render the envelope honestly

```bash
node -e '
  const { ingestUrl } = require("${CLAUDE_PLUGIN_ROOT}/lib/core/url-ingest.cjs");
  ingestUrl(process.env.MOS_ROOM_DIR, process.env.MOS_URL, {
    sessionId: process.env.MOS_SESSION_ID,
    origin: "on_demand",
  }).then((env) => process.stdout.write(JSON.stringify(env)));
'
```

`ingestUrl` returns the typed envelope `{ ok, outcome, research_mode, providers,
artifact, extraction }`. Render it per its enums - the envelope is the truth,
never soften it:

| `outcome` | What you say |
|-----------|--------------|
| `filed` | Filed: the artifact path + how many proposed entities the extraction landed |
| `no_op` | "Already ingested, unchanged" + the prior artifact path (content hash matched) |
| `superseded` | "Content changed - new version filed, SUPERSEDES the prior" + both paths |
| `provider_unavailable` | Rung 1 failed - proceed to the degrade ladder below |
| `size_exceeded` | The page exceeds the filing byte bound - refused, nothing filed |
| `blocked` | The D-10 fence refused it (manual rung from cadence) - nothing filed |
| `error` | A typed refusal (bad URL, symlink refusal, filing fault) - report the reason |

`ok: true` maps ONLY to `filed` / `no_op` / `superseded`. Every other outcome is
`ok: false` with a typed reason - a failed fetch is NEVER an empty success.

### 3. The degrade ladder (D-01 / D-10: three rungs, each honest)

- **Rung 1 - Tavily Extract** (the default above): `ingestUrl` fetches through
  the audited `fetchCorpus({source:'tavily-extract'})` chokepoint. Success
  stamps `research_mode: normal`.
- **Rung 2 - WebFetch (Claude-orchestrated):** on `provider_unavailable`, YOU
  perform WebFetch on the URL (already an allowed-tool), normalize the page to
  clean markdown yourself, and call `ingestUrl` again with `content: <markdown>`
  and `contentSource: "webfetch"` in opts. The envelope stamps
  `research_mode: web_degraded_local_fallback` and `providers.webfetch: used` -
  the degrade stays visible.
- **Rung 3 - gate-OFFERED llm_manual (NEVER default, NEVER silent):** ONLY when
  rungs 1 and 2 have BOTH failed, OFFER at a Decision Gate: "Engines are down.
  I can read and hand-normalize this page myself - slower, labeled
  `llm_manual_baseline`, excluded from calibration. Proceed?" On explicit
  approval ONLY: read the URL via the native web tools on the frozen
  deep_research reach, hand-normalize to clean markdown, and call `ingestUrl`
  with `content` + `contentSource: "llm_manual"`. The pipeline stamps the
  frontmatter `engine_mode: llm_manual_baseline` and `providers.llm_manual:
  used` - non-negotiable labeling, enforced in `lib/core/url-ingest.cjs`.
  This rung is never auto-selected, never silent, excluded from every
  calibration set, and structurally unreachable from cadence (origin
  `cadence` + `llm_manual` returns a typed `blocked` before anything fetches).

`research_mode` on this path draws from the same closed enum the topic mode
returns (all six values are documented in "Recovery modes and outcomes" below).
The url-ingest pipeline composes `normal`, `web_degraded_local_fallback`, and
`insufficient_evidence` (`local_only` is the driver's deliberately-offline
verdict and does not arise from a URL ingest; the two recovery values arise
only when the Phase 221 recovery ladder was involved).

### 4. On [Ingest+Explore]

After a SUCCESSFUL ingest (outcome `filed` or `superseded`), OFFER the existing
219 explore surface (`/mos:explore-opportunity`) on the resulting knowledge at
its OWN gate - never auto-run it (chain fetches stay material, Canon Part 3).

## Stage 1+2+3 -- PRE-FLIGHT + PLAN (research-context-extractor)

This is the explicit moment research becomes context-aware rather than blind.

Resolve the room dir + session id, then invoke the extractor:

```bash
node -e '
  const ex = require("${CLAUDE_PLUGIN_ROOT}/lib/core/research-context-extractor.cjs");
  const { openRoomDb, closeRoomDb } = require("${CLAUDE_PLUGIN_ROOT}/lib/core/room-db.cjs");
  const roomDir = process.env.MOS_ROOM_DIR;
  const db = openRoomDb(roomDir);
  const out = ex.extractContext({ roomDir, sessionId: process.env.MOS_SESSION_ID, topic: process.env.MOS_TOPIC, db });
  closeRoomDb(db);
  process.stdout.write(JSON.stringify(out));
'
```

`extractContext` returns `{ ok, context_summary, lens_set, preflight }`:

- **context_summary** -- a Body Shape A (one conversational paragraph), Larry-voiced
  context summary framed in the dominant persona role (Canon Part 2a). SURFACE this
  to the user before fetching. Example: "Speaking to your investor lens, you are in
  the build-thesis workflow, your JTBD is thesis-build, the section in focus is
  financial-model. You have 3 evidence gaps tagged needs_evidence here, so I will
  research <topic> against THIS context."
- **lens_set** -- the ordered, weighted `[{ lens, weight }]` source-lens set,
  COMPUTED from the room context (section gap / JTBD / persona role_blend), never
  hardcoded. The driver consumes this verbatim. Surface the lens names so the user
  sees which sources will be queried.

If no topic was provided, ask first: "What do you want me to research? Give me a
specific question or topic related to your venture." Then proceed.

## Stage 4 -- EXECUTION (source-lens-driver)

Pass the computed `lens_set` + the pre-flight + the pipeline stage flag to the
driver:

```bash
node -e '
  const drv = require("${CLAUDE_PLUGIN_ROOT}/lib/lens-engine/source-lens-driver.cjs");
  const { openRoomDb, closeRoomDb } = require("${CLAUDE_PLUGIN_ROOT}/lib/core/room-db.cjs");
  const roomDir = process.env.MOS_ROOM_DIR;
  const db = openRoomDb(roomDir);
  const lensSet = JSON.parse(process.env.MOS_LENS_SET);
  const preflight = JSON.parse(process.env.MOS_PREFLIGHT || "null");
  drv.runSourceLens({ roomDir, topic: process.env.MOS_TOPIC, lensSet, preflight, stage: process.env.MOS_STAGE || "explore", db, sessionId: process.env.MOS_SESSION_ID })
    .then((out) => { closeRoomDb(db); process.stdout.write(JSON.stringify(out)); });
'
```

The driver fetches EXCLUSIVELY through the Phase 130.5 shared corpus + cache,
CACHE-FIRST per lens (research-cache TTL read -> live `fetchCorpus` on a miss ->
write-back; it adds no fetcher, no second cache, no second pre-egress audit --
the Canon Part 8 pre-egress audit is the shared hook inside `fetchCorpus`,
inherited on every live fetch), dedups against prior research, ranks by
evidence-tier (Canon Part 5) + relevance, applies the stage threshold (a `commit`
stage drops None-tier findings), and returns
`{ ok, findings, lens_set, research_mode, providers }` with up to the top 5
findings. `research_mode` + `providers` are the D-19 typed envelope: a failing
source is a typed `error` provider status, and a cold corpus returns
`insufficient_evidence` -- never a silent ok + empty arrays. There is NO Python
in this path -- ranking is CJS-native tier + token-overlap relevance.

## Stage 5 -- PRESENTATION

Render the top-5 findings. For each finding, show:

- Title + a 1-line summary
- Source + URL + `retrieved_at` timestamp + `evidence_tier`
- The pre-mapped candidate room location(s) with a % match score against each
  section's existing claim graph
- Persona-aware framing per the role_blend (Canon Part 2a)

Never dump raw search results. Every finding connects to the venture context the
summary named.

## Stage 6 -- F.1 FILING SELECTOR (research-filing-selector)

Per finding, build the F.1 filing gate (Canon Part 3) by routing through the
selector:

```bash
node -e '
  const sel = require("${CLAUDE_PLUGIN_ROOT}/lib/core/research-filing-selector.cjs");
  const finding = JSON.parse(process.env.MOS_FINDING);
  const candidateSections = JSON.parse(process.env.MOS_CANDIDATES);
  const out = sel.buildFilingSelector(finding, candidateSections, { mode: process.env.MOS_MODE || "A" });
  process.stdout.write(JSON.stringify(out));
'
```

`buildFilingSelector` mirrors `lib/hmi/selector-dispatcher.cjs` (it is NOT a bespoke
selector), so Phase 136's richer multi-select widget is a strict superset. It
returns `{ envelope, options }` with the five closed-vocabulary filing verbs:

- File to `<primary section>` (recommended in Mode A when the primary clears the
  0.7 confidence gate -- a pre-filled confident recommendation, still human-gated
  per Canon Part 9 role 5)
- File to `<secondary section>`
- Split: file primary + reference secondary
- Defer to milestone audit
- Reject (capture reason -> REJECTED_BECAUSE edge per Canon Part 4)

Empty candidate sections degrade to a Defer/Reject-only selector. Present the
envelope; collect the user's decision.

## Stage 7 -- WIRING (findings-wirer)

Route the user's decision to the wirer (one of three paths). Each takes a
caller-owned db handle:

- **ACCEPT** -> `wireAccept(db, { finding, decision, roomDir, sessionId, topic })`.
  Writes an `EvidenceClaim` node (review_status `proposed` per Canon Part 9 -- a
  truth-claim node, never auto-confirmed) + an `INFORMS` edge to the resolved target
  (+ `CONTRADICTS` when the finding kills an existing claim, + `SUPERSEDES` when it
  is a better evidence tier, + a Split reference INFORMS to a secondary target) +
  a `research_filed` memory_event carrying URL / retrieved_at / evidence_tier
  provenance. Returns the new EvidenceClaim node id.
- **REJECT** -> `wireReject(db, { finding, reason, decision, roomDir, sessionId })`.
  Files the rejected finding as a proposed EvidenceClaim (the rejection-source node)
  + EXACTLY ONE `REJECTED_BECAUSE` edge carrying the captured reason scalar +
  url / retrieved_at provenance + a `research_rejected` memory_event. Rejection IS
  data (Canon Part 4): the "why not" node teaches the next dedup.
- **DEFER** -> `wireDefer(db, { finding, roomDir, sessionId })`. Emits a
  `research_deferred` memory_event queued to milestone audit; writes no edge.

Edge targets are scoped in the wirer: a LOCAL target resolves to the LOCAL room.db
node id (`section:` + section convention); a TEACHING-GRAPH target resolves to the
canonical 130.7 correlation_id via the consumer-side resolver, so a cascade edge
does not fork across cross-label duplicates.

## Stage 7 (post-filing) -- chain back or next-move

After wiring all accepted findings:

- **Called BY a methodology:** RETURN the accepted `EvidenceClaim` node IDs (the
  handles only) so the caller resumes with the evidence it needed.
- **STANDALONE:** surface the F.1 next-move selector naming the methodologies that
  can now consume the wired claims.

## `--broad` (a 3-lens preset of THIS pipeline)

`/mos:research --broad` runs the FULL Stage 1-7 pipeline above, with one change:
the computed `lens_set` from Stage 3 is OVERRIDDEN by a fixed 3-lens preset --
`scholarly`, `industry`, `patent`, all at equal weight 1.0:

```bash
# --broad: override the extractor's computed lens_set with the 3-lens preset,
# then flow through the SAME driver / selector / wirer modules.
MOS_LENS_SET='[{"lens":"scholarly","weight":1.0},{"lens":"industry","weight":1.0},{"lens":"patent","weight":1.0}]'
```

`--broad` is NOT a separate legacy code path and is NOT deleted (Canon Part 7: do
not delete user-facing capability). It is a documented `lens_set` preset that flows
through the same extractor / driver / selector / wirer modules as the default mode.
The only difference is the fixed lens_set; presentation, the F.1 gate, and wiring
are identical. Use `--broad` for comprehensive parallel-angle intelligence (the
academic + market + patent triple) on a single topic.

## Tri-Polar surfaces (CLI / Desktop / Cowork)

- **CLI:** full power. The `node ${CLAUDE_PLUGIN_ROOT}/...` invocations run the four
  modules directly; the F.1 gate renders via the dispatcher; wiring writes to the
  local room.db via `navigation.cjs`.
- **Desktop / Cowork (MCP):** `/mos:research` routes through the `intelligence`
  tool in `lib/mcp/tool-router.cjs` (the `research` command). The same four pipeline
  modules are the execution layer behind that tool. Larry narrates the same 7-stage
  flow conversationally; the F.1 gate renders via AskUserQuestion; wiring lands in
  the shared `00_Context/` room state.

## Brain boundary (Canon Part 8 + Part 9)

ZERO LOCAL data ever reaches the Brain. The only Brain touch is the read-only
`brain` lens (a generic methodology query inside the shared 130.5 corpus, generic
framework handles only, via the Phase 110 packet path). All graph writes are LOCAL
room.db via `navigation.cjs`. No Python script is called anywhere in this command.

## Web research fetch order + provider honesty (D-19)

The SHIPPED fetch order is CACHE-FIRST (the reality this doc describes; Phase
219-05 reality-to-docs fix): for each lens, the driver consults the shared
`.mindrian/research-cache` (30-day TTL, source+query keyed) FIRST; a miss falls
through to one live `fetchCorpus` call against that lens's 130.5 source
(OpenAlex / Tavily / PubMed / Brain), and a successful live fetch writes back to
the cache. There is NO paid/native provider re-ordering layer in this path.

Every run returns the D-19 provider-status envelope alongside the findings:

- `research_mode` -- the typed run verdict: `normal` |
  `web_degraded_local_fallback` (a live leg failed; cached/local data covered) |
  `local_only` (deliberately offline) | `insufficient_evidence` (a COLD corpus:
  zero items anywhere -- typed, never a silent ok + empty arrays).
- `providers[]` -- per-lens `{ provider, lens, status, reason, counts,
  freshness }` where status is the closed enum `ok | empty | error | skipped`.

A per-source failure degrades that lens to zero items with a TYPED `error`
status (the outage stays visible). When Brain is unreachable, the research
still runs; only the `brain` lens degrades. /mos:research never silently no-ops
because of unconfigured MCPs -- and it never hides an outage behind an empty
success. The public research-cache stores ONLY web-sourced signal data (ids,
titles, public abstracts, DOIs); room body text NEVER lands in it (guard test:
tests/test-219-research-contract.cjs).

## Recovery modes and outcomes (Phase 221: no silent recovery, no invented certainty)

When an engine breaks mid-run, the run does not dead-end and it does not lie.
A typed recovery ladder routes the failure, and the result tells you exactly
what happened. Here is the whole vocabulary, in plain language.

### The six `research_mode` values (what kind of run this was)

| `research_mode` | Plain-English meaning |
|-----------------|----------------------|
| `normal` | Every engine worked. Nothing to disclose. |
| `web_degraded_local_fallback` | A live web leg failed; cached or local data covered the gap. The outage stays visible in `providers`. |
| `local_only` | The run was deliberately offline (your choice, not a failure). |
| `insufficient_evidence` | Zero items anywhere. A cold corpus is a typed verdict, never a silent ok with empty arrays. |
| `llm_engine_recovery` | An engine broke and a bounded, fenced LLM recovery did the intelligence work; everything it did is disclosed (profile, paths, model, claims). |
| `manual_intervention_required` | The run needs YOU, and the message names the smallest missing thing. Two distinct causes worth telling apart: (a) a missing credential or permission, something you can fix in a minute; (b) `spend_limit_exceeded`, meaning your own Claude account hit its monthly spend cap. Raise it at claude.ai/settings/usage, or wait for the reset. This is never a sign anything is wrong with your data or your room (D-11). |

### The five overall outcomes (how the run ended)

| `outcome` | When it appears |
|-----------|-----------------|
| `recovered` | Recovery worked, fully. This word is earned, never guessed: every required stage contract passed AND any filed evidence was readback-confirmed on disk. A filing that did not confirm can never compose `recovered`. |
| `partial_recovery` | Recovery covered part of the scope; the rest is named in `unresolved_gaps`. A budget that ran out ends here too, never as a complete-looking short report. |
| `insufficient_evidence` | Nothing covered the scope. The honest zero. |
| `manual_intervention_required` | The ladder reached the human tier: the disclosure names the exact missing credential, engine, or the spend cap. |
| `policy_blocked` | The Part 8 privacy fence refused an egress. Terminal: never retried, never rerouted, never rephrased. |

### The disclosure block (what you see on every recovery-touched run)

Every run the recovery ladder touched returns a `disclosure` alongside the
findings, so you never wonder what happened behind the curtain:

- `failed_engines` - which stage and engine broke, with the typed failure class
- `recovery_profile` - `diagnostic` | `high_effort` | `forensic` (or null if the LLM tier never ran)
- `recovery_paths` - the exact paths recovery took (retries, substitutes, LLM tier)
- `coverage` - claim coverage: `requested` / `supported` / `conflicting` / `unsupported`
- `freshness` - `live_verified` is true ONLY on a genuine live fetch, never cache or substitute; plus the newest source timestamp and a warning when nothing was live
- `filing` - `attempted` / `confirmed_by_readback` / `reason`: filed means readback-confirmed, nothing less
- `unresolved_gaps` - what remains unknown, each gap corpus-scoped and provisional (absence from one corpus is never project-level nonexistence)
- `model` - which model and version did the recovery work (recorded, never governing)

### The 6-tier recovery ladder (how a failure routes)

| Tier | What it does | Exit |
|------|--------------|------|
| 0 | Normal engine | stage contracts pass |
| 1 | Deterministic retry, idempotent fetches only, bounded backoff | contract passes or retry budget exhausted |
| 2 | Local governed substitute (room corpus, research cache), explicit provenance, never mislabeled as live | coverage restored or gaps explicit |
| 3 | High-effort LLM recovery, gate-OFFERED and fenced | validated recovery bundle or trustworthy-recovery-impossible |
| 4 | Human intervention, the smallest missing thing named | you resolve or defer |
| 5 | Honest termination | the run ends without invented success |

Three honesty rules the ladder never bends:

1. **A legitimate empty is a finding.** `empty_valid` never triggers recovery
   and is never "recovered" into content that was not there.
2. **Cadence runs never invoke the LLM tier unattended.** A scheduled or
   background run terminates honestly instead of spending your budget while
   you are away (Canon Part 3).
3. **An exhausted spend cap is never retried and never handed to the LLM
   tier.** Retrying wastes more of what is already gone, and asking the LLM
   to recover from the LLM being out of budget cannot work (D-11). It routes
   straight to you with the one actionable step.

## Voice

Larry frames the research in venture context:
> "Here's what I found -- and more importantly, here's what it means for what you're
> building, and where it should live in your room..."

Every finding connects to the venture and lands as typed graph data the rest of the
room can navigate.
