agentleFS
tammaitammai/bigin-skills/skills/model-router/SKILL.md

model-router

Skill1 starsChanged 39 days agoMITView on GitHub

Scores task capability and verification needs on separate axes, then routes execution to the worker or architect subagent; also sets the project's model ladder. Triggers: 'route this task', 'which model should handle this', 'does this need the architect'.

  • Reads credentials

17 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

2 rules. 0 are common, 0 are distinctive, and 2 are very specific to this project.

  • 0

    Common

    Most of the 16 edited versions we read keep these.

  • 0

    Distinctive

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    After the subagent returns, check it against the payload's definition-of-done (contract: references/agent-invocation.md's "Return …

    line 127
  2. Very specific:

    Never spawn bigin-skills:verifier from this step — that's a separate, task-workflow-only mechanism scoped to auditing a diff against …

    line 133

“Common” is measured: kept by at least 6 in 10 of the 16 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

model-router

Scores a task, then hands it off to the matching subagent. Two implementer tiers plus the verifier — effort is fixed per agent, the model is whatever the project's profile resolves to.

Tierbalanced (default)frontier
Workerworker — sonnet/highworker-frontier — opus/medium
Architectarchitect — opus/mediumarchitect-frontier — opus/high
Verifierverifier — sonnet/highverifier — sonnet/high

The agent varies by profile, not just the model: effort comes only from an agent file's frontmatter (the Agent tool has no effort parameter), so a profile pinning a tier at a different effort routes to a variant agent. Read both off routing.agents[tier] and routing.models[tier] — never assume the agent from the tier name.

Two axes, scored separately, because they answer different questions:

  • Capability picks the tier, and therefore the model. Raise it when the model had the context, clearly tried, and still got it wrong.
  • Verification sets the gate discipline in the spawn payload, and never touches the model. Raise it when the failure was a skipped file, unrun tests, or a refactor abandoned partway.

Breadth, coverage, and blast radius are verification signals. Spending them on a bigger model over-provisions routine work (a 30-file rename is not hard, just wide) and under-provisions small-but-hard work (a one-file bug in an unfamiliar subsystem is the case that actually wants a stronger model).

Mechanical signals come from scripts/classify.mjs. The capability signals are mostly not mechanically detectable and must be reasoned about directly — never invent them from a diff.

When not to use

  • The user already named both the tier and the model for work they want done right now ("use opus for this") — honor that directly instead of re-scoring. Setting the project's standing ladder in .claude/model-routing.json is this skill's job, so that request does belong here.
  • The spec-format decision — task-workflow's full-spec tier fires only on an explicit request, never on perceived complexity. This skill only picks the executing tier once work is about to start (an already-produced full-spec PLAN.md is an automatic architect-tier signal).

Step 1: Gather mechanical signals

Routing happens before work starts, so pass the planned scope — the files you're about to change, from PLAN.md or the user's description:

node ${CLAUDE_SKILL_DIR}/scripts/classify.mjs --paths "path/one.ts,path/two.ts"

Relay the JSON. Fields: scope, filesChanged, touchedFiles, highRiskMatches, testCoverageRatio, plannedNewFiles, fullSpecDetected, routing.

testCoverageRatio is measured over existing, non-test files whose type the script can look a test up for: .js .jsx .ts .tsx .mjs .cjs .vue .go .py .dart .rs (an allowlist). Everything else — docs, config, .env*, .sql, .html, YAML, styles, assets — is left out of the ratio, so a scope with none of those code types reports null ("no code touched"), not 0. New code files in the planned scope (same allowlist) are reported separately as plannedNewFiles and excluded from the ratio; a new doc or config file is in neither — a file with no code in it can't be "untested", and folding it in produced a 0 that read as a risk signal when it only meant "new". A scope that is entirely new files therefore also reports null. Deleted files (in uncommitted/branch scope) stay in touchedFiles and highRiskMatches but are neither planned new files nor counted for coverage. Backslash paths are normalised to /.

With no --paths the script falls back to uncommitted changes, then the branch diff — correct mid-task, wrong at the start. scope tells you which it used:

  • planned / uncommitted / branch — signals are real, use them.
  • none — a clean tree with no planned scope. filesChanged, highRiskMatches, testCoverageRatio, and plannedNewFiles come back null, meaning unknown, not zero. Never score a null as 0 pts — that's what made every fresh task look trivial. Either re-run with --paths, or estimate from the stated scope in Step 2.
  • On an error field (non-git repo, no git on PATH) — same handling as none. routing is still populated with the default profile either way.

Step 2: Assess capability signals (reason directly, no tool)

  • Is there a pattern to follow? An equivalent already in this codebase, something similar needing real adaptation, or nothing — a new pattern/abstraction.
  • Is there a structural judgment call? More than one reasonable way to structure this, where the choice matters, versus one obvious shape.
  • Is the problem understood? Requirements and cause clear, some ambiguity left, or an unfamiliar domain / unknown root cause.

These predict capability. Note what's not here: reversibility and blast radius moved to Step 3b, where they belong.

Step 3: Score capability → tier

Auto-overrides — skip scoring, go straight to Architect:

  • fullSpecDetected is true (PLAN.md carries the [full-spec] marker of task-workflow's full-spec tier — an explicit user signal; FR/NFR citations alone don't count)
  • The change is a breaking contract change, or a data migration that transforms existing rows
  • The root cause is unknown (a bug whose cause you can't yet name, in code or a domain the worker would be learning on the job) — the "problem understood" row at its top value. Small scope doesn't make it safe: this is where a stronger model gets it right and a cheaper one confidently fixes the wrong thing

A non-empty highRiskMatches is not an override. It's a prompt to ask whether the second bullet applies — additive contract changes and version bumps touch the same paths and are ordinary edits. What high-risk paths do change is the verification bar (Step 3b).

Otherwise, score:

Signal0 pts+1+2+3
Pattern to followAn equivalent exists in this codebaseSimilar, needs real adaptationNone — needs a new pattern/abstraction
Structural judgmentOne obvious structureMore than one reasonable structure, and the choice matters
Problem understoodRequirements and cause are clearSome ambiguity to resolveUnfamiliar domain (root cause unknown is an auto-override)
Simultaneous context≤2 files3–9 files10+ files

Total 0-4 → Worker · 5+ → Architect.

Full tables plus five worked examples: references/scoring-rubric.md.

Step 3b: Set the verification bar (independent of tier)

From the mechanical signals — this changes what the payload demands, never which tier or model runs:

TriggerBar
highRiskMatches non-emptyVerifier round mandatory even where task-workflow would skip it; full gate output; revert path in PLAN.md
testCoverageRatio < 0.3, or null with code changesTests first, per write-tests' TDD ordering
plannedNewFiles non-emptyTests first, same ordering — a new module has no coverage by construction, not by neglect
filesChanged ≥ 5Gates across the whole tree, not just touched files
Flaky/timing symptom≥5 consecutive passes (see debug-workflow's race-conditions.md)
None of the aboveNormal gates: lint + typecheck + tests on the touched scope, each run's pass/fail summary line shown

Triggers stack.

Step 3c: Resolve the model and the agent for that tier

The tier decides which rung; the profile decides both the model and the effort on it. Take both from Step 1's routing:

  • Model — routing.models[tier]. Precedence:
    1. On-demand instruction in this request ("run it on fable", "use the frontier ladder here") — this spawn only. Don't edit the project config for a one-off.
    2. routing.models[tier] from Step 1 (resolved from .claude/model-routing.json).
    3. The balanced default, which is what routing already reports when no config exists.
  • Agent — routing.agents[tier], which is the subagent_type for Step 5. Don't derive it from the tier name. Effort can't be passed at spawn time, so a profile that pins a tier at a different effort routes to a variant agent instead: under frontier the worker tier is worker-frontier and the architect tier architect-frontier. Spawning the base agent there would silently run the task at the wrong effort. The verifier resolves to verifier on both profiles today — read it off routing anyway, since that is what changes when a ladder does.

routing.efforts[tier] reports the effort that agent carries; it's informational (state it in Step 4), never something you pass. If a user asks for a different effort level, say so plainly — there is no way to honor it at the call site.

Relay any non-empty routing.warnings to the user — a malformed config degrades silently to the default otherwise, and a config the user thinks is active but isn't is worse than no config. Setting effort in .claude/model-routing.json is one of those warnings: it isn't a supported key.

Profiles, config schema, and the effort rationale per model: references/model-profiles.md.

Step 4: State tier + agent + model/effort + verification bar + rationale

One line: the chosen tier, the agent that will run it, its model and effort (and where they came from if not the default), the verification bar from Step 3b, and the deciding signal(s). Name the agent whenever it isn't the tier's base — "Worker tier → worker-frontier on opus/medium (frontier ladder)" tells the user what they're actually paying for; "Worker tier" alone doesn't. Only ask the user (single yes/no) if the capability score sits exactly on a bucket boundary and the Step 2 signals were ambiguous — don't ask by default, that reintroduces the triage overhead this skill exists to remove.

Step 5: Spawn

Call the Agent tool with subagent_type: bigin-skills:<routing.agents[tier]> and model: <resolved model from Step 3c> — both from Step 3c, and pass model explicitly on every spawn, even when it matches the agent's frontmatter default, so the handoff and the actual run can't disagree. Pass: one-line task scope, PLAN.md path if one exists, the touched-file list, the chosen tier + rationale, the Step 3b verification bar, and — if graphify-out/graph.json exists in the repo — a note of its presence plus a docs/graph-usage.md pointer, so the subagent knows why it was picked, can flag a mismatch, and navigates structurally before grepping. Also pass objective, constraints, and definition-of-done — a fixed payload template, not prose advice, so Step 6 has something concrete to check the return against. The verification bar belongs in definition-of-done, so an unmet bar is a Step 6 gap rather than a footnote.

Exact call shape and payload fields: references/agent-invocation.md.

Step 6: Return evaluation

After the subagent returns, check it against the payload's definition-of-done (contract: references/agent-invocation.md's "Return evaluation contract").

  • Met — proceed to Step 7 (or use the result directly if nothing else applies).
  • Partial/unmet — resume the same subagent (SendMessage to its agent ID, not a fresh Agent call) naming the specific gap against the definition-of-done. Track the cycle count in your own working context — max 2 follow-up cycles (3 dispatches total, including the original spawn).
  • Cycle cap hit:
    • Either tier → surface to the user, with every prior return and the gaps named; don't retry further and don't escalate tiers on this path.
  • Never spawn bigin-skills:verifier from this step — that's a separate, task-workflow-only mechanism scoped to auditing a diff against PLAN.md. This evaluation is deliberately lighter than a verifier round.
  • Never auto-escalate into the architect tier from exhaustion — it is reachable via Step 3's capability score or its auto-overrides (fullSpecDetected, a breaking contract change or row-transforming migration, an unknown root cause), not by exhausting the worker tier. A worker that hits a real architectural decision says so with ROUTING_MISMATCH: (Step 7).

A ROUTING_MISMATCH: reply at any point during this loop short-circuits straight to Step 7 — re-scoring wins over continuing the retry loop, since a wrong tier makes definition-of-done checks meaningless.

Step 7: Handback protocol

If the spawned subagent reports a routing mismatch (a reply starting ROUTING_MISMATCH: — see references/agent-invocation.md), re-score with the new information and respawn the correct tier (re-running Step 3c for the new tier's model). Don't try to change the running subagent's model or effort mid-session — both are fixed once it's spawned, not mutable in place.

References

  • scripts/classify.mjs — mechanical signal gathering (--paths for planned scope) + model-ladder resolution; never outputs a suggested tier.
  • references/model-profiles.md — profiles, .claude/model-routing.json schema, override precedence, effort rationale per model.
  • references/scoring-rubric.md — both axes in full (capability point table, verification-bar triggers) + 5 worked examples.
  • references/agent-invocation.md — Agent tool call shape, payload fields (objective/constraints/definition-of-done), return evaluation contract, handback contract.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.