Repository navigation
Description
Motivation
The bulk provider switch (#360) exists for one moment: a provider's subscription window is exhausted and every agent on it is stuck. Today the switch transaction (docs/design/provider-switching.md) still asks the source provider for a live turn whenever the history does not fit the target, or whenever a Codex session has ever compacted:
| Direction | Live source turn required today | Frequency on this machine |
|---|---|---|
| Codex → Claude, session ever compacted | Always (encrypted summary → plaintext handoff turn) | 146 of the last 300 rollouts contain compacted |
| Codex → Claude, oversized, never compacted | /compact + handoff turn |
sometimes |
| Claude → Codex over Codex's budget | /compact on Claude |
frequent with a 1M Claude model (budget ≈ 232k tokens on gpt-6-astra) |
| OpenCode → any, oversized | one handoff turn | sometimes |
When the source is at its limit that turn cannot happen: Claude Code does not retry a subscriber 429 and persists a rate_limit error record; Codex returns usage_limit_reached immediately. The switch then waits its full 300 s timeout per agent, shows one native confirmation dialog per agent, and the agents that most need to move are the ones that cannot.
Source facts that make a fix possible (verified against the vendored Claude Code and Codex sources and real local transcripts):
- Codex keeps every pre-compaction record on disk in plaintext. The
compactedpayload holds the user's prompts in plaintext plus one encrypted summary item. Codex never decrypts that item locally; it is sent back to OpenAI verbatim. There is no client-side logic to reproduce. - Claude's compaction summary is plaintext and already portable.
- Both providers absorb overflow natively at the target with their own quota: Codex runs pre-turn auto-compaction from the seeded token count (fatal only if the input exceeds the window), Claude auto-compacts before the first turn and offers "Resume from summary" for large stale sessions.
- Deterministic, model-free shrinking is the blessed approach on both sides: Anthropic's
clear_tool_usescontext editing and OpenAI's ownexternalAgentConfig/import(which narrates every tool call into a note and truncates results to 4,000 characters; it is Claude → Codex only and does no size handling, so it is an oracle for our projector, not a replacement).
Intended behavior
- No switch requires the source provider. Default policy
allowSourceTurns: false. Codex → Claude carries the raw history with encrypted markers dropped. Claude → Codex uses Claude's plaintext summaries plus raw tail, then a deterministic shrink ladder (clear old tool results, then drop oldest complete turns with an index of dropped prompts) so the projection stays under the target's auto-compact threshold. The existing source-side/compactpath remains as an explicit opt-in that is disabled when the source is exhausted. - Compact on arrival for Codex → Claude: after the target pane is live, run Claude's native
/compact(or answer Claude's own resume dialog with "Resume from summary") using Claude's quota. Per-batch checkbox, default on above a size threshold; off keeps the full raw history. - Exhaustion is read structurally from the usage snapshot (Claude session/weekly rows, Codex primary/secondary windows) and from live signals (Claude
rate_limitrecords, Codexusage_limit_reachedvia codex-headless#46). The bulk modal shows the state and reset time, defaults the direction, and offers "switch model instead" when the hit limit is family-scoped (Opus/Sonnet/Fable) rather than all-models. - Honest labeling. Each switched agent reports its strategy (
native,raw,shrunk) in the batch summary and pane toast. - One confirmation per batch instead of one native dialog per agent; agents idle because of a limit error are switchable even if their pane still shows the provider's wait banner.
- Hazards fixed in the same PR: bug(provider-switch): compaction wait may accept a Claude rate-limit message as the portable summary #820 (rate-limit text accepted as a compaction summary), codex-headless#46 (classification), and rate-limit error records are no longer projected into the target as assistant text.
Acceptance criteria
- A Codex session with one and with several
compactedrecords switches to Claude with no Codex process activity beyond wake, and the resulting Claude transcript resumes and answers a read-only marker prompt in the live probe. - A Claude session larger than the Codex budget switches to Codex with no Claude turn; the projected rollout is under Codex's auto-compact limit; Codex resumes and answers the marker prompt.
- With the source provider's usage row at 100 percent, the bulk modal defaults to that source, disables source-side compaction, and a 10-agent batch completes without any 300 s wait.
- Parser tests run against redacted real fixtures (agent-transcript-parser#24); no invented transcript literals.
docs/design/provider-switching.mdupdated: capacity outcomes gainraw-historyandshrunk, source mutation becomes opt-in, arrival compaction documented.- Typecheck, unit, system, renderer suites green; opt-in live probe run once and its report attached to the PR.
Refs #360, #720, #756, #820, Juliusolsson05/agent-transcript-parser#24, Juliusolsson05/codex-headless#46.
Amended 2026-09-07
The implementation PR opened without the Stage 7 live probe, by the user's decision. Two acceptance criteria above depend on that run and are not met by this PR — they now live in #833 (test(provider-switch): run the live probe for quota-independent switching), together with the four Unknowns the probe settles (2, 3, 6, 7) and the shrink ladder's two placeholder thresholds:
- "With the source provider's usage row at 100 percent, … a 10-agent batch completes without any 300 s wait."
- "… opt-in live probe run once and its report attached to the PR."
Nothing above is deleted; read those two bullets as deferred to #833.
Activity
Juliusolsson05 commentedon Sep 7, 2026
The live-probe and batch-timing acceptance criteria move to #833.
The implementation PR for this issue opens today without the Stage 7 live probe, by the user's explicit decision on 2026-09-07. Two of the acceptance criteria above can only be met by that run, so rather than let the PR's
Fixes #821quietly imply they were met, they are tracked in #833:The same run is what closes Unknowns 2, 3, 6 and 7 (characters-per-token accuracy against Codex's auto-compact limit; whether a live Codex tolerates cleared tool outputs semantically; whether a 1M Claude target refuses arrival compaction on this account; OpenCode import limits for shrunk envelopes) and what would replace the shrink ladder's two placeholder thresholds (
keepRecentTurns: 3,maxInputChars: 8_000) with measurements. #833 carries all of that.Everything else on the acceptance list is implemented and verified structurally: the default
allowSourceTurns: falsetransaction, the shrink ladder, arrival compaction, the structural exhaustion signal and the bulk modal, honest per-agent strategy labels, one confirmation per batch, and the #820 / codex-headless#46 hazard fixes. The parser tests run against redacted real fixtures with no invented transcript literals, anddocs/design/provider-switching.mdis updated for the new outcomes, the opt-in source mutation and arrival compaction.