feat(model-router): wave 2-A — phase rows for every repo skill and agent, run slot, typed-slash routing

Rows by role replace the pins as the live source (frontmatter stays as the
off-state floor): phases write=work/high and apply=work/low, 56 skill rows,
21 agent rows + Explore/Plan. Agents get the row's model at spawn (within
the tier, upward only, explicit params win, project-defined agents skipped
via agent.offer) and its effort per step. A typed slash of a rowed skill
routes main through a name-bound marker (composer|sdk|bridge, pending slot
mid-turn) or the idle fallback; a best-tier row lives in a runMain slot
that survives turn end and route calls. An unrowed skill leaves the route.
Typed /effort-* floor code removed (bridge kept until W2-B). The route
answer always names the id. Override rows accept null. Kit suite 58 → 88.

Contract .claude/tasks/contracts/2026-10-09-model-router-w2a-1546.md, plan
r4 .claude/tasks/plans/2026-10-09-model-router-w2-1546.md: 3 lenses + 2
confirmations, feater + 4 rounds, GATE 0 MET, verifier 3x ECARTS on test
coverage only (user-accepted at the cap), security PASS.
This commit is contained in:
bchanot
2026-10-09 17:22:07 +02:00
parent 862740da9d
commit bb56f3e41e
2 changed files with 783 additions and 171 deletions
+467 -57
View File
@@ -23,6 +23,8 @@ async function boot(
on('session.model', () => ({ value: model }))
on('classic.StopFailure', () => ({}))
on('classic.PostModelSwitch', () => ({}))
on('skill.prompt', ($, e) => ({ text: e.text }))
on('agent.offer', () => ({ isOffered: true }))
if (tokens !== null) usageOf(on, tokens)
on('session.start', ($, e) => ({ cwd: e.cwd }))
await $.session.start({ cwd: '/tmp', surface: null, isInteractive: false })
@@ -321,30 +323,14 @@ test('floor: ultrathink survives a model route', async ($, on) => {
expect(await stepEffort($, seen)).toBe('max')
})
test('floor: typed /effort-medium clamps low, lets max pass', async (
test('floor: survives a skill load; an unrowed skill keeps the route', async (
$, on) => {
const seen = await bootFloor($, on)
await $.skill.prompt({ skill: 'effort-medium', text: 'x' })
await $.tool.call({ tool: ROUTE_TOOL, phase: 'mechanical' })
expect(await stepEffort($, seen)).toBe('medium')
await $.tool.call({ tool: ROUTE_TOOL, phase: 'escalate' })
expect(await stepEffort($, seen)).toBe('max')
})
test('floor: typed /effort-low lowers an unrouted turn', async ($, on) => {
const seen = await bootFloor($, on)
const out = await $.skill.prompt({ skill: 'effort-low', text: 'x' })
expect(out.text).toContain('minimum')
expect(await stepEffort($, seen)).toBe('low')
})
test('floor: survives a skill load', async ($, on) => {
const seen = await bootFloor($, on)
await ultrathink($)
await $.tool.call({ tool: ROUTE_TOOL, phase: 'orchestrate' })
await $.tool.call({ tool: 'Skill', skill: 'other' })
expect(await stepEffort($, seen)).toBe('max')
expect(mainLine(await route($, 'show'))).not.toContain('orchestrate')
expect(mainLine(await route($, 'show'))).toContain('model orchestrate')
})
test('floor: lifts a lower sticky, then ends with the turn', async (
@@ -439,35 +425,6 @@ test('reload with an unreadable override keeps the previous config', async (
expect(await route($, 'show')).toContain('switch: on')
})
test('typed marker: a preload in a live agent is ignored', async ($, on) => {
const seen = await bootFloor($, on)
await $.agent.spawn(spawnInput())
const out = await $.skill.prompt({ skill: 'effort-max', text: 'x' })
expect(out.text?.startsWith('model-router: effort-max preload')).toBe(true)
expect(mainLine(await route($, 'show'))).not.toContain('floor')
expect(await stepEffort($, seen)).not.toBe('max')
})
test('typed marker: /effort-max seen at submit writes the floor', async (
$, on) => {
await bootFloor($, on)
await $.agent.spawn(spawnInput())
await $.prompt.submit({
text: '/effort-max go',
wait: false,
origin: { kind: 'composer' },
})
await $.skill.prompt({ skill: 'effort-max', text: 'x' })
expect(mainLine(await route($, 'show'))).toContain('floor max')
})
test('typed slash with no agent and no marker writes the floor', async (
$, on) => {
await bootFloor($, on)
await $.skill.prompt({ skill: 'effort-max', text: 'x' })
expect(mainLine(await route($, 'show'))).toContain('floor max')
})
// ---- tiers, breaker, derived phases -------------------------------------
type Rig = { seen: Seen[]; clock: MockClock; specs: (string | undefined)[] }
@@ -766,16 +723,6 @@ test('default rule: a typed slash command gets no default rule', async (
expect(mainLine(await route($, 'show'))).toContain('session defaults')
})
test('default rule: /effort-low pourquoi sets the floor, no default', async (
$, on) => {
await bootRig($, on)
await typed($, '/effort-low pourquoi ça plante')
await $.skill.prompt({ skill: 'effort-low', text: 'x' })
const line = mainLine(await route($, 'show'))
expect(line).toContain('floor low')
expect(line).not.toContain('reflect')
})
test('per axis: a model-less sticky never hides a turn route tier', async (
$, on) => {
const rig = await bootRig($, on, HAIKU)
@@ -844,3 +791,466 @@ test('text: show names the model a floor upgrade moves to', async (
const line = mainLine(await route($, 'show'))
expect(line).toContain(`${FABLE} (upgrade)`)
})
// ---- wave 2: skill rows, run slot, agent rows ---------------------------
const skillPrompt = ($: Engine, skill: string) =>
$.skill.prompt({ skill, text: 'x' })
const loadSkill = ($: Engine, skill: string, agentId?: string) =>
$.tool.call({
tool: 'Skill',
skill,
...(agentId === undefined ? {} : { agentId }),
})
/** bootRig plus a bottom Skill tool, so a model-loaded skill goes through. */
async function bootRun($: Engine, on: On, model: string = FABLE) {
on('tool.call', { tool: 'Skill' }, () => ({
result: { success: true, commandName: 'bottom' },
}))
return bootRig($, on, model)
}
const spawnOf = (agent: string, extra: object = {}) =>
({ ...spawnInput(), subagentType: agent, ...extra })
const offerOf = ($: Engine, agent: string, source: string) =>
$.agent.offer({
agent,
description: 'd',
source,
provider: { plugin: 'engine', tier: 'core' },
})
/**
* A ui.log recorder. The kit refuses a bottom hook for a void event, so the
* hook sits on top and swallows the missing implementation below it.
*/
function recordLogs(on: On): string[] {
const lines: string[] = []
on('ui.log', async ($, e, next) => {
lines.push(e.text)
try {
await next(e)
} catch {
// nothing implements ui.log below the recorder in the kit
}
})
return lines
}
test('typed /feat routes main to its row, best tier', async ($, on) => {
await bootRun($, on)
await typed($, '/feat add a thing')
await skillPrompt($, 'feat')
const line = mainLine(await route($, 'show'))
expect(line).toContain('skill reflect')
expect(line).toContain('effort high')
expect(line).toContain('[tier best]')
})
test('typed slash: a foreign origin arms nothing, fallback refused', async (
$, on) => {
await bootRun($, on)
await $.prompt.submit({
text: '/feat x',
wait: false,
origin: { kind: 'channel', server: 'slack' },
})
await skillPrompt($, 'feat')
expect(mainLine(await route($, 'show'))).toContain('session defaults')
})
test('typed slash: a preload in a live sub-agent leaves main alone', async (
$, on) => {
await bootRun($, on)
await typed($, 'hello')
await $.agent.spawn(spawnOf('feater'))
await skillPrompt($, 'feat')
expect(mainLine(await route($, 'show'))).toContain('session defaults')
})
test('typed slash: no live loop and an allowed origin still routes', async (
$, on) => {
await bootRun($, on)
await typed($, 'hello')
await skillPrompt($, 'feat')
expect(mainLine(await route($, 'show'))).toContain('skill reflect')
})
test('typed slash: an sdk origin arms the marker', async ($, on) => {
await bootRun($, on)
await $.prompt.submit({
text: '/status',
wait: false,
origin: { kind: 'sdk' },
})
await $.agent.spawn(spawnOf('feater'))
await skillPrompt($, 'status')
expect(mainLine(await route($, 'show'))).toContain('skill mechanical')
})
test('typed slash: a mid-turn /status waits for the turn end', async (
$, on) => {
await bootRun($, on)
await $.prompt.submit({
text: '/status',
wait: true,
origin: { kind: 'composer' },
turnId: 'u1',
})
await skillPrompt($, 'status')
expect(mainLine(await route($, 'show'))).toContain('session defaults')
await endTurn($)
await skillPrompt($, 'status')
expect(mainLine(await route($, 'show'))).toContain('skill mechanical')
})
test('skill without a row keeps the route; a rowed one replaces it', async (
$, on) => {
await bootRun($, on)
await $.tool.call({ tool: ROUTE_TOOL, phase: 'plan' })
await loadSkill($, 'find-docs')
expect(mainLine(await route($, 'show'))).toContain('model plan')
await loadSkill($, 'gitflow')
expect(mainLine(await route($, 'show'))).toContain('skill implement')
})
test('run slot: survives route calls and the turn end', async ($, on) => {
await bootRun($, on)
await loadSkill($, 'feat')
await $.tool.call({ tool: ROUTE_TOOL, phase: 'orchestrate' })
await endTurn($)
expect(mainLine(await route($, 'show'))).toContain('run reflect')
await $.tool.call({ tool: ROUTE_TOOL, phase: 'plan' })
await endTurn($)
expect(mainLine(await route($, 'show'))).toContain('run reflect')
})
test('run slot: a model-loaded helper skill keeps it', async ($, on) => {
await bootRun($, on)
await loadSkill($, 'feat')
await loadSkill($, 'using-git-worktrees')
expect(mainLine(await route($, 'show'))).toContain('skill mechanical')
await endTurn($)
expect(mainLine(await route($, 'show'))).toContain('run reflect')
})
test('run slot: a typed non-best skill drops it', async ($, on) => {
await bootRun($, on)
await loadSkill($, 'feat')
await endTurn($)
await typed($, '/status')
await skillPrompt($, 'status')
await endTurn($)
expect(mainLine(await route($, 'show'))).toContain('session defaults')
})
test('run slot: /route clear and /route off drop it', async ($, on) => {
await bootRun($, on)
await loadSkill($, 'feat')
expect(await route($, 'clear')).toContain('run slot dropped')
await endTurn($)
expect(mainLine(await route($, 'show'))).toContain('session defaults')
await loadSkill($, 'feat')
await route($, 'off')
await route($, 'on')
await endTurn($)
expect(mainLine(await route($, 'show'))).toContain('session defaults')
})
test('run slot: a user /model switch drops it, an auto one does not', async (
$, on) => {
await bootRun($, on)
await loadSkill($, 'feat')
await endTurn($)
await switchTo($, FABLE, OPUS, 'auto')
expect(mainLine(await route($, 'show'))).toContain('run reflect')
await switchTo($, FABLE, OPUS, 'command')
expect(mainLine(await route($, 'show'))).toContain('session defaults')
})
test('run slot: a Skill call inside a sub-agent never touches it', async (
$, on) => {
await bootRun($, on)
await loadSkill($, 'feat', 'a1')
await endTurn($)
expect(mainLine(await route($, 'show'))).toContain('session defaults')
})
test('run slot: route(clear) clears the turn and names the run', async (
$, on) => {
await bootRun($, on)
await loadSkill($, 'feat')
const out = await $.tool.call({ tool: ROUTE_TOOL, clear: true })
expect(JSON.stringify(out)).toContain('run reflect still holds')
})
test('Skill(effort-low) bridge is not sticky', async ($, on) => {
await bootRun($, on)
await loadSkill($, 'effort-low')
expect(mainLine(await route($, 'show'))).toContain('effort low')
await endTurn($)
expect(mainLine(await route($, 'show'))).toContain('session defaults')
})
test('route answer names the id even with a floor in force', async (
$, on) => {
await bootRun($, on)
await ultrathink($)
const out = await $.tool.call({ tool: ROUTE_TOOL, phase: 'plan' })
const text = JSON.stringify(out)
expect(text).toContain(`model ${FABLE}`)
expect(text).toContain('user floor max')
})
test('agent row: feater spawns on sonnet and steps at medium', async (
$, on) => {
const rig = await bootRun($, on)
const out = await $.agent.spawn(spawnOf('feater'))
expect(rig.specs).toEqual([SONNET])
expect(out.model).toBe(SONNET)
await runStep($, { ...highStep('a1'), model: SONNET })
expect(rig.seen[rig.seen.length - 1]).toEqual({
model: SONNET,
effort: 'medium',
})
})
/** Spawn feater, load a skill inside it, return the effort of its step. */
async function effortAfterSkill($: Engine, on: On, skill: string) {
const rig = await bootRun($, on)
await $.agent.spawn(spawnOf('feater'))
await loadSkill($, skill, 'a1')
await runStep($, { ...highStep('a1'), model: SONNET })
return rig.seen[rig.seen.length - 1].effort
}
test('agent skill: an unrowed one leaves the loop effort', async ($, on) => {
expect(await effortAfterSkill($, on, 'find-docs')).toBe('medium')
})
test('agent skill: a rowed one writes the loop effort', async ($, on) => {
expect(await effortAfterSkill($, on, 'status')).toBe('low')
})
test('agent row: judge moves up to fable when opus is down', async (
$, on) => {
const rig = await bootRun($, on)
await switchTo($, OPUS, SONNET, 'auto')
await $.agent.spawn(spawnOf('plan-challenger'))
expect(rig.specs).toEqual([FABLE])
})
test('agent row: opus and fable down, no write and one log line', async (
$, on) => {
const logs = recordLogs(on)
const rig = await bootRun($, on)
await switchTo($, OPUS, SONNET, 'auto')
await switchTo($, FABLE, SONNET, 'auto')
await $.agent.spawn(spawnOf('plan-challenger'))
await $.agent.spawn(spawnOf('plan-challenger'))
expect(rig.specs).toEqual([undefined, undefined])
const down = logs.filter(l => l.includes('frontmatter model kept'))
expect(down).toEqual([
'model-router: plan-challenger tier big down, frontmatter model kept',
])
})
test('agent row: explicit model, explicit effort and fork win', async (
$, on) => {
const rig = await bootRun($, on)
await $.agent.spawn(spawnOf('feater', { model: 'opus' }))
await $.agent.spawn(spawnOf('feater', { fork: true }))
expect(rig.specs).toEqual(['opus', undefined])
await $.tool.call({
tool: 'Agent',
description: 'd',
prompt: 'p',
effort: 'low',
tool_use_id: 't1',
})
await $.agent.spawn(spawnOf('feater'))
await runStep($, { ...highStep('a1'), model: SONNET })
expect(rig.seen[rig.seen.length - 1]?.effort).toBe('high')
})
test('agent row: a project definition keeps its own model', async ($, on) => {
const rig = await bootRun($, on)
await offerOf($, 'verifier', 'projectSettings')
await offerOf($, 'feater', 'userSettings')
await $.agent.spawn(spawnOf('verifier'))
await $.agent.spawn(spawnOf('feater'))
expect(rig.specs).toEqual([undefined, SONNET])
})
test('agent row: an override null drops the row', async ($, on) => {
on('env.get', () => ({ value: '/home/t' }))
on('fs.exists', () => ({ value: true }))
on('fs.stat', () => ({
value: { kind: 'file' as const, size: 40, mtimeMs: 0, isLink: false },
}))
on('fs.read', () => ({ value: '{"agents":{"verifier":null}}' }))
const rig = await bootRun($, on)
await $.agent.spawn(spawnOf('verifier'))
await $.agent.spawn(spawnOf('feater'))
expect(rig.specs).toEqual([undefined, SONNET])
})
test('show lists the write and apply phases', async ($, on) => {
await bootRun($, on)
const text = await route($, 'show')
expect(text).toContain('apply=work→claude-sonnet-5-5/low')
expect(text).toContain('write=work→claude-sonnet-5-5/high')
})
// ---- wave 2: typed-slash origins, spawning counter, spawn exclusions ----
/** A promise the test settles by hand, to hold a hook in flight. */
function deferred() {
let release: () => void = () => {}
const gate = new Promise<void>(r => { release = r })
return { gate, release }
}
test('typed slash: an in-flight spawn refuses the fallback', async (
$, on) => {
const hold = deferred()
const entered = deferred()
on('prompt.submit', ($, e) => ({ text: e.text }))
on('agent.spawn', async ($, e) => {
entered.release()
await hold.gate
return { model: e.model ?? e.parentModel, agentId: 'a1' }
})
await boot($, on)
await typed($, 'hello')
const spawned = $.agent.spawn(spawnOf('feater'))
await entered.gate
await skillPrompt($, 'feat')
expect(mainLine(await route($, 'show'))).toContain('session defaults')
hold.release()
await spawned
})
test('typed slash: a bridge origin arms the marker', async ($, on) => {
await bootRun($, on)
await $.prompt.submit({
text: '/feat x',
wait: false,
origin: { kind: 'bridge' },
})
await $.agent.spawn(spawnOf('feater'))
await skillPrompt($, 'feat')
expect(mainLine(await route($, 'show'))).toContain('skill reflect')
})
test('typed slash: a promoted mid-turn marker routes past a live loop', async (
$, on) => {
await bootRun($, on)
await $.agent.spawn(spawnOf('feater'))
await $.prompt.submit({
text: '/status',
wait: true,
origin: { kind: 'composer' },
turnId: 'u1',
})
await endTurn($)
await skillPrompt($, 'status')
expect(mainLine(await route($, 'show'))).toContain('skill mechanical')
})
test('agent row: any provider plugin applies, a local definition not', async (
$, on) => {
const rig = await bootRun($, on)
const provider = { plugin: 'some-plugin', tier: 'core' as const }
await $.agent.spawn(spawnOf('feater', { provider }))
await offerOf($, 'verifier', 'localSettings')
await $.agent.spawn(spawnOf('verifier', { provider }))
expect(rig.specs).toEqual([SONNET, undefined])
})
test('agent row: a workflow spawn is untouched, model and effort', async (
$, on) => {
const rig = await bootRun($, on)
await $.agent.spawn(spawnOf('feater', { workflow: {} }))
expect(rig.specs).toEqual([undefined])
await runStep($, { ...highStep('a1'), model: SONNET })
expect(rig.seen[rig.seen.length - 1]?.effort).toBe('high')
})
test('typed slash: the marker binds to its skill name only', async (
$, on) => {
await bootRun($, on)
await typed($, '/status')
await $.agent.spawn(spawnOf('feater'))
await skillPrompt($, 'feat')
expect(mainLine(await route($, 'show'))).toContain('session defaults')
})
test('typed slash: the marker is one-shot', async ($, on) => {
await bootRun($, on)
await typed($, '/status')
await $.agent.spawn(spawnOf('feater'))
await skillPrompt($, 'status')
expect(mainLine(await route($, 'show'))).toContain('skill mechanical')
await route($, 'clear')
await skillPrompt($, 'status')
expect(mainLine(await route($, 'show'))).toContain('session defaults')
})
test('skill.prompt nested in a Skill call leaves the run slot', async (
$, on) => {
on('tool.call', { tool: 'Skill' }, async (_api, e) => {
await skillPrompt($, 'status')
return { result: { success: true, commandName: String(e.skill) } }
})
await bootRig($, on, FABLE)
await typed($, '/status')
await loadSkill($, 'feat')
await endTurn($)
expect(mainLine(await route($, 'show'))).toContain('run reflect')
})
/** Submits a prompt from `kind` over a running turn (mid-turn). */
const queued = ($: Engine, kind: string, text: string) =>
$.prompt.submit({
text,
wait: true,
origin: { kind },
turnId: 'u1',
})
test('idle fallback: a mid-turn foreign prompt never arms it', async (
$, on) => {
await bootRun($, on)
await queued($, 'channel', 'hello')
await endTurn($)
await skillPrompt($, 'status')
expect(mainLine(await route($, 'show'))).toContain('session defaults')
})
test('idle fallback: a mid-turn composer prompt promotes the allowance', async (
$, on) => {
await bootRun($, on)
await $.prompt.submit({
text: 'hello',
wait: false,
origin: { kind: 'channel' },
})
await queued($, 'composer', 'hello')
await endTurn($)
await skillPrompt($, 'status')
expect(mainLine(await route($, 'show'))).toContain('skill mechanical')
})
test('spawning: a finished spawn no longer counts as a live loop', async (
$, on) => {
await bootRun($, on)
await $.agent.spawn(spawnOf('feater'))
await agentEnds($)
await typed($, 'hello')
await skillPrompt($, 'status')
expect(mainLine(await route($, 'show'))).toContain('skill mechanical')
})