Claude Code model router
A local HTTP proxy (~400 lines of Python, httpx only) the curator built and runs in front of
claude-code: ANTHROPIC_BASE_URL points Claude Code at http://127.0.0.1:8787, and for every
typed prompt the proxy asks Claude Haiku 4.5 for one word — haiku, sonnet or opus — then
rewrites the request's model field before it reaches api.anthropic.com. The choice holds for
the whole tool loop that follows the prompt. Goal: stretch a subscription's usage limits by not
running routine turns on the most expensive model. Live on every session under ~/projects since
2026-09-24, wired through a .claude/settings.local.json env entry plus a SessionStart hook that
restarts the proxy when it is down.
Tier T3. The design (a proxy rewriting model before the request leaves the box) is a plain
artifact fact, but every number on this page — the routing decisions, the dollar figures, the
policy thresholds — is the author's own measurement on one device, over a few dozen requests, with
no independent check. That is the maker reporting how well the maker's own thing works, which the
tier ladder grades T3 regardless of how carefully it was measured.
The problem the router exists to solve: the prompt cache is per model
Anthropic prices a cache read at 0.1× input and a cache write at 1.25× (5-minute TTL) or 2× (1-hour TTL); Claude Code on a subscription uses the 1-hour TTL. A cache is scoped to one model, so switching models mid-conversation means paying to write the whole context into the new model's cache instead of reading it from the old one's. For a context of C tokens, staying on model X costs 0.1·pX·C to keep reading it; switching to model Y costs w·pY·C to write it fresh. Working the arithmetic through the source's own weights (Haiku 4.5 $1/$5, Sonnet 5 $2/$10, Opus 5.5 $4/$20 per MTok in/out), a downgrade pays for itself on the context alone only when the price ratio between the two models is above roughly 12.5–20×. No pair of current Claude models is that far apart, so a downgrade only pays through the output tokens of the turn and the rest of a long tool loop — never through the context switch itself. See cache-aware-model-routing for the mechanism stated on its own.
The policy
- New session: route by the classifier's answer — there's no cache to lose yet.
- Upgrade: always allowed, on the reasoning that a wrong model choice on a hard prompt costs more in retries than the cache write costs.
- Downgrade: only when the estimated cost of the coming turn on the smaller model — cache write included — comes in at least 15% below staying put. The estimate leans on the session's own running averages (requests per turn, output tokens per request, with priors of 3 and 800), the target model's cache TTL, and what that model's cache already holds: its own earlier context plus the ~44k-token prefix every Claude Code session shares (system prompt and tool definitions).
Under the 1-hour TTL and default priors, an Opus→Haiku downgrade pays off below roughly 25k tokens of context, and Opus→Sonnet almost never pays off at all. In practice this means a session that opens with a simple prompt runs on Haiku from the start, and a session that has already gone deep on Opus mostly stays on Opus — the downgrade rule is conservative by design, not a bug in the estimate.
What building it exposed about Claude Code's own request shape
Reverse-engineered while making the proxy behave, none of it documented by Anthropic as far as the author found:
- Claude Code appends
role: "system"messages tomessages[]for hook context. Both Haiku 4.5 and Sonnet 5 reject that with a 400 (role 'system' is not supported on this model); the proxy folds those messages into the neighbouring user turn as<system-reminder>text. - Claude Code's message-level cache breakpoint sits on the last message, which is often the
system message being folded. The first version of the fold dropped the
cache_controlmarker along with it, and every Haiku/Sonnet request re-sent 6,000–21,000 tokens uncached. Keeping the marker on the fold cut uncached input on a repeat prompt from 6,766 tokens to 10, and its cost from $0.0167 to $0.0081. - Request shape differs by model as Claude Code itself sends it:
max_tokensof 32k / 64k / 128k; budgeted thinking on Haiku and Sonnet, adaptive thinking on Opus 5.5 (which rejectsbudget_tokensoutright); noeffortparameter on Haiku (400: "This model does not support the effort parameter" — see effort-level). - Subagent requests carry
cc_is_subagentinside the billing-header block ofsystem; background calls (session titles) carry no tools. The proxy routes neither class — it only classifies human-typed prompts. - Several "user" turns are written by Claude Code itself, not the human: subagent results
(
<agent-message …>), task notifications, next-prompt suggestions ([SUGGESTION MODE: …), and!/slash-command output (<local-command-caveat>). None of these get classified either. claude -cresumes a conversation on whatever model actually answered the last turn, not the model configured for the session. Routing keys offmetadata.user_id.session_idand reclassifies only the next prompt the human types.
Measurements (router.log, 2026-09-23/24)
| Case | Result |
|---|---|
| Classifier call itself | 112–448 input tokens, ~5 output; about $0.0001–0.0005 per prompt |
| Hard prompt after a Haiku turn | upgraded to Opus; the first Opus request wrote 78,687 tokens to cache ($0.637) |
| "ok thanks, 2+2?" after an Opus turn | stayed on Opus — switching would have cost 20% more |
| A spec-writing turn on Sonnet | 6 requests, $0.196 total; the same turn priced on Opus would run about $0.39 |
| A next-prompt-suggestion request | $0.025–0.03, close to the cost of the answer it follows — it re-reads ~110k tokens of context for about 10 output tokens |
| Source classification (3 samples, this wiki hub) | vLLM → llm-inference-wiki; this router → agentic-tooling-wiki, runner-up llm-providers-wiki; a sourdough recipe → parked. All returned confidence: high |
Per-job subagents as the same idea applied differently
This wiki hub already routes by model per job — a Haiku source-classifier agent picks the spoke, a
Sonnet spoke-ingester agent runs the ingest, /dream dreamers run on Opus (see
../HUB.md → Model per job). The router's author points out that subagents sidestep the whole
cache-switching problem this page describes: each subagent keeps its own context and its own
cache, so there is no mid-conversation model change to pay for, and the router's downgrade logic
never has to run for them. See agent-orchestration and cache-aware-model-routing.
Limits, in the author's own words
- Usage-limit accounting on a subscription is not public; API list prices stand in for it, which may not be how the limit is actually metered.
- The proxy depends on undocumented Claude Code behaviour — the billing-header marker, the
role: "system"message shape, per-model request shape. An unannounced Claude Code update can silently break it. - One process on one phone is a single point of failure for every session run through this hub, Telegram-driven ones included.
Cross-spoke context (llm-providers-wiki — pricing/tier angle, noted here only)
The router's downgrade arithmetic runs entirely on Anthropic's published per-token prices — Haiku 4.5
$1/$5, Sonnet 5 $2/$10, Opus 5.5 $4/$20 (in/out per MTok) — which matches the mid-2026 tier snapshot
already held in ../llm-providers-wiki's llm-api-pricing (Sonnet 5 at $2/$10; Opus 5.5's 20% cut
from Opus 5's $5/$25). It also uses the cache-write multipliers (1.25× at 5-minute TTL, 2× at
1-hour TTL) that page's neighbours (claude-sonnet-5, claude-opus-5-5) record from Anthropic's
own docs. Nothing here changes a number on those pages; it is one more place the same prices turn up,
now as the input to a routing decision rather than a market comparison.
Related
claude-code · cache-aware-model-routing · effort-level · agent-orchestration · agentic-coding-harness · tokenmining · claude-apps-gateway