TheRouter.hook.ts was retired entirely in release 7.0.0 (3baba670, 2026-07-11) — classification abolished, no successor, router libs deleted with it. Two kinds of remnant survived that deletion in the hook layer. Filing both together because they share one root cause and one decision-maker; a companion PR implements the mechanical half (deletion only), and this issue holds the behavior question.
Half 1 — three exports with zero callers
| Export |
Definition |
Former caller |
Callers today |
markSessionNative |
LifeOS/install/hooks/lib/isa-utils.ts:1285 |
TheRouter, on NATIVE turns (the sanctioned algorithm→native downgrade) |
none |
markAlgorithmStarting |
isa-utils.ts:1317 |
TheRouter, on MODE=ALGORITHM (pre-ISA early tier stamp) |
none |
setModeToken |
LifeOS/install/hooks/lib/tab-setter.ts:364 |
TheRouter, "the moment it classifies" (per its own JSDoc) |
none |
Evidence: git grep -nE 'markSessionNative|markAlgorithmStarting|setModeToken' on main returns only the definitions, their own doc-comments/error strings, and historical mentions in docs (PulseSystem.md:852, TerminalTabs.md:94/99, skills/CMUX/DESIGN.md:73). No imports, no tests, no dynamic references. Their docblocks still describe TheRouter as a live, authoritative caller.
Also inside the same dead region: LIVE_ALGO_PHASES (tab-setter.ts:362) is referenced only by setModeToken, and the docblock at isa-utils.ts:1252-1269 is physically orphaned — it documents markAlgorithmStarting but sits above markSessionNative.
Deliberately not included: extractModeToken, MODE_TOKEN_RE, and PromptProcessing's token-recovery branch. Those are live — setPhaseTab still mints fresh E{1-5} tokens on every ISA phase write (ISASync/AlgoPhase → effortToCanonicalELevel), and the recovery branch preserves a live E badge when a mid-run prompt restamps the working tab. Only the N token lost its sole writer. (TerminalTabs.md:81's "no successor stamps an E{tier}/N token" is accordingly wrong for E — happy to fold that one-line doc fix into whichever PR you prefer.)
Proposed: delete the three exports + their docblocks (isa-utils.ts 1252-1400, tab-setter.ts 343-393). The companion PR does exactly this. Alternative, if you'd rather keep them as vestigial API: a separate wording-sweep PR (in preparation, covering all surviving TheRouter comment/prompt-text references — will link here when open) re-annotates them as dead instead. We'd still argue deletion: exported functions whose contracts invite reuse of a retired architecture are a trap for future callers, and the zero-caller/zero-test evidence makes this removal unusually safe.
Half 2 — the downgrade guard now enforces a behavior nobody chose (question, not a patch)
upsertSession (isa-utils.ts:1184-1186) blocks algorithm→native currentMode transitions:
const isDowngradeFromAlgorithm = prevMode === 'algorithm' && resolvedMode === 'native';
if (prevMode !== resolvedMode && !isDowngradeFromAlgorithm) { /* record transition */ }
Its comment says the legitimate downgrade is recorded by TheRouter via markSessionNative(). With both dead, no path performs algorithm→native — the guard became an unconditional one-way latch. One prompt containing any of the 8 isNativeMode substrings (build/create/implement/refactor/design/architect/migrate/deploy — including "don't build this") latches the session for life. In a live instance: 11 of 15 work.json rows latched algorithm, zero algorithm→native flips in any modeHistory, two pure-conversation sessions stuck after a single heavy verb.
Observable blast radius is Pulse-only (session card mis-laning, permanent ModeTimeline segment, Sankey over-reporting escalations, 30-min instead of 15-min stale window). Nothing model-visible reads currentMode — AlgorithmNudge also requires an active algorithm phase, which latched chat sessions never have.
Was the ratchet chosen, or inherited by the deletion? Three resolutions:
- (A) Ratify the ratchet — our recommendation. The only classifier left is a per-prompt 8-verb regex with zero damping; freeing it means turn-by-turn mode flapping and unbounded modeHistory growth (the only
slice(-50) cap lived in dead markSessionNative; upsertSession's push has none). The latch is accidentally load-bearing damping, and "a session that did algorithm work stays an algorithm session for lane purposes" is defensible on its own. Cost: zero code — a comment rewrite stating the one-way truth (rides the wording-sweep PR).
- (B) Successor downgrade path. E.g. downgrade after N consecutive native-classified prompts (min-dwell hysteresis), or on ISA
phase='complete'. Constraint either way: port the slice(-50) cap into upsertSession first. Only worth it if the Pulse mis-laning bothers you.
- (C) Delete the guard. The one wrong answer — see (A)'s flap/growth evidence.
Testing evidence
bun build hooks/lib/isa-utils.ts hooks/lib/tab-setter.ts hooks/PromptProcessing.hook.ts --target=bun green before and after the companion PR's deletions; no *.test.ts in the tree references either file; tab-title rendering degrades cleanly ([token,icon].filter(Boolean).join(' ') — live paths unaffected).
Related: #1524 / #1525 (prior TheRouter-remnant cleanup).
TheRouter.hook.ts was retired entirely in release 7.0.0 (
3baba670, 2026-07-11) — classification abolished, no successor, router libs deleted with it. Two kinds of remnant survived that deletion in the hook layer. Filing both together because they share one root cause and one decision-maker; a companion PR implements the mechanical half (deletion only), and this issue holds the behavior question.Half 1 — three exports with zero callers
markSessionNativeLifeOS/install/hooks/lib/isa-utils.ts:1285markAlgorithmStartingisa-utils.ts:1317setModeTokenLifeOS/install/hooks/lib/tab-setter.ts:364Evidence:
git grep -nE 'markSessionNative|markAlgorithmStarting|setModeToken'onmainreturns only the definitions, their own doc-comments/error strings, and historical mentions in docs (PulseSystem.md:852,TerminalTabs.md:94/99,skills/CMUX/DESIGN.md:73). No imports, no tests, no dynamic references. Their docblocks still describe TheRouter as a live, authoritative caller.Also inside the same dead region:
LIVE_ALGO_PHASES(tab-setter.ts:362) is referenced only bysetModeToken, and the docblock atisa-utils.ts:1252-1269is physically orphaned — it documentsmarkAlgorithmStartingbut sits abovemarkSessionNative.Deliberately not included:
extractModeToken,MODE_TOKEN_RE, and PromptProcessing's token-recovery branch. Those are live —setPhaseTabstill mints freshE{1-5}tokens on every ISA phase write (ISASync/AlgoPhase →effortToCanonicalELevel), and the recovery branch preserves a live E badge when a mid-run prompt restamps the working tab. Only theNtoken lost its sole writer. (TerminalTabs.md:81's "no successor stamps an E{tier}/N token" is accordingly wrong for E — happy to fold that one-line doc fix into whichever PR you prefer.)Proposed: delete the three exports + their docblocks (
isa-utils.ts1252-1400,tab-setter.ts343-393). The companion PR does exactly this. Alternative, if you'd rather keep them as vestigial API: a separate wording-sweep PR (in preparation, covering all surviving TheRouter comment/prompt-text references — will link here when open) re-annotates them as dead instead. We'd still argue deletion: exported functions whose contracts invite reuse of a retired architecture are a trap for future callers, and the zero-caller/zero-test evidence makes this removal unusually safe.Half 2 — the downgrade guard now enforces a behavior nobody chose (question, not a patch)
upsertSession(isa-utils.ts:1184-1186) blocks algorithm→nativecurrentModetransitions:Its comment says the legitimate downgrade is recorded by TheRouter via
markSessionNative(). With both dead, no path performs algorithm→native — the guard became an unconditional one-way latch. One prompt containing any of the 8isNativeModesubstrings (build/create/implement/refactor/design/architect/migrate/deploy — including "don't build this") latches the session for life. In a live instance: 11 of 15 work.json rows latchedalgorithm, zero algorithm→native flips in any modeHistory, two pure-conversation sessions stuck after a single heavy verb.Observable blast radius is Pulse-only (session card mis-laning, permanent ModeTimeline segment, Sankey over-reporting escalations, 30-min instead of 15-min stale window). Nothing model-visible reads
currentMode— AlgorithmNudge also requires an active algorithmphase, which latched chat sessions never have.Was the ratchet chosen, or inherited by the deletion? Three resolutions:
slice(-50)cap lived in deadmarkSessionNative;upsertSession's push has none). The latch is accidentally load-bearing damping, and "a session that did algorithm work stays an algorithm session for lane purposes" is defensible on its own. Cost: zero code — a comment rewrite stating the one-way truth (rides the wording-sweep PR).phase='complete'. Constraint either way: port theslice(-50)cap intoupsertSessionfirst. Only worth it if the Pulse mis-laning bothers you.Testing evidence
bun build hooks/lib/isa-utils.ts hooks/lib/tab-setter.ts hooks/PromptProcessing.hook.ts --target=bungreen before and after the companion PR's deletions; no*.test.tsin the tree references either file; tab-title rendering degrades cleanly ([token,icon].filter(Boolean).join(' ')— live paths unaffected).Related: #1524 / #1525 (prior TheRouter-remnant cleanup).