The strongest model for judgment. The cheapest quota for tokens.
A Claude Code plugin that routes coding subtasks to the cheapest capable model — your main session (Opus) plans, reviews, verifies and merges; cheap executors write the code.
English | 中文
Most of a coding task's tokens go to mechanical labor — reading the repo, writing the implementation, iterating to green (measured: one ~400-line feature burned 1.88M executor input tokens). The part that actually needs your strongest model — planning, reviewing, verifying, merging — is low-token, high-judgment. router splits the work along exactly that line:
| Prompting the agent directly | With router | |
|---|---|---|
| Who executes | Opus (expensive) | the cheaper executor with more quota (codex / sonnet) |
| Change scope | bounded only by the prompt | enforced on the diff: allowed globs + changed-line cap |
| Correctness | you check by hand | CLI gates the diff (scope + secrets + exec bit); Opus runs the build/tests in your real env |
| ...and laziness | trust the model's word | ...plus the main session reviews the diff for lazy/wrong work |
| Where edits land | your working tree, immediately | a router/<task> branch in your own checkout; your branch changes only on land |
| Quota / rate limit | the run stalls | balances codex vs claude by real remaining quota; 429 fallover |
router never auto-merges. The gates decide PASS/FAIL; you decide land.
Measured on this repository's own development (20 real dispatches, router usage --all):
| actual spend | if all on Opus (est) | saved (est) | |
|---|---|---|---|
| 20 dispatches | $23.96 | ~$93.34 | ~$69.38 (~74%) |
The savings figure is a list-price estimate, not a bill — the executors run on plan
subscriptions, so real marginal cost is often lower; --explain-savings prints every
caveat. Quality is guarded by mechanism, not by trusting the cheap model: every diff
passes five mechanical gates, a full-diff review by the main session, real-environment
verification, and a mandatory full-chain CI pass before "done" — the acceptance bar is
identical to Opus writing the code itself. Measured first-pass rate on the routed tier:
89% (n=9, median wall clock 3.4 min).
Requirements: Claude Code · Node.js >= 18 · git · one executor CLI logged in
(codex or claude — a plan subscription is fine,
no API key needed).
Install from inside Claude Code:
/plugin marketplace add MisterRaindrop/agent-router-cc
/plugin install router@agent-router-cc
/reload-plugins
No install step beyond that, no config: dist/router.js is a committed, dependency-free
bundle, and router auto-creates a gitignored .router/ on first use. No init, no
policy file, no commit.
Then just talk to Opus, plan the change together, and:
/router:go
The same two steps without opening Claude Code. The repository is public, so no SSH key and no
gh login are needed:
claude plugin marketplace add MisterRaindrop/agent-router-cc
claude plugin install router@agent-router-cc -y # -y is required when stdin/stdout is not a TTYRestart Claude Code to apply, then check what landed:
claude plugin list | grep -A3 router # → Version, Scope, Status: ✔ enabledclaude plugin marketplace update agent-router-cc # refresh the marketplace cache first
claude plugin update router@agent-router-ccInside Claude Code the equivalent is /plugin marketplace update agent-router-cc, then update
router from the /plugin menu, then /reload-plugins.
Command files (commands/, skills/, hooks/) are read once at startup, so a version that
changes one of them needs the restart. The CLI bundle does not: dist/router.js is spawned fresh
on every call.
everyday task: plan with Opus in conversation → /router:go → /router:review (optional)
one package, one independent, strict
executor, gate, review of landed code
review, land
large feature (opt-in, YOUR call — router never judges task size):
/router:brainstorm → /router:design → /router:design-review (opt.) → /router:workplan → /router:go
question the idea; clarify + independent adversarial the how: steps, executes the
compare with how research; a pass; every objection task breakdown, approved plan
others solve it; DESIGN.md you adjudicated by you, verification; verbatim
argue the case approve section nothing auto-applied you approve
against by section
/router:go pauses at exactly three points — nothing happens without you (when it
executes a Plan approved via the design flow, the breakdown confirmation is skipped: you
already approved that list at /router:workplan):
- Confirm the task breakdown. Every package is shown with its file scope and target model before anything runs.
- Unclear tasks stay with you. Anything needing real judgment or design, Opus does with you directly instead of handing it to a cheap model.
- Approve before merge. Nothing lands in your branch without your say-so.
In between, the package runs on the quota-picked executor in your own checkout, on a
branch called router/<task-id>. That is the point: a fresh git worktree has no
dependencies, no build objects and no configure output, so a real project cannot compile in
one — and the build has to happen in the main checkout anyway. The whole run holds an
exclusive lock on the checkout, your uncommitted work is committed first (with the sha
reported), and the executor commits one functional unit at a time so you can review it
commit by commit.
One task at a time, by design. Parallel dispatch was removed: measured, it cost almost nothing to run (0.26s of orchestration against 393s of executor time) and a great deal to supervise — and every result still needs reviewing one at a time, so review was the bottleneck the parallelism kept feeding.
At the end Opus runs a mandatory acceptance pass: full-chain CI in your real environment, reading the whole output itself, before reporting done.
Every package is a machine contract at .router/tasks/<id>/task.yaml, authored by the
main session from your conversation — there is no global policy file:
# .router/tasks/q2/task.yaml
title: usage --json emits one document per run
plan_id: issue-1234
allowed_globs: ["src/app/**", "test/usage-*.test.ts"]
max_changed_lines: 400 # size it to the real diff shape: tests and deletions count too
tier: weak # capability needed: weak | strong | critical
risk: normal # review it earns: low | normal | high (one-way: only ever raised)
verify: [["npm", "test"]]
depends_on: []| field | question | decides |
|---|---|---|
tier |
how much capability does this need? | which model and reasoning effort |
risk |
how bad if it is wrong? | how much independent review it earns |
A mechanical change to an authentication path is weak and high. The CLI raises
risk from deterministic signals (line count, invariant paths touched) and never lowers
it; quota never demotes a task to a weaker tier.
| tier | codex | claude |
|---|---|---|
weak |
gpt-5.6-terra · medium | haiku · medium |
strong |
gpt-5.6-sol · high | sonnet · high |
critical |
gpt-5.6-sol · xhigh | opus · xhigh |
- Decide the minimum capability tier the task actually requires — the one routing decision that matters.
- Within the tier, both executors are candidates; real remaining quota picks (codex
usage read from
~/.codex/sessions, claude from an optional statusline snapshot). More headroom goes first; a real 429 switches to the other. Quota reorders within a tier — it never demotes. - Reasoning effort is matched to the work, not maxed:
mediumfor mechanical implementation,highfor real capability,xhighreserved forcritical. - The orchestrator's own model appears only at
critical— spending it as an ordinary executor would consume the very budget routing exists to protect.
Override any slot in .router/models.yaml; router models prints the resolved table.
Nothing ever edits it for you.
Environment-free gates — run by the CLI on every diff, the deterministic guarantees a cheap model cannot fake:
| check | meaning |
|---|---|
diff_applies |
applies cleanly onto the base commit |
scope |
only allowed_globs changed, under the line cap, no test deletion |
secret_scan |
no keys or secrets in the added lines |
exec_bit |
a new script carries the executable bit when its siblings do |
verify |
the task's own verify command(s) exited 0 |
verify answers a mechanical question — did it run and pass — never is it right.
The real gate is a property of the project, declared once in .router/gate.yaml. The
executor works in your own checkout now, so it has the same build environment you do — warm
dependencies, warm objects, a real configure result — and the gate runs there under the same
exclusive lock the run already holds:
| key | what it does |
|---|---|
gate |
the incremental build-and-test command |
clean_gate |
the full-rebuild command |
clean_triggers |
globs whose change forces clean_gate instead of gate. Anchored at the repo root, unlike gitignore: CMakeLists.txt is the root one only, the tree needs **/CMakeLists.txt |
reset |
run before verification, to clear state a previous build left behind |
lock_wait_minutes |
how long to wait when another run holds the checkout |
Any deletion or rename in the diff forces clean_gate regardless of triggers: an incremental build
keeps a stale object for a source file that no longer exists, and nothing tells it to drop it.
mode: queue remains for a project that verifies on an integration branch — router gate feeds
commits one at a time into your own checkout under an exclusive lock, refusing if tracked files
are modified, verifying on the current
integration head, keeping the build cache warm (never git clean), and restoring your
branch. A gate that fails is re-run on the pre-merge head, so a project that was already
red doesn't get blamed on the change.
For a large feature — cross-module work, real approach trade-offs — the user opts in. Every document is yours to approve:
/router:brainstorm→BRAINSTORM.md(optional first stage, for when the goal is not settled yet). Every round owes you four things: a question about why, from an angle you have not considered; a comparison with how other products actually solve this; the strongest argument against building it at all; and at least one alternative you did not propose. Your approach is the best one you have, not necessarily the best one there is.status: rejectedis a real outcome — a killed idea with a documented reason is this stage succeeding, and the record is what stops it coming back in three months with nobody remembering why./router:design→DESIGN.md(why / what / what NOT / chosen approach / risks / acceptance criteria). One clarifying question at a time, interleaved with code research (symbol index,file:lineevidence); 2–3 approaches with trade-offs and the rejected ones recorded; then the document is drafted section by section, each section confirmed by you before the next is written. No document is generated while the conversation is still open — that is where models start guessing./router:design-review(optional, any rounds) — an independent model attacks the Design: critique printed verbatim, written in your conversation language, every objection carrying aconfidence, uncertainty phrased as questions rather than assertions, and the reviewer must read Alternatives considered so it never re-proposes a road you already closed. Each objection is adjudicated by you — accept / reject / discuss, recorded inDECISIONS.md; nothing touches the document before your verdict. Runs in the background, truncation-guarded, session resumed across rounds./router:workplan→WORKPLAN.md(how: steps, task breakdown, dependencies, verification matrix, rollout) — derived only from an approved Design and bound to its revision: a Design revision drops the work plan back to draft. You approve a summary;/router:gothen executes it verbatim. Everyday tasks skip all of this and use/router:godirectly.
The task contract carries both the work plan and the Design, verbatim with their sha256s.
They answer different questions: the plan says what to do and in what order, the Design says why
it is built this way and which invariants may not break — and the second is what an executor can
never recover by reading code. BRAINSTORM.md is deliberately excluded: it records the
counter-evidence and the rejected directions, so handing it over would hand the executor a pile
of ideas that were decided against.
Green tests are the precondition, not the evidence — the tests themselves are under review. Two lenses, ideally two different models, 16 fixed dimensions:
- Architect lens (F1–F7): was the need actually solved; should this change exist at all; reuse vs reinvent; root cause vs symptom; simpler-but-still-correct; structure and integration; independent correctness judgment that does not trust the author's tests.
- Senior-dev lens (D1–D9): robustness beyond the tests; failure modes (no silent fallbacks); complexity/over-design ("an explanation longer than the code is complexity dressed as prose"); test design quality; readability; project-style consistency; comments and shortcut labeling; security; performance sense.
Verdicts are two axes, never collapsed: code_health (did we find defects?) and
assurance (is it actually proven?). "No defect found" is not "proven". Blocking must be
earned; a clean diff gets a plain "ship it". Mechanical checks (formatting, import order)
go to lint/CI, not to the LLM.
| command | what it does |
|---|---|
/router:go |
top-level — execute the plan you just agreed on (or an approved WORKPLAN.md, verbatim). One package, one pinned executor, on a router/<task> branch in your own checkout; runs detached, statusline shows live phase and activity, the session is woken at terminal states |
/router:brainstorm |
question an idea before designing it — compare it with how others solve it, argue the case against, propose the option you were not offered |
/router:design |
opt-in for large features — clarify, research, draft a DESIGN.md you approve section by section |
/router:design-review |
adversarial second opinion on the Design — you adjudicate every objection; nothing auto-applied. Also reports where an outside reader could not follow the document |
/router:workplan |
turn the approved Design into WORKPLAN.md — steps, task breakdown, verification; you approve |
/router:review |
strict, independent two-lens review of the landed code |
/router:resume <id> |
send a failure back to that task's own executor session |
/router:result <id> |
per-check verifier report and log tail for a run |
/router:list |
tasks with their last status and whether the task branch is still there |
/router:models |
the resolved model-tier table (bundled default + overrides) |
/router:usage |
cost vs an all-strongest-model baseline; --routing for routing evidence |
/router:symbol |
out-of-context symbol index — locate code without reading whole files |
/router:setup-statusline |
wire claude-side quota reads into Claude Code's statusLine |
docs/workflow.md is the whole protocol end to end — work packages, tiers and risk, both gate modes, what the executor owes back, and when to resume a session. See also docs/quickstart.md and a runnable task in examples/minimal/.
The executor shares your checkout, so isolation is expressed in git and in permissions rather than in a separate directory:
- An exclusive lock on the checkout for the whole run, taken before the first write and
held until the executor is dead. A second
/router:gois turned away naming the holder, with nothing changed. The lock's heartbeat runs in its own process, because verify commands block the event loop and an in-process beat would go silent for exactly as long as the lock's staleness window. - Your uncommitted work is committed first, with the file list and sha reported, before
anything moves. No
git stash(a stash is detached from the branch, and a conflicting pop on a failure path leaves your changes somewhere you have to be told about) and nogit clean. - Nothing destructive without asserting identity: a reset only runs while the current branch
is exactly the task's branch and
base_shais still an ancestor ofHEAD. - Router never merges and never switches back. The run ends telling you which branch you are standing on.
- Executors are supervised with a wall timeout and a stall watchdog; their output never enters the orchestrator's context, and no MCP server from your session is inherited.
- Codex uses its
workspace-writesandbox. Claude runs in plainacceptEdits(neverbypassPermissions). TheBashgrant is an allowlist, not a shell: the task's ownverifycommand plus its program+subcommand prefix, and a narrow set of git subcommands (add,commit,status,diff,log,rev-parse) so the executor can commit its own work.checkout,reset,rebase, branch deletion andpushare unreachable. - An executor cannot touch
.router/. A nestedrouterinvocation refuses outright — a reproduced failure, not a hypothetical: an executor given a task that changedrouter newranrouter new --id smoketo try its own work and wrote real orchestration state, invisible to every gate because.router/is fully gitignored. - Executor CLIs receive only the login-session context needed for plan auth plus an explicitly configured provider key — never your full parent environment.
- Every run ends with a delivery report (
gate_ran,scope_drift,escalate_review); a missing header is a contract violation, and a contract conflict (CONTRACT_CONFLICT) stops the run and returns the decision to you.
npm ci
npm run check # tsc --noEmit + core-purity guard + node --test
npm run build # bundle src/ -> dist/router.js (commit the result)src/ is layered domain -> core -> io -> app -> cli. core/ is pure (no fs,
child_process, process, clock, or randomness — enforced by npm run check:deps), which
keeps the gate logic deterministic and unit-testable.
Contributions are welcome — see CONTRIBUTING.md for the build, test, and PR workflow, ROADMAP.md for where the project is headed, and CHANGELOG.md for what each release changed. Security issues go through SECURITY.md, never public issues.
Apache-2.0.