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. If the agent already asked for something cheaper than the routed tier, that stands.
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 silently does nothing.- 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