Spokes.wiki Search About
Software Source Code T3 stable source
Published
Created
Updated
Sources
claude-code-model-router.md

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 to messages[] 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_control marker 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_tokens of 32k / 64k / 128k; budgeted thinking on Haiku and Sonnet, adaptive thinking on Opus 5.5 (which rejects budget_tokens outright); no effort parameter on Haiku (400: "This model does not support the effort parameter" — see effort-level).
  • Subagent requests carry cc_is_subagent inside the billing-header block of system; 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 -c resumes a conversation on whatever model actually answered the last turn, not the model configured for the session. Routing keys off metadata.user_id.session_id and 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.

claude-code · cache-aware-model-routing · effort-level · agent-orchestration · agentic-coding-harness · tokenmining · claude-apps-gateway