Outcome
The routing policy separates models from roles, so any model the runner can execute can fill any role, and a planner model (Opus 5.5, Astra, Fable) can later be selected as worker, reviewer or dispatcher by editing preferences rather than adding routes.
Problem
runner/policy.py already lists every model in ROUTES, but each route fuses model, effort and role: opus-5.5/high vs opus-5.5/high-review, luna/max vs luna-max-review, astra/max vs astra/medium vs astra/high-review. Stages carry a fixed executor (host, planner, runner) and submit --route accepts implementation routes only, so Opus 5.5 and Astra cannot run through the runner at all, even though the runner already drives Claude (planner callbacks) and Codex (Luna dispatch).
Shape
- Model catalog: harness, pool, model id, allowed efforts, context window, limits, next pool.
- Roles: the existing kits (planner, dispatcher, worker, reviewer, correction, recovery), with permissions, skills, plugins, instructions.
- Preferences per role: ordered model+effort references into the catalog, with today's fallback rules. Today's lanes become worker preferences.
- The critical step stays planner-executed in the planner's own session.
- Per-role, per-pool caps so runner work on the Claude or Codex pool cannot starve the planner (see the planner rate-limit gap).
Acceptance criteria
- One catalog entry per model; no route duplicated only to change role.
- The runner can execute any catalog model in any role it has a harness adapter for (Claude, Codex, OpenCode, Grok).
- The generated routing reference is regenerated from the new policy and its equality test passes.
- Existing jobs whose ledger stores an old route name still resolve, recover and report the same requested and observed route.
- Current default behavior is unchanged: same dispatcher, same worker chain, same fallback order, same caps.
- Full deterministic proof passes.
Non-goals
- The command to run a named model from a thread (separate Issue).
- Changing which models are preferred for any role.
- A learned routing policy.
Blockers
Proof
python3 scripts/test.py, plus a regression that resubmits and recovers a job stored under a pre-refactor route name.
Objective contribution
Deliberate detour, user-authorized 2026-09-24, scheduled after #88 and #86 merge. It does not advance the delivery milestone directly; it removes the structural reason planner-class models cannot serve other roles.
Outcome
The routing policy separates models from roles, so any model the runner can execute can fill any role, and a planner model (Opus 5.5, Astra, Fable) can later be selected as worker, reviewer or dispatcher by editing preferences rather than adding routes.
Problem
runner/policy.pyalready lists every model inROUTES, but each route fuses model, effort and role:opus-5.5/highvsopus-5.5/high-review,luna/maxvsluna-max-review,astra/maxvsastra/mediumvsastra/high-review. Stages carry a fixed executor (host,planner,runner) andsubmit --routeaccepts implementation routes only, so Opus 5.5 and Astra cannot run through the runner at all, even though the runner already drives Claude (planner callbacks) and Codex (Luna dispatch).Shape
Acceptance criteria
Non-goals
Blockers
runner/policy.py,runner/core.py,runner/harnesses.py,runner/cli.pyand the generated reference with Remove elapsed-time termination of active agents and jobs #88 (PR Delete elapsed-time termination of agent turns and jobs (#88) #90) and Expose the live Codex dispatcher through its native app-server transport #86. Implement after both merge. Design can proceed now.Proof
python3 scripts/test.py, plus a regression that resubmits and recovers a job stored under a pre-refactor route name.Objective contribution
Deliberate detour, user-authorized 2026-09-24, scheduled after #88 and #86 merge. It does not advance the delivery milestone directly; it removes the structural reason planner-class models cannot serve other roles.