Right-size Claude Code's subagent spawns. A PreToolUse hook on the Agent
tool asks Nadir's free decision API which model the task
actually needs, then rewrites model in the spawn's input.
Nadir is a decision engine here, not a gateway. Your prompts and completions go straight from Claude Code to Anthropic on your own auth; Nadir is consulted out of band with the spawn's task text and never sees the request, the response, or a provider key. There is no added latency on the token stream.
claude plugin marketplace add https://getnadir.com/marketplace.json
claude plugin install nadir-route@nadirNo key required. Add one to attribute decisions to your account and get a savings figure on the dashboard (see below).
| Nadir's bucket | the hook writes | effect |
|---|---|---|
simple |
haiku |
your cheapest model |
medium |
sonnet |
your default working model |
complex |
nothing | the spawn keeps the model the session already runs |
Three rules, all deliberate:
complexis never rewritten. The top tier stays on the model you chose, including which Opus generation. Nadir right-sizes the cheap end; it does not move you off your frontier model.- It only writes harness aliases.
Agent'smodelparameter is an enum (haiku,sonnet,opus,fable), and Claude Code converts a schema-invalid hook rewrite into a denied tool call. Aliases also resolve through your ownANTHROPIC_DEFAULT_*_MODELconfig, so a routed spawn lands in your family and your generation rather than one this plugin hardcoded. - It never moves a spawn up on its own. If the agent already asked for
something cheaper than the routed tier, that stands. The one exception is an
explicit policy pin from your account, which is a standing instruction from
you and so wins in either direction. A pin naming a full model id
(
claude-sonnet-5) cannot be expressed on the alias enum at all, so it falls through to the tier ladder rather than doing nothing.
Your main thread is never touched, by construction: the hook fires only on spawns.
Measured end to end on Claude Code 2.1.220 — a trivial read bucketed medium
and the subagent started on claude-sonnet-5 (from an Opus inherit); a
multi-region failover design bucketed complex, the hook emitted nothing, and
the subagent ran on claude-opus-5.
Set these in ~/.claude/settings.json under env, or export them.
| Var | Default | Meaning |
|---|---|---|
NADIR_ROUTE_DISABLE |
unset | 1 turns the hook off |
NADIR_BASELINE_MODEL |
unset | the model your sessions run on, e.g. claude-opus-5. Set this for a savings figure: Claude Code fills tool_input.model only when a spawn names one explicitly, so without it Nadir has no baseline to price against and decisions log unpriced |
NADIR_API_KEY |
unset | attributes decisions to your account and surfaces them on the dashboard. Keyless calls store no row at all, by design, so your dashboard stays empty |
NADIR_CLAUDE_LADDER |
{"simple":"haiku","medium":"sonnet"} |
retune the table above. Map a tier to inherit to leave it alone; {"simple":"inherit","medium":"inherit"} is audit mode — decisions recorded, nothing changed |
NADIR_AGENT_POLICY |
{"subagent":"auto"} keyless |
raw JSON role policy. Pin a value ({"subagent":"haiku"}) instead of letting the router pick |
NADIR_TIMEOUT |
5 |
seconds the decision call may take. Do not lower to 2: a 10-way parallel fan-out measures 1.9–2.3s per call, so a 2s cap loses most decisions in exactly the traffic this is for |
NADIR_BUCKET_URL |
https://api.getnadir.com/v1/bucket |
endpoint |
Requires python3 on PATH for JSON handling. Without it every path exits
silently and spawns proceed unrouted.
Every failure path exits 0 with no stdout — disabled, empty prompt, unparseable input, bad policy JSON, network error, timeout, non-200, missing decision field, a model equal to the one already requested, or a rejected key. Claude Code reads that as "no decision" and the spawn proceeds exactly as the agent intended. Only exit code 2 blocks a tool call, and this script cannot produce one.
The cost of that design is that "working" and "doing nothing" look identical, so check explicitly rather than assuming:
echo '{"tool_name":"Agent","tool_input":{"prompt":"rename a variable in one file","description":"rename var","subagent_type":"Explore"}}' \
| sh ~/.claude/plugins/*/nadir-route/scripts/route-spawn.shExpect JSON containing "model":"haiku". Empty output means it is not routing.
An invalid NADIR_API_KEY produces exactly the same silence as an unreachable
network, so if you are keyed, re-run the same check with NADIR_API_KEY= — if it
starts working, your key is being rejected.
Inside a session, Claude Code's PostToolUse tool_response.resolvedModel
names the model a subagent actually started on.
CLAUDE_CODE_SUBAGENT_MODEL, if set to anything butinherit, outranks the per-invocation model this hook writes. The hook now detects this and exits before calling the API, so it spends no decision — and books no savings — on a rewrite that is guaranteed to be discarded.- The hook does beat a subagent's frontmatter
model, includingmodel: inherit. - This is a nudge, not a control. Claude Code's
Agentpermission rules match the agent type, not the model, so there is no local way to enforce a ceiling.
One command, no plugin, same hook — it merges into settings.json, backs it up,
and verifies routing is live before it exits:
curl -fsSL https://getnadir.com/install/claude-code.sh | shFor tiering work that is not a subagent spawn (batch items, "which model should
this use", delegate-vs-inline cost), the full skill documents the whole
/v1/bucket contract: npx skills add https://getnadir.com
Mirrored from integrations/agent-hooks/claude-code in the Nadir monorepo,
which is the source of truth. Issues: https://getnadir.com