docs: fold the model-resolution rulings into the plans; STE pass on the rewrite plan
Bryan ruled on the three open resolution questions (2026-08-01): revert the resolution machinery to main's shape (cut MODEL_LISTS, the cost table, the allowlist), drop pi from the routed set for now (bar list goes with it), and use one fixed fallback model per family (claude -> sonnet, gpt -> terra) with an honest decline behind it. The rewrite plan is now fully decided and rewritten in ASD-STE100 style; the overview's subsystem fates, invariants, and decision records match. Co-authored-by: Isaac
This commit is contained in:
+46
-28
@@ -2,75 +2,93 @@
|
||||
|
||||
> **How to reference this document.** Every block carries an ID: section number + letter (`2c` = third block of §2). Speak the ID and it names the block.
|
||||
|
||||
**0a** Goal: replace PR #3506 with a rewritten branch that ships the same three CUJs (Smart Routing as a model choice on Claude Code and Codex; the Smart Routing harness; routed native subagent spawns) plus the CLI entry points, at a fraction of the current size, in a commit series a reviewer can actually read. This document is the plan Bryan critiques before the Opus fleet executes it.
|
||||
**0a** Goal: replace PR #3506 with a rewritten branch. The new branch ships the same three CUJs plus the CLI entry points at a fraction of the current size, in a commit series a reviewer can read. The three CUJs are: Smart Routing as a model choice on Claude Code and Codex; the Smart Routing harness; routed native subagent spawns. Bryan critiques this plan before the Opus fleet executes it.
|
||||
|
||||
**0b** Hard constraints. The rewrite starts from the current verified tree, not from scratch — every behavior on the branch is evidence-verified (registry: 15/15 matrix, live checks) and re-implementation would forfeit that. Cuts remove *scope*, not correctness. The final branch rebases onto current main (we are 201 commits behind again) and re-runs the §11 matrix before it replaces #3506.
|
||||
**0b** Hard constraints. The rewrite starts from the current verified tree, not from scratch. Every behavior on the branch is evidence-verified (registry: 15/15 matrix, live checks), and a re-implementation would lose that evidence. Cuts remove scope, not correctness. The final branch rebases onto current main (we are 201 commits behind again), and it re-runs the §11 matrix before it replaces #3506.
|
||||
|
||||
## 1. What the PR is today
|
||||
|
||||
**1a** 28,991 insertions / 148 files against origin/main. Composition: python tests 9,591; web source 5,104; web tests 2,625; docs 2,593; server 2,461; runner+inner 3,054; adapters 1,394; cli+other 1,873; telemetry 296. Production source is ~9,100 lines; tests are 12,216 (42%); docs 2,593 (9%).
|
||||
**1a** 28,991 insertions / 148 files against origin/main. Composition: python tests 9,591; web source 5,104; web tests 2,625; docs 2,593; server 2,461; runner+inner 3,054; adapters 1,394; cli+other 1,873; telemetry 296. Production source is ~9,100 lines. Tests are 12,216 lines (42%). Docs are 2,593 lines (9%).
|
||||
|
||||
**1b** Largest single files: `runner/subagent_routing.py` +1,248; `server/smart_routing.py` +1,120; `NewChatDialog.test.tsx` +956; `orchestration.py` +886; `hook_scripts/subagent_router.py` +675; `cli.py` +505; `NewChatDialog.tsx` +488; `codex_executor.py` +484; `smart_routing_cli.py` +394 (the CLI workstream).
|
||||
|
||||
## 2. Keep-core: the minimum each CUJ needs
|
||||
|
||||
**2a** CUJ A (model choice on claude/codex): `smart_routing.py` (client, seam, arm menus, substitution), the orchestration turn gates, decision persistence as conversation items, the claude apply layer (vocabulary, alias pins, /model inject), the codex apply layer (settings push, config mirror, forwarder precedence), the chip rendering rules, the configure-dialog model option, gateway-backed gating (Bryan's explicit rule). **Managed-plugin readiness is part of this keep-core, not a later port.** The routing client is constructed ONCE, dynamically, and a preview-flag evaluation enables or disables it. The flag is evaluated at enable time, when the client is built — never inside `route()` per request. The managed-swap finding is the reason: a per-request flag evaluation leaves the `routing_client is not None` gates true in a flag-off workspace, so the surfaces still offer a pick the workspace will not serve. When the flag is off, or when the external client cannot be built, the seam falls back to LLM-judge routing and the session still runs. The construction seam must match the Databricks managed plugin shape, so the swap is a plugin registration and not a rewrite.
|
||||
**2a** CUJ A (model choice on claude/codex) keeps: `smart_routing.py` (the client, the seam, the arm menus, the family fallback from 3i); the orchestration turn gates; decision persistence as conversation items; the claude apply layer (alias vocabulary, alias pins, `/model` injection); the codex apply layer (settings push, config mirror, forwarder precedence); the chip rendering rules; the configure-dialog model option; gateway-backed gating (Bryan's explicit rule). **Managed-plugin readiness is part of this keep-core, not a later port.** We construct the routing client once, and we construct it dynamically. A preview-flag evaluation enables or disables the client at construction time — never inside `route()` per request. The managed-swap finding gives the reason: a per-request flag evaluation leaves the `routing_client is not None` gates true in a flag-off workspace, so the surfaces offer a pick that the workspace does not serve. When the flag is off, or when we cannot construct the external client, the seam falls back to LLM-judge routing and the session still runs. The construction seam must match the Databricks managed-plugin shape, so the swap is a plugin registration and not a rewrite.
|
||||
|
||||
**2b** CUJ B (Smart Routing harness): `_resolve_native_smart_routing`, pre-session catalogs, `smart_routing_message`, harness row + persistence, cross-family subagents allowed only here.
|
||||
**2b** CUJ B (Smart Routing harness) keeps: `_resolve_native_smart_routing`, the pre-session catalogs, `smart_routing_message`, the harness row and its persistence, and cross-family subagents (allowed only under this harness).
|
||||
|
||||
**2c** CUJ C (routed subagent spawns): hook scripts + loopback relay + server policy (`resolve_subagent_route`), family constraints, per-session override with the Inherit row, codex hooks.json generation + trust handshake + `python -I`.
|
||||
**2c** CUJ C (routed subagent spawns) keeps: the hook scripts, the loopback relay, the server policy (`resolve_subagent_route`), the family constraints, the per-session override with the Inherit row, and the codex `hooks.json` generation + trust handshake + `python -I`.
|
||||
|
||||
**2d** CLI (the new workstream, must survive the rewrite): `smart_routing_cli.py`, the `--smart-routing`/`-p` flags, the glm gateway-route fix (`907f8886`), and the tier-2/3 commits, which have now merged: `8f3c0c60` (merge `6f2893d9`) is the server half — create-time MODEL routing for a create pinned to one *fixed* native harness, which the turn gate can never reach because a TUI's turns originate in the pane; `8d7c9cb2` is the CLI half (flags, `smart_routing_cli.py`, the dispatch-spec `prompt_param`, both dispatch tiers); `b10a7239` fixes the `CLAUDE_NATIVE_AGENT_NAME` import against this branch's `harness_plugins` layout. Mechanics: `CUJ_IMPLEMENTATION.md` §6; registry rows: `CUJ_STATUS.md` §2.10.
|
||||
**2d** CLI (the new workstream, and it must survive the rewrite): `smart_routing_cli.py`, the `--smart-routing`/`-p` flags, the glm gateway-route fix (`907f8886`), and the tier-2/3 commits, which are now merged. `8f3c0c60` (merge `6f2893d9`) is the server half: create-time MODEL routing for a create pinned to one *fixed* native harness. The turn gate can never reach that case, because a TUI's turns originate in the pane. `8d7c9cb2` is the CLI half: the flags, `smart_routing_cli.py`, the dispatch-spec `prompt_param`, and both dispatch tiers. `b10a7239` fixes the `CLAUDE_NATIVE_AGENT_NAME` import against this branch's `harness_plugins` layout. Mechanics: `CUJ_IMPLEMENTATION.md` §6. Registry rows: `CUJ_STATUS.md` §2.10.
|
||||
|
||||
**2e** **The in-session model indicator must show the routed model (new must-fix).** The session UI displays the active model at the bottom right. Bryan observed the terminal running the routed model while that display showed the old one, so the rewrite treats it as a bug to fix, not as inherited behavior. The cause is a disagreement between two channels: `SessionModelEvent` and the chatStore picker state on one side, the pane on the other. The fix belongs in the web commit, and it makes the display read the same routed value the pane applied. The three UI surfaces that must pass acceptance are: (a) Smart Routing offered as a model option on the Claude Code and Codex configure dialogs, (b) Smart Routing offered as a harness, and (c) the in-session model display showing the routed model.
|
||||
**2e** **The in-session model indicator must show the routed model (new must-fix).** The session UI shows the active model at the bottom right. Bryan saw the terminal run the routed model while that display showed the old one. The rewrite treats this as a bug to fix, not as inherited behavior. The cause is a disagreement between two channels: `SessionModelEvent` plus the chatStore picker state on one side, and the pane on the other. The fix belongs in the web commit, and it makes the display show the same routed value that the pane applied. Three UI surfaces must pass acceptance: (a) Smart Routing appears as a model option on the Claude Code and Codex configure dialogs; (b) Smart Routing appears as a harness; (c) the in-session model display shows the routed model.
|
||||
|
||||
## 3. Cut list — each with size, what is lost, and my recommendation
|
||||
|
||||
**3a** **Docs → separate PR (−2,593).** The four design docs move to a docs-only PR stacked on the code PR. Zero functionality lost. Recommendation: cut from the code PR. (We already did this dance once; this time it is a second PR, not untracked files.)
|
||||
**3a** **Docs → separate PR (−2,593).** The four design docs move to a docs-only PR stacked on the code PR. Zero functionality lost. Recommendation: move them out of the code PR. (We untracked them once already; this time they get a second PR, not untracked files.)
|
||||
|
||||
**3b** **The codex enforcement stack → cut entirely (−~1,200 src + ~1,500 tests). RESOLVED (7a).** The canary, the enforcement watcher, the spawn audit + reconciliation, the warning banner (web + server halves), `session_warnings`, and the R8 machinery all leave the tree. Bryan's call: make hook execution work all the time instead of reporting when it does not. A banner that tells the user routing may not have applied does not make sense as a product surface — fix the underlying path. So the rewrite ships no canary, no watcher, no spawn audit, no warning banner, and no `session_warnings`. Keep the hook generation + trust handshake, because deterministic subagent routing depends on them. A follow-up may reintroduce observability if hook execution ever proves unreliable in the field; nothing in the plan schedules that work. This remains the single biggest source-side cut.
|
||||
**3b** **The codex enforcement stack → cut entirely (−~1,200 src + ~1,500 tests). RESOLVED (7a).** The canary, the enforcement watcher, the spawn audit and its reconciliation, the warning banner (web and server halves), `session_warnings`, and the R8 machinery all leave the tree. Bryan's call: make hook execution work all the time instead of reporting when it does not. A banner that tells the user routing may not have applied is not a product surface — fix the underlying path. So the rewrite ships no canary, no watcher, no spawn audit, no warning banner, and no `session_warnings`. We keep the hook generation and the trust handshake, because deterministic subagent routing depends on them. A follow-up may reintroduce observability if hook execution ever proves unreliable in the field; nothing in this plan schedules that work. This is still the single biggest source-side cut.
|
||||
|
||||
**3c** **Cross-harness soft redirect → cut (−~150).** The deny-plus-"use sys_session_send" message. It is a product experiment; the A-sub verification showed models decline to follow it. Lost: any cross-family *delivery* under auto (the cross-family *decision* still records). Recommendation: cut; auto sessions constrain spawns to the resolved harness's family for v1.
|
||||
**3c** **Cross-harness soft redirect → cut (−~150).** This is the deny-plus-"use `sys_session_send`" message. It is a product experiment, and the A-sub verification showed that models decline to follow it. Lost: cross-family *delivery* under auto (the cross-family *decision* still records). Recommendation: cut it; an auto session constrains spawns to the resolved harness's family for v1.
|
||||
|
||||
**3d** **Fork-spawn exemption → cut (−~80).** Test-pinned only, never verified live, no user-visible surface. Recommendation: cut; forks inherit the session model implicitly.
|
||||
**3d** **Fork-spawn exemption → cut (−~80).** It is test-pinned only, never verified live, and it has no user-visible surface. Recommendation: cut it; a fork inherits the session model implicitly.
|
||||
|
||||
**3e** **Telemetry events → keep (±296).** Small, review-hardened (family/tier labels only), and the managed swap references them. Recommendation: keep; not worth the churn to cut.
|
||||
**3e** **Telemetry events → keep (±296).** They are small, review-hardened (family and tier labels only), and the managed swap references them. Recommendation: keep them; a cut is not worth the churn.
|
||||
|
||||
**3f** **Gateway-inference gating → keep in-PR but as its own commit (~900 src+tests).** Bryan's explicit product rule; also the PR's only migration. It is cleanly separable if the critique wants it as a stacked PR instead.
|
||||
**3f** **Gateway-inference gating → keep in-PR as its own commit (~900 src+tests).** It is Bryan's explicit product rule, and it carries the PR's only migration. It is cleanly separable if the critique wants a stacked PR instead.
|
||||
|
||||
**3g** **Test rewrite, not test transplant (−~4,000–5,000 of 12,216).** The current tests accreted per-fix across three review waves: they pin intermediate states, duplicate coverage across consolidated files, and carry fixture scaffolding for deleted machinery. The fleet writes a fresh test suite against the *final* behavior per area (the coverage-gated method from `f8328623` worked; this time start from the behavior list, not the old files). **RESOLVED (7d): there is no numeric line target.** The goal is a directed, useful suite that pins the final behavior. The gate is coverage against the registry inventory (`CUJ_STATUS.md` §2), not a line count. Every keep-core behavior gets a test; nothing gets a test only to reach a number. The suite shrinks because the intermediate-state tests go, and the size that falls out is the size.
|
||||
**3g** **Test rewrite, not test transplant (−~4,000–5,000 of 12,216).** The current tests grew fix by fix across three review waves: they pin intermediate states, they duplicate coverage across consolidated files, and they carry fixture scaffolding for deleted machinery. The fleet writes a fresh suite against the *final* behavior per area (the coverage-gated method from `f8328623` worked; this time start from the behavior list, not from the old files). **RESOLVED (7d): there is no numeric line target.** The goal is a directed, useful suite that pins the final behavior. The gate is coverage against the registry inventory (`CUJ_STATUS.md` §2), not a line count. Every keep-core behavior gets a test. Nothing gets a test only to reach a number. The suite shrinks because the intermediate-state tests go, and the size that falls out is the size.
|
||||
|
||||
**3h** **Web slimming (−~1,000 of 5,104).** 3b takes the banner, its availability plumbing, and their tests out of the tree for good, so no banner code and no banner carve-out survives anywhere in the web layer. The dead-code deletions already happened in review. `NewChatDialog.tsx`'s remaining +488 is mostly the harness row + gating + persistence — keep. The web commit also carries the 2e model-indicator fix. Recommendation: no further web cuts beyond what 3b implies.
|
||||
**3h** **Web slimming (−~1,000 of 5,104).** 3b takes the banner, its availability plumbing, and their tests out of the tree for good, so no banner code and no banner carve-out survives anywhere in the web layer. The dead-code deletions already happened in review. `NewChatDialog.tsx`'s remaining +488 is mostly the harness row, the gating, and the persistence — keep it. The web commit also carries the 2e model-indicator fix. Recommendation: no web cuts beyond what 3b implies.
|
||||
|
||||
**3i** **Things explicitly NOT cut, and one group still under review.** Settled keeps (7e, partially resolved): honest `applied=false` records and the raw/applied chip stay — they caught two real bugs; session-start cadence machinery stays, because it IS the simple path now. **Under review — research in flight:** the `MODEL_LISTS` fork, the cost-substitution table, and the layered redirect. These three are the model-resolution machinery, and they are pending Bryan's review rather than settled. Bryan's directive for that review: with an AIGW-backed host and a configured client there should be no deep fallback chain at all. Never break the session — but prefer an honest decline over a silent substitution driven by a cost table the user cannot see. The final call lands after the research does, and the fleet does not lock this area in before then.
|
||||
**3i** **Model resolution — RESOLVED (2026-08-01; was "under review").** The research landed, and Bryan ruled on all three questions. The machinery reverts to main's simple shape. The settled keeps stand (7e): honest `applied=false` records and the raw/applied chip stay — they caught two real bugs; the session-start cadence machinery stays, because it IS the simple path now. The three rulings:
|
||||
|
||||
**3j** Net size estimate if 3a–3d and 3g land: roughly **28,991 → ~15,500** insertions (−2,593 docs, −~2,700 enforcement src+tests now cut outright, −~230 redirect+fork, −~4,500 test rewrite, −~500 misc consolidation), with production source around 6,500. Honest caveat: the test line is an estimate, not a target (3g). The floor depends on how much coverage the behavior inventory demands, and the inventory wins.
|
||||
- **Cut the resolution machinery.** We cut the `MODEL_LISTS` fork, the cost-substitution machinery (~260 source lines: `MODEL_LISTS`, `_cost_position`, the nearest-cost walk), and the 10-id hardcoded allowlist. Research basis: the substitution path has zero live triggers on the reference workspace; all five frozen arms resolve exactly today; of the 20 recorded raw-model events, 17 were prefix-spelling restores and 3 came from one bug that is already fixed.
|
||||
- **The chain becomes four steps.** Strip the prefix → match the catalog exactly → apply the family fallback → decline honestly. An honest decline writes no pin, records `applied=false`, shows a decision card, and keeps the session default. The session never breaks.
|
||||
- **The fallback is one fixed model per family (Bryan's rule).** The claude family falls back to sonnet. The gpt family falls back to terra (catalog id `databricks-gpt-5-6-terra`, confirmed in `LIVE_MODEL_STATE.md`). A fallback stamps `raw_model`, so the record and the chip stay honest. Assumption for Bryan to confirm: glm is a single-arm family with no designated fallback, so an unservable glm pick declines honestly.
|
||||
|
||||
Two boundaries for the fleet: the gateway spelling pin (glm serves under the `system.ai.` route) stays, and it is a spelling, not a substitution; the claude `/model` alias vocabulary is NOT part of this cut, because the Claude CLI accepts only its aliases for a mid-session switch (2a keeps it).
|
||||
|
||||
**3j** Net size estimate if 3a–3d, 3g, and the 3i/3k cuts land: roughly **28,991 → ~14,900** insertions (−2,593 docs, −~2,700 enforcement src+tests, −~230 redirect+fork, −~600 resolution machinery + bar list with their tests, −~4,500 test rewrite, −~500 misc consolidation), with production source around 6,100. Honest caveat: the test line is an estimate, not a target (3g). The floor depends on how much coverage the behavior inventory demands, and the inventory wins.
|
||||
|
||||
**3k** **Pi is not a routed harness (RESOLVED 2026-08-01; Bryan: "for now").** Smart-routing eligibility requires a gateway-backed family, and that requirement includes the mid-session toggle on ChatPage (`isCostRoutingEligible`). `gateway_inference` reports only the claude and codex families, so pi leaves the routed set. This closes a real hole: a pi session could toggle Smart Routing on and pass the gateway rule vacuously, because the rule never saw pi's family. Consequences: we cut the bar list (~137 lines plus plumbing) — pi was its only consumer; our layered-redirect diff against main goes to ~zero, because main's wire-compat redirect function stays as main wrote it. The door stays open for later. This PR does no pi work.
|
||||
|
||||
## 4. Shape of the rewritten branch
|
||||
|
||||
**4a** Method: assemble, don't re-implement. New branch from current origin/main; bring over the final tree per area minus the cut list; hand-reconcile only where main moved again (expect drift in `smart_routing.py`-adjacent files — main gained 107 more commits since the last rebase). Every commit is a working slice with its tests.
|
||||
**4a** Method: assemble, do not re-implement. Start a new branch from current origin/main. Bring over the final tree per area, minus the cut list. Hand-reconcile only where main moved again (expect drift in the `smart_routing.py`-adjacent files — main gained 107 more commits since the last rebase). Every commit is a working slice with its tests.
|
||||
|
||||
**4b** Proposed series (~9 commits): (1) routing core — client, seam, arms, substitution, settings, **plus managed-plugin readiness**: the single dynamic client construction, the preview-flag evaluation at enable time, and the graceful fallback to LLM-judge routing when the flag is off or the external client cannot be built (2a states the rule and the reason); (2) decision persistence + session overrides; (3) server orchestration — turn gates, smart-routing create, pre-session catalogs; (4) claude apply layer; (5) codex apply layer incl. hooks generation + trust; (6) subagent routing — hook scripts, loopback, policy, family rules, override; (7) gateway-inference gating (+ the migration); (8) CLI — flags, `smart_routing_cli`, prompt param, glm route fix; (9) web — all surfaces. Tests ride inside each commit; no separate test commit.
|
||||
**4b** Proposed series (~9 commits). Tests ride inside each commit; there is no separate test commit.
|
||||
|
||||
**4c** **RESOLVED (7b): the code ships as one single PR, and the docs split is the only stacking.** The docs PR stacks on top (plan, registry, walkthrough, model-state — updated to describe the slimmed scope, with the cut subsystems described as cut, not deferred). Gating (3f) stays a commit inside the one PR, not a stacked PR.
|
||||
1. Routing core: the client, the seam, the arms, the family fallback, the settings — **plus managed-plugin readiness**: the single dynamic client construction, the preview-flag evaluation at construction time, and the graceful fallback to LLM-judge routing when the flag is off or when the external client cannot be built (2a states the rule and the reason).
|
||||
2. Decision persistence and the session overrides.
|
||||
3. Server orchestration: the turn gates, the smart-routing create, the pre-session catalogs.
|
||||
4. The claude apply layer.
|
||||
5. The codex apply layer, including hooks generation and trust.
|
||||
6. Subagent routing: the hook scripts, the loopback, the policy, the family rules, the override.
|
||||
7. Gateway-inference gating, plus the migration.
|
||||
8. CLI: the flags, `smart_routing_cli`, the prompt param, the glm route fix.
|
||||
9. Web: all surfaces.
|
||||
|
||||
**4d** One follow-up PR is planned, and it is **telemetry integration for the Databricks-managed deployment**. The OSS analytics events ship unchanged in this PR (3e); the managed side — wiring those events into the managed telemetry pipeline — lands after merge, alongside the managed plugin swap that commit 1 prepares for (2a). This block is a placeholder for that work. It is *not* the enforcement follow-up: 3b cuts that stack outright and schedules nothing.
|
||||
**4c** **RESOLVED (7b): the code ships as one single PR, and the docs split is the only stacking.** The docs PR stacks on top (the plan, the registry, the walkthrough, the model-state notes — updated to describe the slimmed scope, with the cut subsystems described as cut, not deferred). Gating (3f) stays a commit inside the one PR, not a stacked PR.
|
||||
|
||||
**4d** One follow-up PR is planned: **telemetry integration for the Databricks-managed deployment.** The OSS analytics events ship unchanged in this PR (3e). The managed side — wiring those events into the managed telemetry pipeline — lands after merge, alongside the managed plugin swap that commit 1 prepares (2a). This block is a placeholder for that work. It is *not* the enforcement follow-up: 3b cuts that stack outright and schedules nothing.
|
||||
|
||||
## 5. CLI fixes integration
|
||||
|
||||
**5a** Sequencing rule: the CLI worktree merges into `routing-mvp` FIRST (Bryan's other session owns that merge), the matrix re-runs on the merged tree, and only then does the rewrite assembly start — the rewrite must never race an inbound merge. **Both halves are now in** (`907f8886`, then `8f3c0c60`/`6f2893d9` + `8d7c9cb2` + `b10a7239`), so nothing further is inbound from that worktree. What is still owed is the *verification*: the CLI surface is unit-verified only, so the re-run must add the `CUJ_STATUS.md` §2.10 rows (recipe **R10**) alongside the 15-row matrix.
|
||||
**5a** Sequencing rule: the CLI worktree merges into `routing-mvp` FIRST (Bryan's other session owns that merge). The matrix re-runs on the merged tree. Only then does the rewrite assembly start — the rewrite must never race an inbound merge. **Both halves are now in** (`907f8886`, then `8f3c0c60`/`6f2893d9` + `8d7c9cb2` + `b10a7239`), so nothing further is inbound from that worktree. The *verification* is still owed: the CLI surface is unit-verified only, so the re-run must add the `CUJ_STATUS.md` §2.10 rows (recipe **R10**) alongside the 15-row matrix.
|
||||
|
||||
**5b** In the rewrite, CLI lands as commit 8 (4b) — and the tier-2 server half is already a **separate** commit, so the split is mechanical rather than a hand-untangling job. `8f3c0c60` touches only `orchestration.py` plus its test and goes into commit 3 verbatim; `8d7c9cb2` touches **no** server file and goes into commit 8 verbatim. It also did **not** extend `_resolve_native_smart_routing`: the fixed-harness route is a parallel path (`_fixed_native_routing_harness` + `_resolve_fixed_native_model_routing`), and the only edit to the auto path is a refactor that lifts its authorize-first host lookup into the shared `_routing_host_for_create` — which the assembler must keep, because both create paths now call it and dropping it un-does the §4.3d authorization-order fix.
|
||||
**5b** In the rewrite, CLI lands as commit 8 (4b) — and the tier-2 server half is already a **separate** commit, so the split is mechanical rather than a hand-untangling job. `8f3c0c60` touches only `orchestration.py` plus its test, and it goes into commit 3 verbatim. `8d7c9cb2` touches **no** server file, and it goes into commit 8 verbatim. It also did **not** extend `_resolve_native_smart_routing`: the fixed-harness route is a parallel path (`_fixed_native_routing_harness` + `_resolve_fixed_native_model_routing`), and the only edit to the auto path is a refactor that lifts its authorize-first host lookup into the shared `_routing_host_for_create`. The assembler must keep that helper, because both create paths now call it, and a drop would undo the §4.3d authorization-order fix.
|
||||
|
||||
## 6. Execution and verification
|
||||
|
||||
**6a** Fleet plan: one agent per 4b commit-slice working in ONE worktree sequentially per area (the file-ownership partition from the review waves worked; reuse it), a verification agent running the registry recipes after slices 5, 6, 8, and 9, and the lead verifying + pushing. Before anything replaces #3506: full suites, `pre-commit --all-files`, the 15-row matrix + session-start + manual-pin + R9 checks live, registry re-stamped, PR body regenerated from the final diff.
|
||||
**6a** Fleet plan: one agent per 4b commit-slice, working in ONE worktree, sequentially per area (the file-ownership partition from the review waves worked; reuse it). A verification agent runs the registry recipes after slices 5, 6, 8, and 9. The lead verifies and pushes. Before anything replaces #3506: the full suites, `pre-commit --all-files`, the 15-row matrix + session-start + manual-pin + R9 checks live, the registry re-stamped, and the PR body regenerated from the final diff.
|
||||
|
||||
**6b** The old branch survives as `routing-mvp-v1` (like `routing-mvp-backup` before it); the PR either force-pushes or opens fresh — Bryan's call at handoff time.
|
||||
**6b** The old branch survives as `routing-mvp-v1` (like `routing-mvp-backup` before it). The PR either force-pushes or opens fresh — Bryan's call at handoff time.
|
||||
|
||||
## 7. Bryan's critique — the decisions
|
||||
|
||||
**7a** **RESOLVED: cut the enforcement stack entirely, do not defer it with a banner.** Bryan: make it work all the time instead; the warning does not make sense; just fix it. No canary, no watcher, no spawn audit, no warning banner, no `session_warnings`. Hooks generation and the trust handshake stay, because deterministic subagent routing needs them. See 3b, 3h, and overview `2i`.
|
||||
**7a** **RESOLVED: cut the enforcement stack entirely; do not defer it with a banner.** Bryan: make it work all the time instead; the warning does not make sense; just fix it. No canary, no watcher, no spawn audit, no warning banner, no `session_warnings`. Hooks generation and the trust handshake stay, because deterministic subagent routing needs them. See 3b, 3h, and overview `2i`.
|
||||
|
||||
**7b** **RESOLVED: one single PR.** The docs split (3a) is the only stacking. See 4c.
|
||||
|
||||
@@ -78,6 +96,6 @@
|
||||
|
||||
**7d** **RESOLVED: no numeric test target.** The fleet writes directed, useful tests that pin the final behavior, gated on coverage against the registry inventory. The ≤5,500 / ≤1,500 numbers are withdrawn. See 3g.
|
||||
|
||||
**7e** **Partially resolved.** Honest `applied=false` and the raw/applied chip stay — decided. The rest of 3i (the `MODEL_LISTS` fork, the cost-substitution table, the layered redirect) is pending Bryan's review with research in flight; see 3i for the directive that governs the call.
|
||||
**7e** **RESOLVED in full (2026-08-01; was "partially resolved").** The settled keeps stand: honest `applied=false` and the raw/applied chip stay. Bryan ruled on the rest: cut the `MODEL_LISTS` fork and the cost-substitution table; revert the resolution machinery to main's shape; use the per-family fallback (claude → sonnet, gpt → terra) with an honest decline behind it; drop pi from the routed set for now. See 3i and 3k. Two assumptions stay open for Bryan's veto: glm has no fallback and declines; terra's catalog id is `databricks-gpt-5-6-terra`.
|
||||
|
||||
**7f** Two items entered the plan from the same critique rather than leaving it: managed-plugin readiness as a commit-1 requirement (2a, 4b) and the in-session model indicator as a must-fix bug (2e).
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
**2a — The routing client and the task_v1 contract.** The client calls the AI Gateway `routes:select` API, and it resolves the router's pick to a servable catalog id. `cli.py` builds exactly one client at startup, so no runtime fallback chain exists. Without the client no journey can produce a pick. It lives in `omnigent/server/smart_routing.py` (+1,120 lines, shared with 2b). Fate: keep-core, commit 1 of the series (plan `2a`). Commit 1 also carries managed-plugin readiness. The code builds the client once and builds it dynamically. A preview-flag evaluation enables or disables the client, and that evaluation happens when the client is built, not inside `route()` on each request. The managed-swap finding gives the reason: a per-request evaluation leaves the `routing_client is not None` gates true in a flag-off workspace, and the surfaces then offer a pick the workspace cannot serve. When the flag is off, or when the external client cannot be built, the seam falls back to LLM-judge routing and the session still runs. The construction seam matches the Databricks managed plugin shape, so the managed swap is a registration and not a rewrite.
|
||||
|
||||
**2b — The resolution seam and arm substitution.** One module boundary holds all router vocabulary: it builds the offered menu, resolves the pick to a `(harness, model)` pair, and substitutes a servable model for an arm the workspace cannot serve. Without the seam every caller would learn the router's vocabulary. It shares `smart_routing.py` with 2a. Fate: keep-core, commit 1. The model-resolution machinery inside it — the `MODEL_LISTS` fork, the cost-substitution table, and the layered redirect — is **under review, with research in flight** (plan `3i`). Bryan's directive for that review: an AIGW-backed host with a configured client should need no deep fallback. Never break the session, but prefer an honest decline over a silent cost-table substitution. The final call lands after the research does.
|
||||
**2b — The resolution seam and the family fallback.** One module boundary holds all router vocabulary: it builds the offered menu, resolves the pick to a `(harness, model)` pair, and applies the family fallback when the workspace does not serve the picked arm. Without the seam every caller would learn the router's vocabulary. It shares `smart_routing.py` with 2a. Fate: keep-core, commit 1, minus the resolution machinery — **RESOLVED (2026-08-01, plan `3i`)**. We cut the `MODEL_LISTS` fork, the cost-substitution table (~260 source lines), the 10-id allowlist, and the bar list (~137 lines; pi was its only consumer — plan `3k`). The seam reverts to main's simple shape, and our layered-redirect diff against main goes to ~zero. The chain has four steps: strip the prefix → match the catalog exactly → apply the family fallback → decline honestly. The fallback is one fixed model per family: claude → sonnet, and gpt → terra (`databricks-gpt-5-6-terra`). A fallback stamps `raw_model`, so the record and the chip stay honest. Glm has no fallback and declines (an assumption for Bryan to confirm). Research basis: the substitution path has zero live triggers on the reference workspace.
|
||||
|
||||
**2c — The turn and create gates in orchestration.** The server decides per turn whether to route, and it decides per create whether a native session routes its harness, its model, or neither. Without the gates a routed session would re-route on every turn and lose its pin. It lives in `omnigent/server/routes/_sessions/orchestration.py` (+886) and `helpers.py` (+79). Fate: keep-core, commit 3 (plan `2a`, `2b`, `5b`).
|
||||
|
||||
@@ -38,17 +38,17 @@
|
||||
|
||||
**2g — Codex hooks generation and trust.** The code generates the Omnigent half of `hooks.json`, merges it with the user's half in one atomic write, and then runs a trust handshake over the app-server. Without the handshake codex silently skips the routing hooks. It lives in `omnigent/inner/codex_executor.py` (+484). Fate: keep-core, commit 5. The plan keeps this half of the codex machinery even though it cuts 2i, because deterministic subagent routing depends on it (plan `2c`, `3b`).
|
||||
|
||||
**2h — The subagent loopback, hook scripts, and policy.** A runner-local HTTP endpoint answers the harness's hook subprocess, and the policy returns `allow`, `rewrite`, `redirect`, or `deny`. Without the loopback an in-harness spawn never reaches any router. It spans `runner/subagent_routing.py` (+1,248), `inner/hook_scripts/` (+941), and `routes_hooks.py` (+150). Fate: keep-core, commit 6, minus two cuts: the cross-harness soft redirect (plan `3c`, −~~150) and the fork-spawn exemption (plan~~ `3d`~~, −~~80).
|
||||
**2h — The subagent loopback, hook scripts, and policy.** A runner-local HTTP endpoint answers the harness's hook subprocess, and the policy returns `allow`, `rewrite`, `redirect`, or `deny`. Without the loopback an in-harness spawn never reaches any router. It spans `runner/subagent_routing.py` (+1,248), `inner/hook_scripts/` (+941), and `routes_hooks.py` (+150). Fate: keep-core, commit 6, minus two cuts: the cross-harness soft redirect (plan `3c`, −~150) and the fork-spawn exemption (plan `3d`, −~80).
|
||||
|
||||
**2i — The enforcement and observability stack.** A canary hook writes a file, a watcher reports the file as absent, a spawn audit reconciles the models that actually ran, and a banner shows the warning. Without it codex can skip the hooks and nothing reports the failure. It spans `runtime/session_warnings.py` (+165), `SessionWarningBanner.tsx` (+95), and the watcher and audit code inside 2f and 2g. Fate: **cut entirely** (plan `3b`, `7a`, −~~1,200 source and −~~1,500 tests). Bryan's call: make the hooks run every time instead of reporting when they do not, because a warning banner is not a product answer. The rewrite therefore ships no canary, no watcher, no spawn audit, no banner, and no `session_warnings`. No follow-up PR restores them. A follow-up may reintroduce observability if hook execution ever proves unreliable in the field. This is still the largest source-side cut.
|
||||
**2i — The enforcement and observability stack.** A canary hook writes a file, a watcher reports the file as absent, a spawn audit reconciles the models that actually ran, and a banner shows the warning. Without it codex can skip the hooks and nothing reports the failure. It spans `runtime/session_warnings.py` (+165), `SessionWarningBanner.tsx` (+95), and the watcher and audit code inside 2f and 2g. Fate: **cut entirely** (plan `3b`, `7a`, −~1,200 source and −~1,500 tests). Bryan's call: make the hooks run every time instead of reporting when they do not, because a warning banner is not a product answer. The rewrite therefore ships no canary, no watcher, no spawn audit, no banner, and no `session_warnings`. The plan schedules no follow-up. A future PR may reintroduce observability only if hook execution proves unreliable in the field. This is still the largest source-side cut.
|
||||
|
||||
**2j — Gateway-inference gating.** The host reports, per harness family, whether its inference resolves to the workspace AI Gateway, and every surface hides Smart Routing on an explicit `false`. Without the gate a host offers a pick that its pane can never run. It spans `gateway_inference.py` (+93), `databricks_ai_gateway.py` (+68), the host frames and store (+192), the hosts routes (+98), one migration (+49), and `smartRoutingAvailability.ts` (+114). Fate: keep in-PR as its own commit 7 (plan `3f`, ~900 source and tests).
|
||||
**2j — Gateway-inference gating.** The host reports, per harness family, whether its inference resolves to the workspace AI Gateway, and every surface hides Smart Routing on an explicit `false`. Without the gate a host offers a pick that its pane can never run. It spans `gateway_inference.py` (+93), `databricks_ai_gateway.py` (+68), the host frames and store (+192), the hosts routes (+98), one migration (+49), and `smartRoutingAvailability.ts` (+114). Fate: keep in-PR as its own commit 7 (plan `3f`, ~900 source and tests). The gate also fixes the routed harness set: eligibility, including the mid-session toggle, requires a gateway-backed family, and pi has none, so pi is not a routed harness for now (plan `3k`, resolved 2026-08-01).
|
||||
|
||||
**2k — The web surfaces.** The new-chat dialog holds the Model option, the harness row, and the availability notices. The chat page holds the subagent-routing row, and the status blocks render the decision card. Without them the user has no way to pick routing or to read a decision. `web/src` adds +4,278 lines, of which the tests are about 2,625. The largest files are `NewChatDialog.tsx` (+488), `renderItems.ts` (+186), and `ChatPage.tsx` (+180). Fate: keep, minus the banner that 2i takes with it, and with no banner carve-out left behind (plan `3h`, −~1,000). This layer also owns a **known bug the rewrite must fix**: the in-session model indicator at the bottom right of the session UI must show the routed model. Bryan saw the terminal run the routed model while that display still showed the old one. The `SessionModelEvent` and chatStore picker-state channel disagrees with the pane, and the fix makes the display read the value the pane applied (plan `2e`). The three UI surfaces that must pass acceptance are: Smart Routing as a model option on the Claude Code and Codex configure dialogs, Smart Routing as a harness, and a correct in-session model display.
|
||||
|
||||
**2l — Telemetry.** Two analytics events record a decision and a setting change, and both reduce a model id to a family label and a tier label. Without them no deployment can measure the routing rate. It lives in `omnigent/telemetry/` (+296). Fate: keep unchanged (plan `3e`). The OSS events ship as they are. A follow-up PR wires them into the Databricks-managed telemetry pipeline (plan `4d`).
|
||||
|
||||
**2m — The CLI layer.** Three commands take `--smart-routing`, and the CLI runs preflight, drives the create, and passes the routed model to the wrapper as a launch flag. Without it a CLI user must start every routed session in a browser. It spans `cli.py` (+505), `smart_routing_cli.py` (+394), and `cli_native.py` (+77). Fate: keep as commit 8 (plan `2d`, `5b`). §4 holds the open question about its PR placement.
|
||||
**2m — The CLI layer.** Three commands take `--smart-routing`, and the CLI runs preflight, drives the create, and passes the routed model to the wrapper as a launch flag. Without it a CLI user must start every routed session in a browser. It spans `cli.py` (+505), `smart_routing_cli.py` (+394), and `cli_native.py` (+77). Fate: keep as commit 8 (plan `2d`, `5b`). Its PR placement is decided: commit 8 inside the single PR (4c).
|
||||
|
||||
**2n — The tests.** The branch adds +12,278 lines of Python and web tests. They accreted per fix over three review waves, so they pin intermediate states and duplicate coverage. Fate: rewrite against the final behavior rather than transplant (plan `3g`). No line target governs the rewrite. The gate is coverage against the registry inventory, and the suite is as large as directed, useful tests of the final behavior require.
|
||||
|
||||
@@ -66,12 +66,14 @@
|
||||
|
||||
**3d — Every gate fails open.** A router outage, a hook timeout, a transport error, or a failed translation leaves the turn unrouted and attaches the reason. Routing is advisory over a system that must work without it.
|
||||
|
||||
**3e — The arm menus are a wire contract.** The router version is frozen upstream, so the arm list lives in code and the workspace catalog cannot change it. A menu that the code derives from the catalog returns 400 or scores against an uncalibrated recipe. The machinery that resolves an arm to a servable model — the fork, the cost table, and the layered redirect — is under review; the wire contract itself is not (plan `3i`).
|
||||
**3e — The arm menus are a wire contract.** The router version is frozen upstream, so the arm list lives in code and the workspace catalog cannot change it. A menu that the code derives from the catalog returns 400 or scores against an uncalibrated recipe. The machinery that resolves an arm to a servable model is now settled (plan `3i`): exact match, then the family fallback, then an honest decline. The wire contract itself does not change.
|
||||
|
||||
**3f — A manual pin blocks routing.** Any `model_override` closes the turn gate, whoever wrote it. The two controls are therefore mutually exclusive in the UI as well.
|
||||
|
||||
**3g — The display agrees with the pane.** The in-session model indicator shows the model the process is running. If the pane applied a routed model, the indicator shows that model. A display that disagrees with the pane teaches the user to distrust the feature, so the picker-state channel follows the applied value and never the stale pick (plan `2e`).
|
||||
|
||||
**3h — An unservable pick falls back inside the family, or it declines.** When the workspace does not serve the router's pick, the seam applies the family's designated fallback (claude → sonnet, gpt → terra) and stamps `raw_model`. When the workspace does not serve the fallback either, the seam declines honestly and the session keeps its default model. The session never breaks (plan `3i`).
|
||||
|
||||
---
|
||||
|
||||
## 4. The decisions
|
||||
@@ -84,6 +86,8 @@
|
||||
|
||||
**4d — RESOLVED: no numeric test target.** The fleet writes directed tests of the final behavior and gates them on coverage against the registry inventory. The 5,500 and 1,500 line numbers are withdrawn (plan `7d`, `3g`).
|
||||
|
||||
**4e — Partially resolved.** The honest `applied=false` record and the raw-versus-applied arrow on the chip stay. The rest — the `MODEL_LISTS` fork, the cost table, and the layered redirect — is under review with research in flight; 2b holds the directive that governs the call (plan `7e`, `3i`).
|
||||
**4e — RESOLVED (2026-08-01).** The honest `applied=false` record and the raw-versus-applied arrow on the chip stay. Bryan ruled on the rest: cut the `MODEL_LISTS` fork and the cost table; revert the seam to main's shape; use the per-family fallback (claude → sonnet, gpt → terra) with an honest decline behind it (plan `7e`, `3i`). Two assumptions stay open for Bryan's veto: glm has no fallback and declines; terra's catalog id is `databricks-gpt-5-6-terra`.
|
||||
|
||||
**4f — Two items the same critique added.** Managed-plugin readiness is a commit-1 requirement (2a), and the in-session model indicator is a must-fix bug (2k, 3g).
|
||||
|
||||
**4g — RESOLVED: pi is not a routed harness for now.** Smart-routing eligibility requires a gateway-backed family, and the requirement includes the mid-session toggle. The bar list goes with it, and the layered-redirect diff against main goes to ~zero (plan `3k`).
|
||||
|
||||
Reference in New Issue
Block a user