Skip to content

feat(provider-switch): switch agents off an exhausted provider without a source-side summarization turn #821

Closed

Description

@Juliusolsson05

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 compacted payload 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_uses context editing and OpenAI's own externalAgentConfig/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

  1. 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 /compact path remains as an explicit opt-in that is disabled when the source is exhausted.
  2. 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.
  3. Exhaustion is read structurally from the usage snapshot (Claude session/weekly rows, Codex primary/secondary windows) and from live signals (Claude rate_limit records, Codex usage_limit_reached via 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.
  4. Honest labeling. Each switched agent reports its strategy (native, raw, shrunk) in the batch summary and pane toast.
  5. 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.
  6. 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 compacted records 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.md updated: capacity outcomes gain raw-history and shrunk, 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

  1. Juliusolsson05 commented on Sep 7, 2026

    @Juliusolsson05
    OwnerAuthor

    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 #821 quietly imply they were met, they are tracked in #833:

    • "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." — the modal's defaults and the disabled source-compaction checkbox are implemented and unit-tested against the exhaustion signal, but nobody has run a 10-agent batch against a genuinely exhausted window and timed it.
    • "Typecheck, unit, system, renderer suites green; opt-in live probe run once and its report attached to the PR." — the suites are green and reported on the PR; the probe was not run, so there is no report to attach.

    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: false transaction, 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, and docs/design/provider-switching.md is updated for the new outcomes, the opt-in source mutation and arrival compaction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions