Skip to content

Add provider handoff compaction on model switches#1911

Open
juliusmarminge wants to merge 2 commits intomainfrom
t3code/provider-switch-compaction
Open

Add provider handoff compaction on model switches#1911
juliusmarminge wants to merge 2 commits intomainfrom
t3code/provider-switch-compaction

Conversation

@juliusmarminge
Copy link
Copy Markdown
Member

@juliusmarminge juliusmarminge commented Apr 11, 2026

Summary

  • Add provider switch handoff flow so mid-thread model changes compact the active thread, restart the target provider, and prepend a handoff summary to the next user turn.
  • Implement compaction support in both Codex and Claude provider adapters, plus server-side session lifecycle handling for thread/compact/start.
  • Tighten runtime ingestion and session bookkeeping so events from a replaced provider session do not leak into the active thread state.
  • Update web chat/session logic to preserve draft behavior and render handoff context cleanly.
  • Add coverage for provider switching, compaction, runtime ingestion, and UI-facing state transitions.

Testing

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test
  • Added/updated tests for provider adapter compaction, provider command handoff, runtime ingestion guards, and chat/session draft behavior.

Note

Medium Risk
Changes provider/session lifecycle by allowing mid-thread provider switching with compaction and handoff, which can impact routing, state correctness, and event ingestion across concurrent sessions.

Overview
Enables mid-thread provider switching by compacting the active provider thread, starting a new session on the target provider, and prepending a <provider_handoff> summary to the next user turn.

Adds a compactThread capability across adapters and ProviderService, plus stopSessionForProvider to cleanly terminate the replaced provider session without breaking the new active binding; updates runtime ingestion guards to ignore lifecycle/error events from non-active providers.

Updates web chat logic to keep provider/model selection consistent during handoff and render provider.handoff.* activities as dedicated transition banners, with expanded test coverage for the new handoff/compaction flow.

Reviewed by Cursor Bugbot for commit 84b54f7. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add provider handoff compaction when switching AI providers mid-thread

  • When a thread switches providers mid-conversation, the orchestration layer compacts the current thread context via the source provider, starts a new session for the target provider, and sends the first turn with a <provider_handoff> preamble containing the compaction summary.
  • Both Claude and Codex adapters gain a compactThread operation: Claude enqueues /compact and awaits a PostCompact hook callback (60s timeout); Codex triggers thread/compact/start and waits for a thread/compacted notification.
  • ProviderService gains compactThread and stopSessionForProvider methods to support provider-specific session teardown without disturbing other active bindings.
  • Provider handoff progress is emitted as provider.handoff.compacting and provider.handoff.completed activities, surfaced in the work log and rendered in the chat UI as a provider-to-provider transition pill with animated state indicators.
  • deriveEffectiveComposerModelState is fixed to ignore model selections from a different provider than the currently selected one, preventing cross-provider model bleedthrough.
  • Behavioral Change: lifecycle events from providers that don't match the thread's active session provider are now silently ignored under STRICT_PROVIDER_LIFECYCLE_GUARD.

Macroscope summarized 84b54f7.

- Compact the active thread before switching providers
- Preserve context with a handoff summary in the next turn
- Update orchestration and adapter tests for compaction
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 11, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ddcccd1a-acdd-46c7-b21b-68bf95362e3d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/provider-switch-compaction

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Apr 11, 2026
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Unguarded cache clear on mismatched provider session exit
    • Added shouldApplyThreadLifecycle guard to the clearTurnStateForSession call so it only runs when the exiting provider matches the active session, preventing a replaced provider's exit from wiping the new provider's turn caches.
  • ✅ Fixed: Broad looksLikeCompactionItem matches any item with summary
    • Removed the overly broad typeof record.summary === "string" fallback from looksLikeCompactionItem, keeping only the specific compact_summary and compactSummary checks that reliably identify compaction items.

Create PR

Or push these changes by commenting:

@cursor push d4c9511f80
Preview (d4c9511f80)
diff --git a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
--- a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
+++ b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
@@ -1147,7 +1147,7 @@
       }
     }
 
-    if (event.type === "session.exited") {
+    if (event.type === "session.exited" && shouldApplyThreadLifecycle) {
       yield* clearTurnStateForSession(thread.id);
     }
 

diff --git a/apps/server/src/provider/handoffSummary.ts b/apps/server/src/provider/handoffSummary.ts
--- a/apps/server/src/provider/handoffSummary.ts
+++ b/apps/server/src/provider/handoffSummary.ts
@@ -72,11 +72,7 @@
   if (COMPACTION_TYPE_SNIPPETS.some((snippet) => type.includes(snippet))) {
     return true;
   }
-  return (
-    typeof record.compact_summary === "string" ||
-    typeof record.compactSummary === "string" ||
-    typeof record.summary === "string"
-  );
+  return typeof record.compact_summary === "string" || typeof record.compactSummary === "string";
 }
 
 export function extractCompactionSummaryFromSnapshot(snapshot: {

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 84b54f7. Configure here.


const shouldApplyRuntimeError = !STRICT_PROVIDER_LIFECYCLE_GUARD
? true
: activeTurnId === null || eventTurnId === undefined || sameId(activeTurnId, eventTurnId);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unguarded cache clear on mismatched provider session exit

High Severity

After a provider switch, the old provider's session.exited event is correctly blocked from updating thread session state by the new guard at lines 895–904 (via shouldApplyThreadLifecycle). However, clearTurnStateForSession at line 1150 runs unconditionally for any session.exited event, regardless of whether the event's provider matches the active session. When the replaced provider exits, this wipes the turn message/proposed-plan caches that may already belong to the new provider's in-progress turn.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 84b54f7. Configure here.

typeof record.compactSummary === "string" ||
typeof record.summary === "string"
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Broad looksLikeCompactionItem matches any item with summary

Medium Severity

looksLikeCompactionItem returns true for any object that has a summary property of type string (line 78). Since many regular thread items (e.g. tool results, assistant messages) commonly carry a summary field, this heuristic is overly broad and can misidentify non-compaction items as compaction items, extracting incorrect text as the handoff summary.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 84b54f7. Configure here.

@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp bot commented Apr 11, 2026

Approvability

Verdict: Needs human review

This PR introduces a new provider handoff feature with significant runtime behavior changes across orchestration, provider adapters, and UI. Two unresolved review comments identify potential bugs: a high-severity cache clearing issue during provider switches and a medium-severity heuristic issue in compaction summary extraction.

You can customize Macroscope's approvability policy. Learn more.

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

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant