Skip to content

Routing

Every request is filtered, scored and picked, and every candidate's reasoning is kept so /admin/requests/{id} can show why the losers lost.

The availability signal

Every Anthropic response carries the account's live budget:

anthropic-ratelimit-requests-limit / -remaining / -reset
anthropic-ratelimit-input-tokens-limit / -remaining / -reset
anthropic-ratelimit-output-tokens-limit / -remaining / -reset
retry-after                                            (on 429)

The gateway mirrors these per account on every response, so availability is real-time with no probe traffic. A window whose reset has passed reads as refilled; a window never seen reads as full, which is the right optimism for a first request.

Filtering

An account is eligible if it is healthy, in the key's pool scope, supports the model, is under its spend cap, is below max_concurrency, has a closed circuit breaker, and has projected headroom for the request. Anything else produces a verdict string that travels to the inspector: filtered — cooling, 27s remaining is a complete answer.

Scoring

score =  w_affinity  · is_sticky_owner
       + w_headroom  · min(token_headroom, request_headroom)
       + w_priority  · account_priority
       - w_load      · inflight / max_concurrency
       - w_errors    · recent_error_rate
       - w_cost      · normalized_cost_per_tier

Weights are routing.weights in the config. Ties break on a deterministic hash of the session key, so selection is stable and reproducible under replay.

Strategies

Strategy Behaviour
sticky_headroom Default. Affinity, then most headroom.
headroom sticky_headroom with affinity off. Identical to it whenever a request has no cache owner, so it never routes better — only the same, or worse.
cost_tiered Drain cheap accounts first, spill upward.
priority Strict ordered failover: primary, then backup.
least_loaded Baseline.
round_robin Baseline. Ignores every signal on purpose.

The last three are cache-blind. Read Why is my bill higher? before choosing one.

cost_tiered deserves its own warning. Its cost term is normalised across the eligible pool, so the size of a tier gap is erased: accounts at 1.0 and 1.05 score exactly as far apart as accounts at 1.0 and 5.0. With cost weighted at 0.80 against affinity's 0.20 it will re-home an established conversation over a 5% tier difference, and a cache break costs far more than 5%. sticky_headroom already prefers the cheaper account when placing a new session, which is the part worth having — so set cost_tier and leave the strategy alone unless your tiers differ by more than the cache penalty.

tokenbiryani strategies lists what your install actually has, including plugins.

Leases

Scoring alone is not enough. Without an atomic reservation, N concurrent requests all read the same "plenty of headroom" and stampede one account into a 429.

So the estimated cost is reserved before dispatch and released on response, then reconciled against actual usage. Estimates are deliberately pessimistic (routing.estimate_safety_margin, default 1.15): reserving slightly too much is the safe direction.

Sizing the output half

Input is approximated from the body. Output has no such handle before the request runs — only the caller's max_tokens, which is a ceiling and not a forecast. An agent client sends 32,000 and returns a few hundred tokens.

Reserving the ceiling costs most of the pool's concurrency:

Output window max_tokens Concurrent requests admitted
16,000 8,192 1
64,000 32,000 2

The lease is released afterwards, so no quota is spent on the difference — the cost is paid during the request. An account leased to its ceiling reads as full, an account that reads as full is filtered out of routing, and a conversation whose owner is filtered out gets re-homed onto a credential that has never seen its prefix. An estimation problem becomes a cache break, which is the expensive kind.

So routing.output_estimate: adaptive (the default) keeps a rolling sample of what each model really returns and leases a high quantile of it. Three things bound it:

  • Never above the caller's max_tokens. It can only ever reserve less than the old behaviour, never more.
  • The ceiling until there is evidenceoutput_estimate_min_samples, default 20.
  • The ceiling again if it is being beaten, past output_estimate_max_undershoot. A p95 predictor is outrun about 5% of the time by construction; this catches a distribution that has changed shape.

GET /admin/estimation shows what it believes per model, including whether it is predicting at all. routing.output_estimate: max_tokens restores the old behaviour.

Admission is unaffected. max_tokens larger than an account's entire output window still makes that account unable to serve the request — predicting sizes the lease, it does not overrule a bound the caller stated.

Session affinity

The affinity key is the X-TokenBiryani-Session header when present, otherwise a fingerprint of the request's stable head — system prompt, tool names, and the first two messages. That stays constant as a conversation grows, so no client changes are needed.