feat: subscription-aware routing (discount covered models by observed quota headroom)#495
Conversation
…observed quota headroom
When a caller presents a subscription (ChatGPT/Codex or Claude Pro/Max), bias
routing toward the models that subscription already pays for, priced by the
actual remaining rate-limit headroom rather than full catalog cost. These quotas
are perishable (reset every window), so unused headroom has zero salvage value —
the marginal cost of a covered turn is ~0 while the window has slack and rises to
full price only as it binds (use-it-or-lose-it / bid-price control).
- internal/proxy/usage: new pure observer. Parses Codex x-codex-{primary,secondary}-*
and Anthropic anthropic-ratelimit-unified-{5h,weekly}-* headers into per-credential
window state; CostFactor maps utilization u to epsilon+(1-epsilon)*u^gamma — ~epsilon
when slack, →1 (full price) as the window binds. In-memory, TTL-evicted, salted keys.
- providers: context-carried UpstreamHeaderObserver (no Client-interface change);
anthropic + openai clients surface resp.Header after every upstream call.
- proxy: records headroom keyed by the resolved subscription credential; pre-routing,
computes per-covered-model cost factors (Codex→headline GPT set; Claude→catalog
anthropic models) and sets them on router.Request.
- scorer: blendScoresV2 multiplies a covered model's cost term by its factor.
- Off by default behind ROUTER_SUBSCRIPTION_AWARE_ROUTING (+ _COST_EPSILON/_GAMMA,
_OBSERVATION_TTL). Cold start = full price until the first response is observed.
Tested: usage parsing + shadow-price monotonicity (slack→epsilon, binding→full,
tighter window governs) + observer TTL; scorer no-op guard; full suite green.
Validation gate before enabling: offline screen on cost-weighted prod traffic.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
… symmetry - parsePercent always divides 0-100 percent headers by 100 (+ clamps), instead of a magnitude heuristic that misread the boundary: "1" (1% used, max headroom) was left as 1.0 (=100%, full price), silently wiping the subsidy at the freshest moment. Both backends document these as 0-100; the remaining/limit path already computes a fraction directly and bypasses this. (greptile P1) - withUsageObserver now gates the Codex recording on codexSubscriptionFromContext (token + account-id), mirroring subsidyFactors exactly — no more recording observations under a key the read side never consumes. (greptile P2) - Add regression tests for the 1% case + >100 clamp. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
costs (and the cMin/cMax cost normalization) range over the full deployed model set, but only eligibleModels are scored. Discounting an EXCLUDED covered model (context overflow, pin max-out, operator filter) to ~epsilon could make it the artificial cost-normalization floor, weakening the cost axis for models that can actually win. Gate the discount on membership in eligibleModels — an ineligible model can't be chosen, so subsidizing it serves no purpose. (cursor) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The session planner compared pin-vs-fresh on full catalog pricing, so a session pinned to a cheap model would never switch to a now-near-free subscription model — the discount only applied to the fresh decision and was inert on sticky sessions after turn 1 (cursor). Thread the per-model subsidy factor into planner.Inputs and scale both sides' effective price in the EV math (Input/Output scaled uniformly; CacheReadMultiplier is a ratio and stays correct). nil factor map = today's behavior. Added a deterministic flip test (cheap pin → subsidized covered model switches). Also document that the legacy v1 scoring path has no cost axis, so the subsidy is V2-only by construction (all deployed bundles run V2). (cursor, low) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on headroom - Header observer is now context-aware and records only responses served on the caller's subscription, keyed by the call's RESOLVED credential. The handover summarizer (runs on the same request after clearCredentials, on a deployment key) no longer poisons the user's headroom snapshot. (cursor) - Subsidized requests are cache-ineligible: the semantic-cache key doesn't capture headroom-dependent model choice, so a hit could return a body from a model chosen under different headroom. subsidyFactors early-returns nil when the feature is off, so OFF deployments are unaffected. (cursor) - gofmt the planner test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… epsilon=0 - usage.Observer.Record now merges per-window with the prior (non-stale) observation: a response reporting only one rate-limit window no longer erases the other window's last-known utilization (which would make CostFactor look slack and over-discount until TTL). A genuinely reset window reports used≈0 (present) and still overwrites; only an omitted window is preserved. (cursor) - planner applySubsidy keys on map MEMBERSHIP, not factor sign, so a legitimate 0.0 factor (ROUTER_SUBSCRIPTION_COST_EPSILON=0) discounts in the EV math just as the scorer multiplies the cost term by it — no stay/switch vs fresh disagreement. (cursor) - Tests for both. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f4d5f7a. Configure here.

What
When a caller presents a subscription (ChatGPT/Codex or Claude Pro/Max), bias routing toward the models that subscription already pays for — priced by the actual remaining rate-limit headroom, not full catalog cost. Today the scorer prices every model at full catalog cost and the sub only changes billing after a model wins, so a pre-paid GPT turn still loses to cheap OSS on the cost axis (e.g. a real opencode turn scored
deepseek-v4-pro 1.67vsgpt-5.5 0.89and routed to deepseek).These quotas are perishable (reset every window), so unused headroom has zero salvage value: the marginal cost of a covered turn is ~0 while the window has slack and rises to full price only as it binds — use-it-or-lose-it / bid-price control. (Design doc:
router-internal/docs/plans/SUBSCRIPTION_AWARE_ROUTING.md.)How
internal/proxy/usage(new, pure/inner-ring): parses the quota headroom both backends already report on every response — Codexx-codex-{primary,secondary}-*and Anthropicanthropic-ratelimit-unified-{5h,weekly}-*— into per-credential window state.CostFactormaps utilizationu→ε + (1−ε)·u^γ: ~ε when the window has slack (covered model ~free), →1 (full catalog price, no subsidy) as it binds. The tighter of the two windows governs. In-memory, TTL-evicted, salted credential keys.providers: a context-carriedUpstreamHeaderObserver(noClient-interface change); the anthropic + openai clients surfaceresp.Headerafter every upstream call (including 429s, where headroom matters most).proxy: records headroom keyed by the resolved subscription credential; pre-routing computes per-covered-model cost factors (Codex → conservative headline GPT set the backend bills; Claude → catalog Anthropic models) and sets them onrouter.Request.blendScoresV2multiplies a covered model's cost term by its factor — so it rides the same per-cluster α/λ blend as the base cost.Safety / rollout
ROUTER_SUBSCRIPTION_AWARE_ROUTING(+_COST_EPSILONdefault 0.05,_COST_GAMMAdefault 2,_OBSERVATION_TTLdefault 10m). Feature-off path is byte-identical (no observer installed, no factors computed).Testing
usage: header parsing (both families), shadow-price monotonicity (slack→ε, binding→full price, tighter window governs), observer record/TTL/keying.go build ./...+ fullgo test ./...green.Follow-ups (documented, out of scope)
u^γ, which already encodes the perishability bias).X-Weave-*-Subscriptionheaders — the opencode dual-sub path).🤖 Generated with Claude Code