# ROUTER-1: Sonnet executor, Opus advisor, evidence-earned Haiku, evidence-gated escalation

Status: plan (Architect, 2026-10-07; default-on adjustment from steering the same day). CTO decision 2026-10-07 21:40Z, refined by the founder at 21:45Z (D89 amendment), governs R1-19 only: when the default-on flip lands after the CTO gate, the default executor is Sonnet 5.5; Haiku 5.5 per work unit when Opus assigns it. Opus 5.5 is the architect, router and advisor: it plans, assigns each work unit and is consulted on hard calls. Sonnet is the senior engineer and the default for implementation and fixes. Haiku does tests, docs and small low-risk changes only when Opus assigns them in the plan. Haiku is never the blanket default. The per-unit schema (section 4.1) lands in 11.2.0 or 11.2.1 depending on what is merged; the earlier whole-run schema below is superseded. 11.1.0 and 11.2.0 still ship the router OFF. Earlier text below that calls Haiku the default executor describes the superseded plan. While slices are built they merge behind `LOKI_ROUTER` (unset = off). R1-19, the final slice, makes it default ON, gated on steering's FireLater#17 gate plus the B9 scoreboard on the release candidate. After the flip `LOKI_ROUTER=0` is the opt-out and restores the exact pre-router model behavior, pins included (R1-21 test).
Source: founder directive 2026-10-07 ("Opus is the advisor, Haiku does all engineering work; Sonnet is optional, routed by Opus when Haiku is not performing well enough; model decides").
Scope: the engine10 path (`loki start <issue|text>`, `loki-ts/src/engine10`). The legacy RARV route (`autonomy/run.sh`, `runner/autonomous.ts`, `runner/capability_router.ts`) keeps its tiers; only its pricing table mirror changes (R1-02).

## 1. Goal
After the R1-19 flip, every engine10 session runs on Claude Sonnet 5.5 as the default executor with Opus 5.5 attached through the native Claude Code advisor tool, so the model itself decides when it needs stronger judgment. Opus picks the executor at plan time with a recorded reason. Opus may route a task to Haiku 5.5 only when the R1-16 history and a B9 row show Haiku at outcome parity on that repo shape; a shape with no evidence stays on Sonnet. Escalation stays evidence-gated (haiku -> sonnet -> opus, and sonnet -> opus). The outcome (solve rate, time, cost) is never below raw `claude -p`, proven on the B9 scoreboard.

## 2. What already exists (read before building; cite, do not rebuild)
- Session model plumbing: `engine10/session.ts:25` resolveModel, `:31-60` childEnv (tier pins via `LOKI_CLAUDE_MODEL_<TIER>`, `LOKI_E10_MODEL_DEFAULT` omits `--model`).
- SDK call site: `runner/providers.ts:507` buildSdkLoopOptions, `:683-735` `query()` options (model, resume, tools, settingSources `["project"]` for engine10 at `:544`, effort, fallbackModel). CLI route argv at `:380-392` (passes `--settings hostGuardSettingsJson()`).
- Escalation already exists: `engine10/stages/fix.ts:64-71` escalates a repeated test-failure signature to `escalationModel()` = opus (`runner/model_rank.ts:8`). Stall stop: `engine10/machine.ts:186` (same signature 3 verifies). `MAX_FIX_ROUNDS = 2` (`engine10/types.ts:25`).
- Owner attribution: verify marks harness-owned checks `owner: "harness"` (`engine10/stages/verify.ts:39,144-145,303`); only these never feed fix rounds.
- Downgrade list: `runner/model_downgrades.ts:6-20` (cascade, plan fast tier, Wall pin, already-done pin), printed on the start line `engine10/supervisor.ts:305-306`, recorded on run.started `:340`.
- Sonnet pins to remove: `engine10/sizing.ts:62` wallModel (default sonnet), `:64` cascadeImplementModel; used by `stages/wall.ts:174-175`, `already_done.ts:35-36`; plan runs `tier: "fast"` (`stages/plan.ts:68`) which resolves to sonnet unless `LOKI_ALLOW_HAIKU=true` (`runner/model_downgrades.ts:13`, `providers/claude_flags.ts:153-166`).
- Static L1 guard: `loki-ts/tests/engine10/never_below_raw.test.ts:100-125` allowlists every session model pin; any new pin fails CI until vetted.
- Resume across a model change is refused by design: `runner/session_resume.ts:41`.
- Plan scope file: `<runDir>/plan-scope.json` (`util/run_cap.ts:10,25-28`), written by the plan session, read for FC-21b cap resize.
- Cost: engine10 dollars come from the SDK result `total_cost_usd` (`engine10/cost.ts:74`); `model-pricing.json` prices only killed-session partial streams (`runner/budget.ts:483-500`) and estimates (`tools/estimate-run.py:84`). Per-message usage is already parsed (`runner/sdk_stream_parser.ts:161-213`).
- Not present: no advisor wiring anywhere (grep of loki-ts/src for "advisor" finds only unrelated text); no Bedrock/Vertex/Foundry detection; no B9 harness script (B9 is a manual row in `docs/v10/RELEASE-11.md:46`; the nearest harness is `scripts/first-run-gate.sh:259-270`).
- Name collision: `LOKI_ADVISOR_MODEL` already means the legacy council reviewer pin (`runner/quality_gates.ts:1532,1612`). ROUTER-1 must not reuse it; new knobs are `LOKI_ROUTER*`.

## 3. Engine Laws touched
- L0 (model decides, harness proves): the executor choice is made by Opus (advisor in the plan session, or the plan session itself on Opus when no advisor), returned as schema-checked JSON. The harness adds no regex or `if` about repo shape, language or task wording. Harness-owned logic is limited to mechanism: provider capability detection (env and version), execution-evidence triggers, and recording. `tests/engine10/l0_guard.test.ts` must stay green. The shape key (section 4.6) is a lookup into executed outcome evidence and may only move the executor up (ENGINE-LAWS H4); this is the documented exception to the L0 review rule.
- L1 amendment (R1-04, founder decision D89): "never below raw" means OUTCOME parity (solve rate, time, cost on the B9 corpus and the Parity Gate), not model identity. A Haiku executor runs only in a configuration with Parity Gate evidence: with the Opus advisor attached, or without it only if arm 4 (section 7) meets the Parity Gate; otherwise the executor is Sonnet 5.5. The executor, advisor state and any fallback reason are printed on the start line and in the receipt (no silent reduction). Escalation still goes UP: Haiku -> Sonnet 5.5 (rest of run) -> Opus for a repeated failure on Sonnet (the existing L1 path, `model_rank.ts:8`), always before STALLED. Non-Claude providers and `LOKI_ROUTER=0` keep the provider's best default and escalate to the strongest model the provider offers.
- L3 (evidence outranks heuristics): escalation fires only on rung 1/2 evidence (a code-owned FAIL repeating after a fix round, a SPEC_CONFLICT exit, a stall or limit kill) or rung 3 model judgment (the advisor's explicit recommendation). Per-shape history (H4) is executed outcome data, not a heuristic.
- L5 (every failure has an owner): only `owner` code FAIL (verify.ts) drives escalation. Harness, env and provider ERRORs never escalate and never count; an advisor that is unavailable is NOT PROVEN with owner provider, never a run failure.
- H4 (self-improving): per-repo, per-shape outcome history (executor, verdict, escalated, usd, wall time) is written at seal and fed to the Opus routing prompt and, as an evidence floor, makes Sonnet the default on a shape where Haiku lost 2 of the last 3 runs (code-owned FAIL or escalation; harness, env and provider ERRORs and NOT PROVEN outcomes do not count, L5). The floor is cleared only by a B9 row showing Haiku at parity on that shape, since Haiku never runs there again while the floor holds. NOTE: "H4" is not defined anywhere in docs/ today; R1-04 defines it in ENGINE-LAWS.md.
- L7 (outputs are contracts): the route block in the receipt, the start line and the PR line get golden tests (R1-15).

## 4. Architecture
### 4.1 Routing decision schema (written into plan-scope.json by the plan session; parsed by `runner/router/decision.ts`)
```
route: { default: "sonnet",
         units: [ { id: string, kind: string, executor: "haiku" | "sonnet", reason: string (<= 200 chars) } ],
         source: "opus-plan" | "advisor" | "default" }
```
Per-unit schema (founder refinement 2026-10-07 21:45Z; lands in 11.2.0 or 11.2.1 depending on what is merged). Opus writes the units into plan-scope.json at plan time, with a run default of sonnet. The harness never routes by task type or path regex (L0): it only executes, verifies and escalates. R1-16 history is an input to the Opus planning prompt, not a harness rule. The receipt lists the executor, the assigner and the reason per unit.
A missing or invalid unit route goes to sonnet (`{executor: "sonnet", source: "default", reason: "no route returned"}`; the superseded plan returned haiku here) plus a NOT PROVEN line (L2: fail open on work). Aliases resolve through `providers/model_catalog.json` (haiku -> claude-haiku-5-5, sonnet -> claude-sonnet-5-5, opus -> claude-opus-5-5).

### 4.2 Where the decision is made
1. Run start: advisor availability is probed (R1-06). Start line: `executor sonnet-5.5, advisor opus` (or `executor haiku-5.5, advisor opus` for a shape with earned Haiku evidence; or `executor sonnet-5.5, advisor unavailable: <reason>`).
2. Plan stage runs (normal tasks): the plan session runs on the executor with the Opus advisor attached; its brief asks it to consult the advisor and write `route` into plan-scope.json. With no advisor, the plan session itself is pinned to Opus (the single cheap Opus call) and writes the same field; a `haiku` route is then raised to Sonnet (section 4.4).
3. Plan skipped (small task, `plan.ts:55`): no extra call. Executor = history floor or shape default if one applies, else Sonnet with the advisor (post-flip default; Haiku only for a shape listed `haiku` in section 4.6), `source: default`.
4. Recorded: a `route` event (implement), run.started `route_initial`, receipt `route` block (initial decision, every escalation with trigger and evidence pointer, advisor availability, share of requests above 100K).

### 4.3 Escalation state machine (pure function in `runner/router/decision.ts`)
```
EXEC(haiku) --a: code-owned FAIL repeats after a fix round------------> EXEC(sonnet, rest of run)
EXEC(haiku) --b: LOKI_ESCALATE: <reason> marker (advisor recommended)--> EXEC(sonnet, rest of run)
EXEC(haiku) --c: SPEC_CONFLICT exit | stall | implement limit kill------> EXEC(sonnet) + one retry of that stage
EXEC(sonnet) --a or stall------------------------------------------------> fix round on opus (existing L1 path) -> STALLED
any state --harness/env/provider-owned ERROR-----------------------------> no transition (NOT PROVEN path)
```
Per-unit rule (founder refinement 2026-10-07 21:45Z): a haiku unit that fails a code-owned check is redone on sonnet, then escalates to opus; a Wall assigned to haiku gets an Opus review before implement. The state machine above is the whole-run form and is read per unit. At most one Haiku -> Sonnet transition per run (whole-run form). A SPEC_CONFLICT confirmed by Sonnet stays BLOCKED. Resume across the swap: the fix session resumes the prior session id with the new model when the SDK honors `model` on `resume` (verified in R1-12); otherwise it starts fresh with the full brief plus the prior diagnosis and records `fix_resume_why`.

### 4.4 Advisor wiring and fallback detection
- Wiring: SDK route passes `settings: { advisorModel }` (in SDK 0.3.288 `advisorModel` is a field of `interface Settings`, `sdk.d.ts:6674,8987`, not a top-level Option; R1-07 confirms the 0.3.293 shape). CLI route merges `advisorModel` into the existing `--settings` JSON or uses `--advisor` when `claudeFlagSupported` says so.
- Unavailable when any of: provider is not claude; `CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX` or `CLAUDE_CODE_USE_FOUNDRY` is set; `ANTHROPIC_BASE_URL` points at a non-Anthropic host; the bundled or installed Claude Code is below 2.1.293; `LOKI_ROUTER_ADVISOR=off`; or a session reports an advisor tool error (sticky for the rest of the run, cached in runDir). Unavailable never fails a run: plan runs on Opus, the cascade still works, the receipt says `advisor: NOT PROVEN (owner provider): <reason>`. With the advisor unavailable, the executor is Sonnet 5.5 unless arm 4 of the B9 parity run shows Haiku without the advisor at parity; 11.1.0 ships Sonnet here (resolved in `decision.ts`, applied by R1-07 and R1-09), and only a later slice citing arm 4 METRICS.md rows may lower it to Haiku.
- Unknown to verify in R1-07: whether the engine10 `tools` whitelist (`providers.ts:512`, 6 tools) hides the server-side advisor tool.

### 4.5 Context cost guard (keep requests under 100K, where the Haiku price is 5x lower)
Lean briefs and the cache-stable prefix already exist (`LOKI_E10_PREFIX=lean`, `providers.ts:524`). R1-07 adds `autoCompactWindow: 100000` (the SDK minimum, `sdk.d.ts` Settings) for routed sessions. R1-08 records per request `input+cache_read+cache_creation` and writes `requests_total`, `requests_over_100k` and advisor tokens into each cost event; the receipt shows the share.

### 4.6 Per-shape defaults (a shape that fails parity routes to Sonnet instead of holding the release)
- Where it lives: `loki-ts/data/router-shape-defaults.json`, shipped in the package next to model-pricing.json. Schema `{"$schema_version":1, "_source":"<B9 run ids + RC version + date>", "shapes":{"<shape_key>":{"executor":"haiku"|"prior-default","evidence":"<METRICS.md row ref>"}}}`. Semantics inverted by the 2026-10-07 21:40Z CTO decision and refined by the founder at 21:45Z (D89 amendment): the default executor is Sonnet 5.5 and Haiku 5.5 is used per work unit when Opus assigns it. A shape is listed as `haiku` once it has earned it with a B9 row showing Haiku at outcome parity on that shape, and an absent listing means Sonnet 5.5. The shipped file and the R1-16 history are inputs to the Opus planning prompt, not harness routing rules; the harness never routes by task type or path regex (L0). `prior-default` (the `LOKI_ROUTER=0` model for that stage) stays legal for a shape where Sonnet also loses. The shipped file is empty (R1-16), so every shape starts on Sonnet.
- Shape key: Project Model `workspaceKind` plus the sorted, lowercased, de-duplicated package `runner` labels from `.loki/project.json` (both model-derived, L4; the schema has no language field, `project_model/schema.ts:20-30`, decided 2026-10-07 by the CoS after R1-16 found the gap), for example `multi-root:pytest+vitest`. A package with no runner contributes `none`. The same function (`shapeKey()` in history.ts) computes it at runtime and in the receipt (R1-15 writes `route.shape_key`), so B9 rows and runtime agree by construction. No file-pattern regex (L0).
- Seeding: `scripts/b9-scoreboard.sh --emit-shape-defaults <out.json>` (R1-17) reads each run's receipt `route.shape_key`, compares arm 2 (router) with arm 1 (raw) per shape on correctness, and lists every shape where router solve count < raw. A shape where Sonnet also loses on the confirming rerun is emitted as `prior-default`. R1-19 commits that output as the shipped file, citing the METRICS.md rows. Later B9 runs reseed it in a normal slice; removing a shape, or lowering it from `prior-default` to `sonnet`, needs a B9 row showing the lower executor at parity on it.
- Read path: `decision.ts` resolves the executor as the strongest of: Opus route, shipped shape default, per-repo history floor (R1-16, local runs; under the inverted semantics Haiku is reachable only when the shape is listed `haiku` and the history does not show Haiku losing 2 of the last 3 by code-owned FAIL or escalation -> Sonnet, cleared only by a B9 row showing Haiku at parity on that shape), the no-advisor floor (section 4.4), then Sonnet unless the shape is listed `haiku`. `prior-default` resolves to the exact `LOKI_ROUTER=0` model for that stage (R1-21 fixture semantics). Explicit `LOKI_MODEL_OVERRIDE`, `LOKI_CLAUDE_MODEL_DEVELOPMENT` or `LOKI_ROUTER_EXECUTOR` still win. Evidence rungs never lower a model: a shape default can only move the executor up. The shape key is a lookup into executed outcome evidence and may only move the executor up (ENGINE-LAWS H4); this is the documented exception to the L0 review rule. The receipt records `source: shape-default` with the evidence ref. Missing or corrupt file = empty map plus a NOT PROVEN line, never a crash.

## 5. Knobs
`LOKI_ROUTER` (unset = off while building; unset = on after R1-19; `0` = opt-out to pre-router behavior), `LOKI_ROUTER_EXECUTOR` (post-flip default sonnet; haiku only by earned evidence), `LOKI_ROUTER_ESCALATE` (default sonnet), `LOKI_ROUTER_ADVISOR` (default opus; `off` disables). `LOKI_MODEL_OVERRIDE` and explicit `LOKI_CLAUDE_MODEL_DEVELOPMENT` still win and bypass the router (recorded `source: override`).

## 6. Slices
Tier sets the review (D12): LOW/MEDIUM by Tech Leads, HIGH by opus reviewers, unanimous. Every TS slice rebuilds dist in the main checkout before its train (memory: symlinked node_modules), runs `bash scripts/local-ci.sh`, and keeps `LOKI_ROUTER` unset behavior byte-identical (existing suites green). File sets do not overlap; `runner/router/flag.ts` (created by R1-05) and `loki-ts/data/router-shape-defaults.json` (created empty by R1-16) are edited only by R1-19, after every other slice has merged, so no two slices touch them concurrently.

| id | goal | file set | tier | builder | red-then-green test | Wall check | deps |
|---|---|---|---|---|---|---|---|
| R1-01 | Agent SDK 0.3.288 -> 0.3.293 (bundled Claude Code >= 2.1.293, needed by the advisor) | package.json, loki-ts/package.json, bun.lock, loki-ts/bun.lock, loki-ts/tests/runner/sdk_version_floor.test.ts | MEDIUM | sonnet | new test asserts installed SDK >= 0.3.293 and its package.json claudeCodeVersion >= 2.1.293 (red today: 0.3.288 / 2.1.288; root node_modules is even 0.3.283) | full bun test + tsc; `bun install` in the main checkout; one real `loki start` smoke on a fixture | none |
| R1-02 | Haiku 5.5 price, exact-id key, >100K tier | loki-ts/data/model-pricing.json, loki-ts/src/runner/budget.ts, autonomy/run.sh (`_write_pricing_json` only, `:7570-7590`), tools/estimate-run.py, loki-ts/tests/runner/budget_haiku55.test.ts | MEDIUM | sonnet | STEP 1: read the Anthropic pricing page (platform.claude.com/docs/en/about-claude/pricing) and record URL + date in `_source`; if it does not show Haiku 5.5, STOP and report (never write an unverified price). Test: 1M input on `claude-haiku-5-5` prices $0.10 and `claude-haiku-4-5` stays $1.00 (red today: family substring match, `budget.ts:495`); above-100K fields priced | budget suites + estimate-run smoke | none |
| R1-03 | Catalog: claude-haiku-5-5 and claude-sonnet-5-5, aliases | providers/model_catalog.json, providers/models.sh, loki-ts/tests/providers/model_catalog_router.test.ts | MEDIUM | sonnet | STEP 1: real calls `claude -p --model haiku` and `--model sonnet`, record canonicalModel. Test: aliases resolve to the verified ids via `resolveModelAlias` (`sizing.ts:58`) (red: haiku -> claude-haiku-4-5, no sonnet-5-5 entry) | tests/test-provider-loader.sh, model catalog tests | none |
| R1-04 | L1 amendment (outcome parity), define H4, record D89 | docs/v10/ENGINE-LAWS.md, docs/v10/DECISIONS.md | HIGH | sonnet | doc lint: no em/en dashes; L1 text names outcome parity, the escalation ladder and the advisor requirement | CTO approval; founder directive quoted in D89; D89 records default-on only after the R1-19 gate | none |
| R1-05 | Router core: schema, parser, executor resolution, escalation state machine, flag | loki-ts/src/runner/router/decision.ts, loki-ts/src/runner/router/flag.ts, loki-ts/tests/runner/router_decision.test.ts | MEDIUM | sonnet | table test of every transition in 4.3, including harness-owned ERROR = no transition and a single Haiku -> Sonnet swap; invalid JSON -> default + NOT PROVEN; advisor unavailable raises a `haiku` route to sonnet; `prior-default` resolves to the LOKI_ROUTER=0 model | tsc; l0_guard green (no repo-shape regex) | R1-16 |
| R1-06 | Advisor availability probe | loki-ts/src/runner/router/advisor_probe.ts, loki-ts/tests/runner/advisor_probe.test.ts | MEDIUM | sonnet | each unavailable condition in 4.4 yields `{available:false, reason}`; sticky runtime marker read from runDir | fixtures for Bedrock, Vertex, Foundry, gateway URL, old CLI | none |
| R1-07 | Wire advisorModel and autoCompactWindow into SDK and CLI sessions | loki-ts/src/runner/providers.ts, loki-ts/tests/runner/providers_advisor.test.ts | HIGH | sonnet | buildSdkLoopOptions under LOKI_ROUTER=1 carries `settings.advisorModel` and `autoCompactWindow`, absent when unavailable or flag off; with the advisor unavailable the routed session model is sonnet, never haiku; CLI argv carries it | STEP: one real `claude -p --model haiku` run with the advisor that consults it; prove advisor usage appears in the stream and the 6-tool whitelist does not hide it; record output | R1-01, R1-06 |
| R1-08 | Telemetry: per-request size, advisor calls and tokens | loki-ts/src/runner/sdk_stream_parser.ts, loki-ts/src/engine10/cost.ts, loki-ts/tests/runner/sdk_stream_router_usage.test.ts | MEDIUM | sonnet | recorded stream fixture with a 120K request and an advisor call yields requests_over_100k=1 and advisor tokens (red: fields absent) | cost suites; partial-stream path unchanged | R1-01 |
| R1-09 | Session plumbing: LOKI_ESCALATE marker, cost event fields | loki-ts/src/engine10/session.ts, loki-ts/src/engine10/types.ts, loki-ts/tests/engine10/session_router.test.ts | MEDIUM | sonnet | `LOKI_ESCALATE: <reason>` parses into markers.escalate; cost event carries R1-08 fields; with the advisor unavailable the child env pins the executor tier to sonnet (no-advisor executor is Sonnet, section 4.4); supervisor event validator still accepts | session + supervisor suites | R1-08 |
| R1-10 | Plan-time route by Opus | loki-ts/src/engine10/stages/plan.ts, loki-ts/tests/engine10/plan_route.test.ts | HIGH | sonnet | flag on: brief asks for `route` JSON after consulting the advisor; advisor unavailable: plan session pinned to opus and a `haiku` route is raised to sonnet; flag off: brief byte-identical | real plan run on FireLater#17 writes a valid route | R1-05, R1-06 |
| R1-11 | Implement on the routed executor; triggers b and c | loki-ts/src/engine10/stages/implement.ts, loki-ts/tests/engine10/implement_router.test.ts | HIGH | sonnet | route event emitted with source; SPEC_CONFLICT on haiku retries once on sonnet and stays BLOCKED only when sonnet agrees; LOKI_ESCALATE switches the rest of the run | fake-session e2e for each trigger | R1-05, R1-09, R1-10 |
| R1-12 | Fix rounds: trigger a, ladder haiku -> sonnet -> opus, resume across swap | loki-ts/src/engine10/stages/fix.ts, loki-ts/src/runner/model_rank.ts, loki-ts/src/runner/session_resume.ts, loki-ts/tests/engine10/fix_router.test.ts | HIGH | sonnet | repeated code-owned FAIL on haiku -> sonnet; on sonnet -> opus; harness-owned never escalates; resume keeps the session when the SDK honors the new model (STEP: verify with a real resume) else records why | never_below_raw "escalated model at or above run model" still green | R1-05, R1-11 |
| R1-13 | Stall and limit-kill escalation before STALLED | loki-ts/src/engine10/machine.ts, loki-ts/tests/engine10/machine_router.test.ts | HIGH | sonnet | a 3x identical code-owned signature on haiku gets one sonnet round instead of STALLED; harness-owned stall unchanged | machine + fatal e2e suites | R1-12 |
| R1-14 | Replace sonnet pins and the downgrade list with the router | loki-ts/src/runner/model_downgrades.ts, loki-ts/src/engine10/sizing.ts, loki-ts/src/engine10/stages/wall.ts, loki-ts/src/engine10/already_done.ts, loki-ts/tests/engine10/never_below_raw.test.ts | HIGH | sonnet | flag on: Wall, plan, already-done use the routed executor + advisor and the downgrade list is replaced by the route record; guard allowlist adds `routedModel()` with a mutation test (an unconditional haiku pin without the router still fails) | l0_guard and never_below_raw green | R1-04, R1-05 |
| R1-15 | Start line, receipt route block, PR line | loki-ts/src/engine10/supervisor.ts, loki-ts/src/engine10/stages/seal.ts, loki-ts/src/engine10/pr_body.ts, loki-ts/tests/engine10/route_receipt_golden.test.ts | MEDIUM | sonnet | golden: `executor haiku-5.5, advisor opus: <reason>`; receipt lists `route.shape_key`, escalations with trigger and evidence, advisor NOT PROVEN with owner, >100K share; `route.shape_parity: NOT PROVEN` for a shape with no B9 row (D89 item 10); flag off byte-identical (receipt hash stable) | pr_body_golden, portable_receipt, seal suites | R1-05, R1-06, R1-11 |
| R1-16 | Per-shape outcome history (H4) | loki-ts/src/runner/router/history.ts, loki-ts/src/e10ext/repomemory.ts, loki-ts/tests/runner/router_history.test.ts | MEDIUM | haiku | append/read in the per-repo cache dir; shape key from Project Model workspaceKind + sorted runner labels (model-derived, L4), never from file regex; floor: haiku lost 2 of last 3 on the shape (code-owned FAIL or escalation; harness, env and provider ERRORs do not count) -> sonnet, cleared only by a B9 row showing Haiku at parity on that shape; fixture: 3 harness-owned FAILs on haiku do NOT trigger the floor. Also creates the empty shipped `loki-ts/data/router-shape-defaults.json` and its reader `shapeDefault(key)` (section 4.6), which accepts `sonnet` and `prior-default` and rejects any other value (cold read + NOT PROVEN) | corrupt file is a cold read, never a crash | none |
| R1-17 | B9 scoreboard harness | scripts/b9-scoreboard.sh, tests/test-b9-scoreboard.sh, tests/run-all-tests.sh (registration + shard row) | MEDIUM | sonnet | dry-run with a stub `claude` produces one METRICS row per arm with solve, wall seconds, usd, shape_key; `--emit-shape-defaults` on a fixture where the router loses one shape lists exactly that shape as `sonnet`; a fixture where the Sonnet confirming rerun also loses emits it as `prior-default` | shellcheck, `timeout -k`, runner registration guard (memory: new test file structural guards) | none |
| R1-18 | Docs: GUIDE model-routing section, CHANGELOG | docs/v10/GUIDE.md, CHANGELOG.md | LOW | haiku | doc lint; GUIDE names every knob in section 5 and the fallback | Read against the receipt | R1-15 |
| R1-19 | Final flip after the CTO gate: LOKI_ROUTER default ON with Sonnet 5.5 + Opus 5.5 advisor as the default executor (11.1.0 and 11.2.0 still ship OFF); Haiku 5.5 per work unit only when Opus assigns it in the plan, per-unit schema `units: [{id, kind, executor, reason}]` (11.2.0 or 11.2.1); ship seeded shape defaults (`haiku` only for earned shapes) | loki-ts/src/runner/router/flag.ts, loki-ts/data/router-shape-defaults.json, loki-ts/tests/runner/router_flag_default.test.ts | HIGH | sonnet | red: unset env resolves off; green: unset resolves on, `0`/`off`/`false` resolve off; R1-21 opt-out golden still green with `LOKI_ROUTER=0` | GATE (all on the release candidate SHA): steering's FireLater#17 gate green (VERIFIED, ready PR, time within raw + 30s, cost <= raw x 1.2); B9 correctness parity with raw `claude -p` on the Sonnet default and on every shape listed `haiku` (a shape with no evidence stays on Sonnet); arm 4 correctness parity, or the Sonnet no-advisor fallback in place (R1-07, R1-09); every shape listed as `prior-default` runs the exact LOKI_ROUTER=0 models (R1-21 golden per listed shape); the seeded file committed with its METRICS.md refs | all |
| R1-21 | Opt-out golden: `LOKI_ROUTER=0` equals pre-router behavior exactly | loki-ts/tests/engine10/router_optout_golden.test.ts, loki-ts/tests/engine10/fixtures/pre_router_models.json | HIGH | sonnet | fixture captured on 11.0.3 code BEFORE any router slice merges: for an env matrix (default; LOKI_E10_CASCADE=1; LOKI_E10_WALL_TIER=haiku; LOKI_ALLOW_HAIKU=true; LOKI_MODEL_OVERRIDE=sonnet; LOKI_CLAUDE_MODEL_DEVELOPMENT=opus; LOKI_E10_PLAN=always) it records, per stage (plan, wall, already_done, implement, fix round 1 and escalated round 2), the model passed to sessions.run, the child-env LOKI_CLAUDE_MODEL_* values, the SDK query options model/fallbackModel/settings, the downgrade list, the start line and the run.started keys. The test replays the matrix through fake sessions with `LOKI_ROUTER=0` (and unset, until R1-19) and requires deep equality. Mutation: changing wallModel's default or adding `settings.advisorModel` under `0` turns it red | runs in the fast tier; must be green on every slice's train | none (lands first in W1) |
| R1-20 | `loki doctor` shows advisor availability and bundled CC version | loki-ts/src/commands/doctor.ts, loki-ts/tests/commands/doctor_router.test.ts | LOW | haiku | doctor prints `advisor: available` or the reason from R1-06 | doctor suites | R1-06 |

Parallel waves: W1 = R1-21 (merge first, it captures the baseline), R1-01, R1-02, R1-03, R1-04, R1-06, R1-16, R1-17 (8 independent). W2 = R1-05, R1-07, R1-08, R1-20. W3 = R1-09, R1-10, R1-14. W4 = R1-11, then R1-12, R1-15. W5 = R1-13, R1-18. W6 = release candidate built with the flag ON in env, FireLater#17 gate + B9 runs, `--emit-shape-defaults`, then R1-19.
Critical path: R1-01 -> R1-08 -> R1-09 -> R1-11 -> R1-12 -> R1-13 -> RC + FireLater#17 gate + B9 -> R1-19 (R1-07 and R1-10 join at R1-11; R1-02 price confirmation gates R1-19).

## 7. Whole-feature Wall and B9 parity measurement
Corpus: B9 rows in RELEASE-11.md (FireLater#17, augmentiq#52, the small fixture) plus one Repo Shape Matrix shape per ENGINE-LAWS section 4 that is already runnable (single npm, python uv). Same base SHA per arm, hidden tests judge correctness.
Arms (n=3 per repo per arm; a 0/3 needs a positive control first):
1. raw `claude -p` on the account default (the L1 baseline).
2. Loki 11.1.0, `LOKI_ROUTER=1` (Haiku 5.5 + Opus advisor + cascade).
3. Loki 11.1.0, `LOKI_ROUTER=0` (today's behavior, attribution control).
4. Loki, `LOKI_ROUTER=1 LOKI_ROUTER_ADVISOR=off` (fallback path, proves "never fail a run for it").
Metrics per run: solved (hidden tests), wall seconds, usd (SDK total_cost_usd, including advisor tokens), escalations taken, share of requests above 100K, advisor consultations.
Pass: arm 2 solve rate >= arm 1 on every repo; time within raw + 30s (small) or raw x 1.3; cost <= raw x 1.2 (expected well below); zero harness-owned FAILED/STALLED; arm 4 completes every run with advisor NOT PROVEN recorded, and its solve rate is >= arm 1 on every repo; if not, the no-advisor executor is Sonnet 5.5 (section 4.4). Rows go to docs/v10/METRICS.md with commands and exit codes. A correctness loss on a shape does NOT hold the release: `--emit-shape-defaults` lists it, R1-19 ships it in `router-shape-defaults.json`, and that shape runs on Sonnet (+ Opus advisor) by default (section 4.6). Before R1-19 merges, rerun B9 on the losing shape with the seeded file present to confirm Sonnet reaches parity there; if Sonnet also loses, it ships as `prior-default` in router-shape-defaults.json, plus a FAILURE-CLASSES row; it is not a release blocker for other shapes. Time and cost misses on a shape are recorded and routed the same way. FireLater#17 is binding: it must pass on whatever executor its shape resolves to. The R1-19 gate also requires arm 4 correctness parity (or the Sonnet no-advisor fallback in place) and that every `prior-default` shape runs the exact `LOKI_ROUTER=0` models.
Budget: B9 runs count inside the 10% measurement budget; the coordinator runs them, not a slice builder.

## 8. Risks and open questions
1. Price unverified: $0.10/$0.50 (<=100K) and $0.50/$2.50 (>100K), cache read $0.01/$0.05, are steering-reported. R1-02 step 1 confirms on the Anthropic pricing page or blocks. Engine10 run dollars come from the SDK, so a wrong table only skews partial streams and estimates, but receipts would still be wrong there.
2. Advisor via SDK: the 0.3.293 Options shape for advisorModel, whether the engine10 tool whitelist and `settingSources: ["project"]` keep it, and how advisor tokens show in `modelUsage`. R1-07 proves it with a real run before HIGH review.
3. Resume across a model swap: refused today (`session_resume.ts:41`); SDK behavior of `resume` + new `model` is unverified (R1-12 step).
4. Gateways: a proxy that forwards to the Anthropic API might support the advisor even with a custom `ANTHROPIC_BASE_URL`; the probe treats it as unavailable (safe, costs some quality). Revisit with field data.
5. Haiku default reach: `LOKI_ALLOW_HAIKU` gates haiku everywhere else (`providers/claude.sh:47-79`, `claude_flags.ts:153`). The router bypasses it only on the engine10 path under the flag; the legacy route is unchanged. Decide later whether the legacy route follows.
6. Cost of advisor at Opus rates on long sessions could erase the Haiku saving; the B9 cost column and >100K share decide. If cost > raw x 1.2 on a shape, `--emit-shape-defaults` lists it (Sonnet, or `prior-default` if Sonnet also misses).
7. Rate limits: Haiku 5.5 is one day old; `fallbackForPrimary` (`claude_flags.ts:155`) has no haiku arm, so a haiku 429 has no fallback model. R1-07 adds `fallbackModel: sonnet` for a haiku primary.
8. Requested removal of "hand-rolled opus review stages the advisor covers": none exist in engine10 (xreview is an opt-in second provider, `stages/xreview.ts`; the council reviewer runs on the account default). Nothing to delete; the advisor is additive.
