Repository navigation
[Feature]: Quota-aware model routing and handoff at the T3 layer #26838
Description
Problem
T3 Code already has most of the pieces to make small subscriptions feel much bigger, but it does not connect them.
It can read Claude's 5-hour and weekly windows, Codex's 5-hour and weekly windows, and OpenCode Go's rolling, weekly, and monthly allowances. It can switch a thread to another provider and build a real context handoff. It can resume a usage-limited thread when the reported reset arrives.
What it cannot do is decide that the current subscription is about to run out and continue the work on another subscription the user already pays for.
Today the behaviour is:
- New threads fall back to one configured fallback model when the default provider is already at 100% usage (#26164). Running threads keep their model.
- A running thread that hits a usage limit stops and, if enabled, waits until the provider-reported reset (
UsageLimitRecoveryWorker.ts). It does not continue on another account or provider. - Switching providers works, with a handoff, but only when a human selects it (
provider.switch,ProviderSessionTransitionPolicy.ts). - Limits are pooled for display in Usage → Limits, but request routing is still per-thread/provider-instance. T3 knows Codex has headroom while Claude is at 98%; it just does nothing with that information.
This is not a request for a LiteLLM/OpenRouter-style proxy. A proxy switches HTTP requests. It cannot see T3's turn boundaries, worktree, checkpoints, approvals, or native provider session. It would switch mid-tool-call and leave the next model with a half-applied edit. The routing and handoff belong in T3's orchestration layer, because that is the only layer that knows what "continue this work" means.
Who this hurts
People on small plans.
A $20 Claude plan and a $20 ChatGPT plan are already a real monthly cost in many countries. Add a $10 OpenCode Go plan and a small prepaid API credit, and you have a stack that should be able to keep working all day. Instead:
- one long debugging turn can consume the Claude 5-hour window;
- the thread stops and auto-resume offers to wake it in up to five hours;
- the ChatGPT, OpenCode Go, and prepaid API capacity sits idle;
- starting a new thread may use the fallback, but the work in progress cannot move;
- switching by hand means noticing the limit, picking a model, and paying the handoff cost yourself;
- a reset at 2 AM is not a reset for the person who is working.
This is especially painful because T3's Claude sessions currently appear to drain the 5-hour window faster than the Claude Code CLI (about 1.7x in #24649; fix proposed in #25597). Even after that is fixed, the structural problem remains: one quota is not the same as all quotas.
For people who cannot buy Max, multiple Claude accounts, or a pile of API credit, this is the difference between T3 being usable for a day of work and T3 being a toy that stops at the worst moment.
What a T3-level solution looks like
The goal: when the current subscription is exhausted or about to be, continue the thread on the best available subscription automatically, with a T3 handoff, and show the user what happened.
Not a hidden router. Not an extra model call by default. Not credential pooling. A policy inside Orchestrator V2 that uses the usage data T3 already collects and the switch/handoff machinery T3 already has.
Track 1 — Quota-aware continuation (MVP)
On a usage_limit failure, before arming reset recovery:
- Read the thread's configured fallback chain (an ordered list of
ModelSelections, not one model). - Skip the exhausted provider/account and any account that shares the same credential fingerprint.
- Pick the first candidate that is enabled, installed, authenticated, available, has a fresh observed usage reading, and has headroom in every window that applies to the selected model.
- Dispatch
provider.switchand a server continuation message ("Continue where you left off"). - Reuse
ContextHandoffServiceV2; it already creates the handoff when the provider instance changes. - If no candidate is usable, fall back to today's reset recovery.
- Show a banner: "Claude hit its 5-hour limit. Continued on Codex GPT-6.1 Sol." with a one-click "switch back".
This is the smallest useful change and it works for Codex and OpenCode immediately. For Claude there is an extra wrinkle below.
Track 2 — Predictive guard (the part that actually helps Claude)
T3's own docs note that Claude Code can hold a turn until the 5-hour window reopens. A post-failure worker may therefore fire too late.
Add two earlier triggers:
- Before a turn: estimate whether the selected model has room for a typical turn. Use recent burn rate — the delta in
usedPercentover the last few turns — not just the absolute percentage. If the last three turns each added 7% and the window is at 84%, switch now. - Mid-turn: when a
rate_limit_eventsays 100% and the reset is not imminent, interrupt the turn and let the continuation policy take over. If the reset is a few minutes away, waiting is cheaper than a handoff.
Keep the post-failure path as the safety net.
Track 3 — Candidate intelligence
The chain should not be a blind round-robin. Ordering:
- Same model on another account of the same provider (native resume where the continuation key matches).
- Same-tier peer (for example Claude Opus/Sonnet ↔ Codex GPT-6.1 Sol), using a user-defined order.
- OpenCode Go model with monthly/weekly headroom.
- Paid API (Fireworks, OpenRouter, etc.) only when the user opts in and a spend cap allows it.
Guardrails:
- never switch on stale or missing usage data; wait for reset instead;
- count a hub account and a direct account once via
credentialFingerprint; - never switch mid-tool-call; only at a turn boundary or after an explicit abort;
- cap automatic switches (for example 3 per thread per day) and emit an audit event;
- do not switch back automatically after a reset; let the user or the next limit decide;
- reserve headroom for interactive work: background and delegated tasks should not consume the last 10–20% of a subscription;
- prefer waiting when the session reset is minutes away; prefer switching when the weekly window is nearly gone or the reset is hours away.
Track 4 — Quota-aware delegation
The same problem exists one level up. An orchestrator thread can spawn child threads with delegate_task, but it cannot see which account has room. #24412 adds a read-only provider_usage MCP tool; that is the right direction. A quota-aware launch admission should then:
- prefer the same model on a healthy account;
- otherwise use the approved fallback/chain;
- otherwise wait durably for capacity instead of projecting a failed child;
- keep the original task identity through waiting and retries.
#22281 explored this and was closed for missing prior maintainer approval, not because the problem was solved. It is worth re-opening as an approved, smaller track.
Track 5 — Handoff quality
T3's portable handoff is a budgeted selection, not an agent-written summary (docs). It includes recent user/assistant messages, commands, file changes, checkpoints, and references for omitted history, and the incoming agent can read the thread for more. That is a good base.
Improvements that matter for quota failover:
- raise
T3CODE_CONTEXT_HANDOFF_TOKEN_CAPguidance for coding threads (16k default is small; 32k–64k is available); - include the failed turn's partial output and the current worktree/checkpoint state explicitly;
- optionally generate a short deterministic "state of work" note (files touched, commands run, failing tests, next step) from existing turn items, without an extra model call;
- never drop the user's next request to make history fit;
- keep the switch visible and reversible.
Acceptance criteria
- A thread that hits a usage limit continues on the next configured candidate without user action when fresh headroom exists.
- No switch happens on stale, missing, or ambiguous usage data.
- No switch happens mid-tool-call; a running turn is interrupted or allowed to finish first.
- A hub account and its direct equivalent are never treated as two independent quotas.
- Automatic switches are capped and auditable; the user can see and undo the last switch.
- If no candidate exists, the existing reset recovery still works.
- The feature is off by default, or at least explicit about spending money on paid API candidates.
- Works for Claude Pro, ChatGPT Plus, OpenCode Go, and a prepaid API provider without a proxy.
Non-goals
- Building a general-purpose model router.
- Pooling or sharing credentials across people.
- Bypassing provider quotas or hiding overage spend.
- Switching models inside a tool call or replaying a partially applied edit.
- Replacing T3's handoff with an opaque summary generated by a third-party service.
Implementation notes
The pieces are close to each other:
packages/shared/src/usageLimits.ts— add a pureselectQuotaCandidatewith tests; it already hasexhaustedUsageWindowandresolveNewThreadModelSelection.apps/server/src/orchestration-v2/UsageLimitRecoveryWorker.ts— extend the sweep, or add a sibling worker registered inruntimeLayer.ts.apps/server/src/orchestration-v2/ProviderSwitchService.ts— reuse the existing switch/handoff path.packages/contracts/src/settings.ts— add an ordered chain and thresholds; keepfallbackModelSelectionfor new threads.apps/web/src/components/settings/SettingsPanels.tsx— expose the chain and the enable/disable switch; reuse the existing Usage limits section.- Tests:
packages/shared/src/usageLimits.test.ts, a worker test, and a ProviderSwitch integration replay.
This can be split cleanly: Track 3's pure candidate selector is a small, testable PR on its own. Track 1 is the server worker. Track 2 touches adapter events. Track 4 is contracts/MCP. Track 5 is docs and handoff content.
A plea to people with tokens to spare
I know maintainer review capacity is the bottleneck, and I know this is a feature request, not a bug report. I am filing it here because the problem is real and because the Ideas discussion alone has not turned into a policy.
If you have spare implementation tokens, a free afternoon, or a provider plan you can test against, please pick up one track. Even the pure candidate selector plus tests would unblock the rest. If you are a maintainer and the direction is acceptable, please say so in an Ideas discussion so contributors can work without guessing.
I can test on macOS against Claude Pro, ChatGPT Plus, OpenCode Go, and a prepaid Fireworks credit, and I can provide logs, repros, and verification for the Claude 5-hour hold case. I am not asking for free quota or a bypass; I am asking T3 to use the subscriptions I already pay for before telling me to come back in five hours.
Small subscriptions are not a lesser way to use T3. For a lot of us they are the only way.
Activity