feat(orchestrator): move a running thread between devices - #5544

Draft
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff
Draft

feat(orchestrator): move a running thread between devices#5544
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff

Conversation

@Bil0000

@Bil0000Bil0000 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Closes#5363.

Stacked on #2829 (t3code/codex-turn-mapping). main and the orchestrator branch share no common ancestor; every primitive this builds on exists only there.

What this does

Run on picks the machine a thread starts on and locks once it has a message — a thread is born on one device and dies there. This makes that reversible: pick another connected environment from the same control, and the conversation, the provider continuation, and the git working state move there. Pull it back later from the same control, which shows where the thread originally ran.

Verified live: real threads moved repeatedly between a MacBook (desktop app) and a Linux server and continued on each side, through the actual relay-free client-brokered transfer — including round trips, pull-backs, and the failure modes listed under Verification.

The safety model

  • A branch tip only ever moves to a descendant of itself, on either machine. Diverged or unrelated history parks the sender's commits at refs/handoff/<environment>/<handoff>/<branch> and stops with both machines untouched.
  • Exactly one side is live.thread.handoff.depart locks the giving side before any bundle is applied anywhere; mutations on an away thread are refused in the orchestrator (a small allowlist of local-copy lifecycle verbs excepted). An interrupted transfer releases the lock; a stranded one has a Continue here escape hatch, offered only while the transfer never landed. A completed handoff can no longer be aborted or repointed.
  • Old tip tagged and dirty worktrees stashed (root and destination worktree independently) before any pointer moves; patches dry-run first; a failed apply rolls the repository back and cleans up everything it extracted. Rollback metadata (pre-tag, stashes, apply directory) is persisted before the patch lands, so startup recovery can restore the tree from any crash point — including a first arrival that died before its thread projection existed.
  • A busy thread is interrupted first and the transfer starts when it goes idle; the depart command re-checks for active runs under the dispatch lock, so a racing message cannot slip a live run past the snapshot.
  • Payload ceilings (200 MB warn / 1 GB refuse) enforced during prepare and again per chunk on the receiving side. First hops cut the bundle against the repo's remote-tracking refs; a fully pushed branch ships no bundle at all.
  • Untracked files that would overwrite tracked files on the receiver refuse the hop (checked on real extracted paths in a staging directory, immune to path tricks). ThreadHandoffId is schema-restricted to a URL- and git-ref-safe alphabet.

What travels

Branch + unpushed commits (git bundle, checkpoint refs ride along so revert keeps working), tracked changes (binary patch), untracked files (null-delimited tar), the conversation (turn items + covered run ordinals), attachments (original names, which the items reference), terminal scrollback (restored on first open like after a restart), workspace shape (worktree threads land in worktrees — reuse the branch's worktree or provision one, detached when git forbids attaching). A target that lacks the repository entirely clones it from the bundle — no remote, credentials, or network needed on the receiving machine.

How it moves

Manifest inline, large parts content-addressed and fetched by digest in resumable 4 MB chunks; the receiver rejects a chunk that does not continue the staged bytes and treats an exact re-send of already-staged bytes as a no-op, so interrupted uploads resume. The client brokers the copy — the only component authenticated to both environments — and only offers targets that are connected and advertise the capability. Arrivals go through the event sink, so the thread appears in every connected client's sidebar the moment it lands.

Lineage, not direction

Each hop records the previous hop in a lineage table; a return trip is an ordinary hop toward an environment already in the chain, and the receiver revives the prior thread pair even when the client no longer remembers it — one conversation, one sidebar row, wherever it currently runs. A caller-named return target is validated against that lineage before anything is applied. Provenance survives round trips: every arrival keeps a link recording where the thread came from. Lifecycle actions (delete, archive, rename) apply to the pair once.

Version skew

threadHandoff capability follows the threadSettlement contract: absent means the environment is not offered as a destination.

Verification

  • contracts 266 · client-runtime 621 · web 118 · server suites green (orchestration-v2, persistence, environment, ws, startup, terminal) · monorepo typecheck, lint, fmt clean.
  • New coverage: tip-classification table, ref-name sanitization, full dispatch-path depart/refuse/complete/abort with reason surfacing, payload ceilings, chunk windows, sidebar pair-visibility rules, migration idempotency and mid-apply recovery.
  • Live smoke: repeated MacBook ↔ Linux server transfers of real threads, including the failure modes found and fixed along the way (full-history bundle timeout, empty bundle on a fully pushed branch, missing dispatch routing, JSON codec gap on the handoff timestamp, arrivals invisible to connected clients, post-push navigation, pair dedup after round trips).
  • Code review: all Macroscope findings across the review rounds addressed and resolved, including the crash-recovery, payload-abuse, and single-live-side race classes.

Related

Migrations

047 (orchestration_v2_thread_handoffs lineage table), 048 (apply_cwd column for projection-independent crash recovery) and 049 (root_stash_ref/root_cwd so root and worktree stashes recover independently). 047 was renumbered from 046 after the base branch inserted a migration.

Note

Add thread handoff to move a running thread between devices

  • Introduces an end-to-end thread handoff system allowing a running thread to be transferred between execution environments (devices), preserving git state, conversation history, terminals, and attachments.
  • Adds OrchestrationV2HandoffBundleV1 manifest contract, typed part kinds (git-bundle, tracked-patch, untracked-tar, etc.), and versioned DB migrations (047–050) for the orchestration_v2_thread_handoffs table.
  • Exposes readHandoffPart/writeHandoffPart HTTP endpoints on the environment API with offset-validated, idempotent chunked transfer and a 4 MB chunk size.
  • Adds prepareThreadHandoff and receiveThreadHandoff WebSocket RPCs (requiring AuthOrchestrationOperateScope) handled in the server WS layer and wired into production startup with a handoff recovery phase.
  • The web UI gains a ThreadHandoffDialog for progress-tracked moves, a banner for threads running elsewhere (with pull-back and continue-here actions), and sidebar deduplication hiding the 'away' copy when the 'here' peer is visible.
  • The mobile UI gains a ThreadHandoffBanner to surface away threads and resume them locally.
  • Archiving, unarchiving, and deleting a thread now best-effort mirrors the operation to the handoff peer thread.
  • Risk: handoff recovery runs at server startup; interrupted handoffs from a prior session are automatically resumed, which may replay partial git or file operations.

Macroscope summarized 0250f02.

@coderabbitai

coderabbitaiBot commented Aug 6, 2026

Copy link
Copy Markdown

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 Plus

Run ID: f38d45d4-abf4-498e-bf1f-f44233f84eeb

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@macroscopeappmacroscopeappBot left a comment

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.

One finding on the new handoff error contract; the migration, its tests, and the bundle schemas otherwise follow the Effect conventions (subpath namespace imports, no service-instance injection, no ManagedRuntime/runPromise, no Layer.succeed shortcuts).

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/contracts/src/orchestrationV2.ts Outdated

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review of the two new service modules. Three findings, all in apps/server/src/orchestration-v2/. Earlier feedback on OrchestrationV2HandoffError.message still stands and is not repeated here.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
@Bil0000Bil0000 changed the title feat(contracts): thread handoff bundle contractfeat(orchestrator): move a running thread between devicesAug 6, 2026
Comment threadpackages/client-runtime/src/state/threadHandoffTransfer.ts
Comment threadapps/web/src/components/ThreadHandoffDialog.tsx
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadpackages/contracts/src/environmentHttp.ts
Comment threadapps/web/src/components/BranchToolbar.tsx
juliusmarmingeand others added 23 commits August 7, 2026 13:17
Co-authored-by: codex <codex@users.noreply.github.com>
- Document the target orchestration graph, IDs, lifecycles, and capability model
- Add Codex app-server probe fixtures and update the probe test harness
- Introduce orchestration v2 service interfaces and error types
- Add replay runtime, fixtures, and integration coverage
- Update shared contracts and probe transcripts
Co-authored-by: codex <codex@users.noreply.github.com>
- Add Codex adapter and replay harness wiring
- Introduce in-memory orchestration projections and provider registry
- Expand orchestration contracts for turn and runtime events
Co-authored-by: codex <codex@users.noreply.github.com>
- Add context transfer IDs, schemas, and projections
- Support cheap fork creation and Codex native fork rollback
- Cover fork idempotency and replay behavior in tests
- Track remaining projection, context transfer, rollback, capability, and subagent work
- Clarify current V2 baseline and debugger-only follow-ups
- Map fork and merge-back turns into stored handoffs and transfer resolutions
- Add shell snapshot projection support plus coverage tests
- Update replay fixtures and web contracts for the new turn flow
Co-authored-by: codex <codex@users.noreply.github.com>
- Move Codex replay recording into `apps/server`
- Add Claude Agent SDK replay fixtures and test harness
- Update orchestration-v2 fixture scenarios and docs
- Move Claude provider runtime logic into its own module
- Share the SDK query runner between live and replay paths
- Add replay driver error wrapping for unexpected failures
- persist the selected model on run records
- surface run model selection in the debug UI
- update replay fixtures and contracts for the new field
- Record Claude SDK transcripts across multiple prompts and restart/query modes
- Add approval and tool-call replay coverage for new orchestration fixtures
- Update Claude adapter testkit to model open/prompt/permission frames
- Derive Claude SDK query options from runtime policy
- Add read-only replay fixture and policy mapping tests
- Reuse shared approval-policy fixtures across orchestrator tests
Co-authored-by: codex <codex@users.noreply.github.com>
- add active steering and interrupt-restart replay fixtures
- update Claude adapter/orchestrator turn handling for steering
- refresh replay and integration test coverage
- add interrupt and mid-tool replay fixtures for Claude and Codex
- log Claude Agent SDK protocol frames to native event traces
- project Codex commandExecution start events into orchestration updates
- Map Cursor SDK agents and runs to V2 thread and turn lifecycles
- Update MCP capability, tool, and testing guidance for SDK-based injection
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Record provider fixtures for continued forks and sibling merge-backs, exercise context survival end to end, and stop completed run listeners from consuming later provider events.
Co-authored-by: codex <codex@users.noreply.github.com>
Allow a pending merge-back to be consumed while changing providers and deliver both the provider-switch history and fork delta to the selected provider.
Co-authored-by: codex <codex@users.noreply.github.com>
Send full canonical target-thread context when a provider switch consumes a merge-back, and cover returning to an existing provider thread after a cross-provider fork.
Co-authored-by: codex <codex@users.noreply.github.com>
CI enforces preferSchemaOverJson; the created-worktree recovery path and
its test read the stored manifest with JSON.parse. Both now go through
Schema.fromJsonString(OrchestrationV2HandoffBundleV1), the same codec
that wrote the column.
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts

@macroscopeappmacroscopeappBot left a comment

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.

Reviewed the new Effect service modules (ThreadHandoffGit, ThreadHandoffService) and their call sites against the service conventions. The service definitions, make/layer exports, dependency acquisition and error modelling all look consistent with the conventions; the remaining findings are service-boundary imports in the two new test files that alias layer and erase the module namespace.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.test.ts Outdated
- An unreadable attachments directory now fails the prepare instead of
silently shipping a conversation whose attachments never travelled;
a missing directory still just means no attachments
- recordHop keeps rollback metadata when the same attempt records its
arrival; startup recovery finishes the stash pops for an arrived hop
that stopped before handing the receiver's changes back
- Import the handoff modules as namespaces in the new tests, matching
every production consumer
@Bil0000
Bil0000 marked this pull request as ready for review August 8, 2026 18:12
juliusmarmingeand others added 2 commits August 10, 2026 03:14
- Keep success feedback visible in the Git action control for 10 seconds
- Move the running elapsed timer into the panel menu slot
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 5 times, most recently from 5aca742 to 77168d0CompareAugust 14, 2026 09:27
@Bil0000
Bil0000 marked this pull request as draft August 14, 2026 11:17
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 14, 2026
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 519c42a to 4c55679CompareAugust 17, 2026 10:28
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 13 times, most recently from ceea97b to d2f1f51CompareSeptember 2, 2026 18:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@Bil0000@juliusmarminge@maria-rcks@mwolson@PixPMusic@nsxdavid@Yusuf007R
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(orchestrator): move a running thread between devices - #5544

Draft
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff
Draft

feat(orchestrator): move a running thread between devices#5544
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff

Conversation

@Bil0000

@Bil0000Bil0000 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Closes#5363.

Stacked on #2829 (t3code/codex-turn-mapping). main and the orchestrator branch share no common ancestor; every primitive this builds on exists only there.

What this does

Run on picks the machine a thread starts on and locks once it has a message — a thread is born on one device and dies there. This makes that reversible: pick another connected environment from the same control, and the conversation, the provider continuation, and the git working state move there. Pull it back later from the same control, which shows where the thread originally ran.

Verified live: real threads moved repeatedly between a MacBook (desktop app) and a Linux server and continued on each side, through the actual relay-free client-brokered transfer — including round trips, pull-backs, and the failure modes listed under Verification.

The safety model

  • A branch tip only ever moves to a descendant of itself, on either machine. Diverged or unrelated history parks the sender's commits at refs/handoff/<environment>/<handoff>/<branch> and stops with both machines untouched.
  • Exactly one side is live.thread.handoff.depart locks the giving side before any bundle is applied anywhere; mutations on an away thread are refused in the orchestrator (a small allowlist of local-copy lifecycle verbs excepted). An interrupted transfer releases the lock; a stranded one has a Continue here escape hatch, offered only while the transfer never landed. A completed handoff can no longer be aborted or repointed.
  • Old tip tagged and dirty worktrees stashed (root and destination worktree independently) before any pointer moves; patches dry-run first; a failed apply rolls the repository back and cleans up everything it extracted. Rollback metadata (pre-tag, stashes, apply directory) is persisted before the patch lands, so startup recovery can restore the tree from any crash point — including a first arrival that died before its thread projection existed.
  • A busy thread is interrupted first and the transfer starts when it goes idle; the depart command re-checks for active runs under the dispatch lock, so a racing message cannot slip a live run past the snapshot.
  • Payload ceilings (200 MB warn / 1 GB refuse) enforced during prepare and again per chunk on the receiving side. First hops cut the bundle against the repo's remote-tracking refs; a fully pushed branch ships no bundle at all.
  • Untracked files that would overwrite tracked files on the receiver refuse the hop (checked on real extracted paths in a staging directory, immune to path tricks). ThreadHandoffId is schema-restricted to a URL- and git-ref-safe alphabet.

What travels

Branch + unpushed commits (git bundle, checkpoint refs ride along so revert keeps working), tracked changes (binary patch), untracked files (null-delimited tar), the conversation (turn items + covered run ordinals), attachments (original names, which the items reference), terminal scrollback (restored on first open like after a restart), workspace shape (worktree threads land in worktrees — reuse the branch's worktree or provision one, detached when git forbids attaching). A target that lacks the repository entirely clones it from the bundle — no remote, credentials, or network needed on the receiving machine.

How it moves

Manifest inline, large parts content-addressed and fetched by digest in resumable 4 MB chunks; the receiver rejects a chunk that does not continue the staged bytes and treats an exact re-send of already-staged bytes as a no-op, so interrupted uploads resume. The client brokers the copy — the only component authenticated to both environments — and only offers targets that are connected and advertise the capability. Arrivals go through the event sink, so the thread appears in every connected client's sidebar the moment it lands.

Lineage, not direction

Each hop records the previous hop in a lineage table; a return trip is an ordinary hop toward an environment already in the chain, and the receiver revives the prior thread pair even when the client no longer remembers it — one conversation, one sidebar row, wherever it currently runs. A caller-named return target is validated against that lineage before anything is applied. Provenance survives round trips: every arrival keeps a link recording where the thread came from. Lifecycle actions (delete, archive, rename) apply to the pair once.

Version skew

threadHandoff capability follows the threadSettlement contract: absent means the environment is not offered as a destination.

Verification

  • contracts 266 · client-runtime 621 · web 118 · server suites green (orchestration-v2, persistence, environment, ws, startup, terminal) · monorepo typecheck, lint, fmt clean.
  • New coverage: tip-classification table, ref-name sanitization, full dispatch-path depart/refuse/complete/abort with reason surfacing, payload ceilings, chunk windows, sidebar pair-visibility rules, migration idempotency and mid-apply recovery.
  • Live smoke: repeated MacBook ↔ Linux server transfers of real threads, including the failure modes found and fixed along the way (full-history bundle timeout, empty bundle on a fully pushed branch, missing dispatch routing, JSON codec gap on the handoff timestamp, arrivals invisible to connected clients, post-push navigation, pair dedup after round trips).
  • Code review: all Macroscope findings across the review rounds addressed and resolved, including the crash-recovery, payload-abuse, and single-live-side race classes.

Related

Migrations

047 (orchestration_v2_thread_handoffs lineage table), 048 (apply_cwd column for projection-independent crash recovery) and 049 (root_stash_ref/root_cwd so root and worktree stashes recover independently). 047 was renumbered from 046 after the base branch inserted a migration.

Note

Add thread handoff to move a running thread between devices

  • Introduces an end-to-end thread handoff system allowing a running thread to be transferred between execution environments (devices), preserving git state, conversation history, terminals, and attachments.
  • Adds OrchestrationV2HandoffBundleV1 manifest contract, typed part kinds (git-bundle, tracked-patch, untracked-tar, etc.), and versioned DB migrations (047–050) for the orchestration_v2_thread_handoffs table.
  • Exposes readHandoffPart/writeHandoffPart HTTP endpoints on the environment API with offset-validated, idempotent chunked transfer and a 4 MB chunk size.
  • Adds prepareThreadHandoff and receiveThreadHandoff WebSocket RPCs (requiring AuthOrchestrationOperateScope) handled in the server WS layer and wired into production startup with a handoff recovery phase.
  • The web UI gains a ThreadHandoffDialog for progress-tracked moves, a banner for threads running elsewhere (with pull-back and continue-here actions), and sidebar deduplication hiding the 'away' copy when the 'here' peer is visible.
  • The mobile UI gains a ThreadHandoffBanner to surface away threads and resume them locally.
  • Archiving, unarchiving, and deleting a thread now best-effort mirrors the operation to the handoff peer thread.
  • Risk: handoff recovery runs at server startup; interrupted handoffs from a prior session are automatically resumed, which may replay partial git or file operations.

Macroscope summarized 0250f02.

@coderabbitai

coderabbitaiBot commented Aug 6, 2026

Copy link
Copy Markdown

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 Plus

Run ID: f38d45d4-abf4-498e-bf1f-f44233f84eeb

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@macroscopeappmacroscopeappBot left a comment

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.

One finding on the new handoff error contract; the migration, its tests, and the bundle schemas otherwise follow the Effect conventions (subpath namespace imports, no service-instance injection, no ManagedRuntime/runPromise, no Layer.succeed shortcuts).

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/contracts/src/orchestrationV2.ts Outdated

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review of the two new service modules. Three findings, all in apps/server/src/orchestration-v2/. Earlier feedback on OrchestrationV2HandoffError.message still stands and is not repeated here.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
@Bil0000Bil0000 changed the title feat(contracts): thread handoff bundle contractfeat(orchestrator): move a running thread between devicesAug 6, 2026
Comment threadpackages/client-runtime/src/state/threadHandoffTransfer.ts
Comment threadapps/web/src/components/ThreadHandoffDialog.tsx
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadpackages/contracts/src/environmentHttp.ts
Comment threadapps/web/src/components/BranchToolbar.tsx
juliusmarmingeand others added 23 commits August 7, 2026 13:17
Co-authored-by: codex <codex@users.noreply.github.com>
- Document the target orchestration graph, IDs, lifecycles, and capability model
- Add Codex app-server probe fixtures and update the probe test harness
- Introduce orchestration v2 service interfaces and error types
- Add replay runtime, fixtures, and integration coverage
- Update shared contracts and probe transcripts
Co-authored-by: codex <codex@users.noreply.github.com>
- Add Codex adapter and replay harness wiring
- Introduce in-memory orchestration projections and provider registry
- Expand orchestration contracts for turn and runtime events
Co-authored-by: codex <codex@users.noreply.github.com>
- Add context transfer IDs, schemas, and projections
- Support cheap fork creation and Codex native fork rollback
- Cover fork idempotency and replay behavior in tests
- Track remaining projection, context transfer, rollback, capability, and subagent work
- Clarify current V2 baseline and debugger-only follow-ups
- Map fork and merge-back turns into stored handoffs and transfer resolutions
- Add shell snapshot projection support plus coverage tests
- Update replay fixtures and web contracts for the new turn flow
Co-authored-by: codex <codex@users.noreply.github.com>
- Move Codex replay recording into `apps/server`
- Add Claude Agent SDK replay fixtures and test harness
- Update orchestration-v2 fixture scenarios and docs
- Move Claude provider runtime logic into its own module
- Share the SDK query runner between live and replay paths
- Add replay driver error wrapping for unexpected failures
- persist the selected model on run records
- surface run model selection in the debug UI
- update replay fixtures and contracts for the new field
- Record Claude SDK transcripts across multiple prompts and restart/query modes
- Add approval and tool-call replay coverage for new orchestration fixtures
- Update Claude adapter testkit to model open/prompt/permission frames
- Derive Claude SDK query options from runtime policy
- Add read-only replay fixture and policy mapping tests
- Reuse shared approval-policy fixtures across orchestrator tests
Co-authored-by: codex <codex@users.noreply.github.com>
- add active steering and interrupt-restart replay fixtures
- update Claude adapter/orchestrator turn handling for steering
- refresh replay and integration test coverage
- add interrupt and mid-tool replay fixtures for Claude and Codex
- log Claude Agent SDK protocol frames to native event traces
- project Codex commandExecution start events into orchestration updates
- Map Cursor SDK agents and runs to V2 thread and turn lifecycles
- Update MCP capability, tool, and testing guidance for SDK-based injection
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Record provider fixtures for continued forks and sibling merge-backs, exercise context survival end to end, and stop completed run listeners from consuming later provider events.
Co-authored-by: codex <codex@users.noreply.github.com>
Allow a pending merge-back to be consumed while changing providers and deliver both the provider-switch history and fork delta to the selected provider.
Co-authored-by: codex <codex@users.noreply.github.com>
Send full canonical target-thread context when a provider switch consumes a merge-back, and cover returning to an existing provider thread after a cross-provider fork.
Co-authored-by: codex <codex@users.noreply.github.com>
CI enforces preferSchemaOverJson; the created-worktree recovery path and
its test read the stored manifest with JSON.parse. Both now go through
Schema.fromJsonString(OrchestrationV2HandoffBundleV1), the same codec
that wrote the column.
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts

@macroscopeappmacroscopeappBot left a comment

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.

Reviewed the new Effect service modules (ThreadHandoffGit, ThreadHandoffService) and their call sites against the service conventions. The service definitions, make/layer exports, dependency acquisition and error modelling all look consistent with the conventions; the remaining findings are service-boundary imports in the two new test files that alias layer and erase the module namespace.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.test.ts Outdated
- An unreadable attachments directory now fails the prepare instead of
silently shipping a conversation whose attachments never travelled;
a missing directory still just means no attachments
- recordHop keeps rollback metadata when the same attempt records its
arrival; startup recovery finishes the stash pops for an arrived hop
that stopped before handing the receiver's changes back
- Import the handoff modules as namespaces in the new tests, matching
every production consumer
@Bil0000
Bil0000 marked this pull request as ready for review August 8, 2026 18:12
juliusmarmingeand others added 2 commits August 10, 2026 03:14
- Keep success feedback visible in the Git action control for 10 seconds
- Move the running elapsed timer into the panel menu slot
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 5 times, most recently from 5aca742 to 77168d0CompareAugust 14, 2026 09:27
@Bil0000
Bil0000 marked this pull request as draft August 14, 2026 11:17
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 14, 2026
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 519c42a to 4c55679CompareAugust 17, 2026 10:28
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 13 times, most recently from ceea97b to d2f1f51CompareSeptember 2, 2026 18:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@Bil0000@juliusmarminge@maria-rcks@mwolson@PixPMusic@nsxdavid@Yusuf007R
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(orchestrator): move a running thread between devices - #5544

Draft
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff
Draft

feat(orchestrator): move a running thread between devices#5544
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff

Conversation

@Bil0000

@Bil0000Bil0000 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Closes#5363.

Stacked on #2829 (t3code/codex-turn-mapping). main and the orchestrator branch share no common ancestor; every primitive this builds on exists only there.

What this does

Run on picks the machine a thread starts on and locks once it has a message — a thread is born on one device and dies there. This makes that reversible: pick another connected environment from the same control, and the conversation, the provider continuation, and the git working state move there. Pull it back later from the same control, which shows where the thread originally ran.

Verified live: real threads moved repeatedly between a MacBook (desktop app) and a Linux server and continued on each side, through the actual relay-free client-brokered transfer — including round trips, pull-backs, and the failure modes listed under Verification.

The safety model

  • A branch tip only ever moves to a descendant of itself, on either machine. Diverged or unrelated history parks the sender's commits at refs/handoff/<environment>/<handoff>/<branch> and stops with both machines untouched.
  • Exactly one side is live.thread.handoff.depart locks the giving side before any bundle is applied anywhere; mutations on an away thread are refused in the orchestrator (a small allowlist of local-copy lifecycle verbs excepted). An interrupted transfer releases the lock; a stranded one has a Continue here escape hatch, offered only while the transfer never landed. A completed handoff can no longer be aborted or repointed.
  • Old tip tagged and dirty worktrees stashed (root and destination worktree independently) before any pointer moves; patches dry-run first; a failed apply rolls the repository back and cleans up everything it extracted. Rollback metadata (pre-tag, stashes, apply directory) is persisted before the patch lands, so startup recovery can restore the tree from any crash point — including a first arrival that died before its thread projection existed.
  • A busy thread is interrupted first and the transfer starts when it goes idle; the depart command re-checks for active runs under the dispatch lock, so a racing message cannot slip a live run past the snapshot.
  • Payload ceilings (200 MB warn / 1 GB refuse) enforced during prepare and again per chunk on the receiving side. First hops cut the bundle against the repo's remote-tracking refs; a fully pushed branch ships no bundle at all.
  • Untracked files that would overwrite tracked files on the receiver refuse the hop (checked on real extracted paths in a staging directory, immune to path tricks). ThreadHandoffId is schema-restricted to a URL- and git-ref-safe alphabet.

What travels

Branch + unpushed commits (git bundle, checkpoint refs ride along so revert keeps working), tracked changes (binary patch), untracked files (null-delimited tar), the conversation (turn items + covered run ordinals), attachments (original names, which the items reference), terminal scrollback (restored on first open like after a restart), workspace shape (worktree threads land in worktrees — reuse the branch's worktree or provision one, detached when git forbids attaching). A target that lacks the repository entirely clones it from the bundle — no remote, credentials, or network needed on the receiving machine.

How it moves

Manifest inline, large parts content-addressed and fetched by digest in resumable 4 MB chunks; the receiver rejects a chunk that does not continue the staged bytes and treats an exact re-send of already-staged bytes as a no-op, so interrupted uploads resume. The client brokers the copy — the only component authenticated to both environments — and only offers targets that are connected and advertise the capability. Arrivals go through the event sink, so the thread appears in every connected client's sidebar the moment it lands.

Lineage, not direction

Each hop records the previous hop in a lineage table; a return trip is an ordinary hop toward an environment already in the chain, and the receiver revives the prior thread pair even when the client no longer remembers it — one conversation, one sidebar row, wherever it currently runs. A caller-named return target is validated against that lineage before anything is applied. Provenance survives round trips: every arrival keeps a link recording where the thread came from. Lifecycle actions (delete, archive, rename) apply to the pair once.

Version skew

threadHandoff capability follows the threadSettlement contract: absent means the environment is not offered as a destination.

Verification

  • contracts 266 · client-runtime 621 · web 118 · server suites green (orchestration-v2, persistence, environment, ws, startup, terminal) · monorepo typecheck, lint, fmt clean.
  • New coverage: tip-classification table, ref-name sanitization, full dispatch-path depart/refuse/complete/abort with reason surfacing, payload ceilings, chunk windows, sidebar pair-visibility rules, migration idempotency and mid-apply recovery.
  • Live smoke: repeated MacBook ↔ Linux server transfers of real threads, including the failure modes found and fixed along the way (full-history bundle timeout, empty bundle on a fully pushed branch, missing dispatch routing, JSON codec gap on the handoff timestamp, arrivals invisible to connected clients, post-push navigation, pair dedup after round trips).
  • Code review: all Macroscope findings across the review rounds addressed and resolved, including the crash-recovery, payload-abuse, and single-live-side race classes.

Related

Migrations

047 (orchestration_v2_thread_handoffs lineage table), 048 (apply_cwd column for projection-independent crash recovery) and 049 (root_stash_ref/root_cwd so root and worktree stashes recover independently). 047 was renumbered from 046 after the base branch inserted a migration.

Note

Add thread handoff to move a running thread between devices

  • Introduces an end-to-end thread handoff system allowing a running thread to be transferred between execution environments (devices), preserving git state, conversation history, terminals, and attachments.
  • Adds OrchestrationV2HandoffBundleV1 manifest contract, typed part kinds (git-bundle, tracked-patch, untracked-tar, etc.), and versioned DB migrations (047–050) for the orchestration_v2_thread_handoffs table.
  • Exposes readHandoffPart/writeHandoffPart HTTP endpoints on the environment API with offset-validated, idempotent chunked transfer and a 4 MB chunk size.
  • Adds prepareThreadHandoff and receiveThreadHandoff WebSocket RPCs (requiring AuthOrchestrationOperateScope) handled in the server WS layer and wired into production startup with a handoff recovery phase.
  • The web UI gains a ThreadHandoffDialog for progress-tracked moves, a banner for threads running elsewhere (with pull-back and continue-here actions), and sidebar deduplication hiding the 'away' copy when the 'here' peer is visible.
  • The mobile UI gains a ThreadHandoffBanner to surface away threads and resume them locally.
  • Archiving, unarchiving, and deleting a thread now best-effort mirrors the operation to the handoff peer thread.
  • Risk: handoff recovery runs at server startup; interrupted handoffs from a prior session are automatically resumed, which may replay partial git or file operations.

Macroscope summarized 0250f02.

@coderabbitai

coderabbitaiBot commented Aug 6, 2026

Copy link
Copy Markdown

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 Plus

Run ID: f38d45d4-abf4-498e-bf1f-f44233f84eeb

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@macroscopeappmacroscopeappBot left a comment

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.

One finding on the new handoff error contract; the migration, its tests, and the bundle schemas otherwise follow the Effect conventions (subpath namespace imports, no service-instance injection, no ManagedRuntime/runPromise, no Layer.succeed shortcuts).

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/contracts/src/orchestrationV2.ts Outdated

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review of the two new service modules. Three findings, all in apps/server/src/orchestration-v2/. Earlier feedback on OrchestrationV2HandoffError.message still stands and is not repeated here.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
@Bil0000Bil0000 changed the title feat(contracts): thread handoff bundle contractfeat(orchestrator): move a running thread between devicesAug 6, 2026
Comment threadpackages/client-runtime/src/state/threadHandoffTransfer.ts
Comment threadapps/web/src/components/ThreadHandoffDialog.tsx
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadpackages/contracts/src/environmentHttp.ts
Comment threadapps/web/src/components/BranchToolbar.tsx
juliusmarmingeand others added 23 commits August 7, 2026 13:17
Co-authored-by: codex <codex@users.noreply.github.com>
- Document the target orchestration graph, IDs, lifecycles, and capability model
- Add Codex app-server probe fixtures and update the probe test harness
- Introduce orchestration v2 service interfaces and error types
- Add replay runtime, fixtures, and integration coverage
- Update shared contracts and probe transcripts
Co-authored-by: codex <codex@users.noreply.github.com>
- Add Codex adapter and replay harness wiring
- Introduce in-memory orchestration projections and provider registry
- Expand orchestration contracts for turn and runtime events
Co-authored-by: codex <codex@users.noreply.github.com>
- Add context transfer IDs, schemas, and projections
- Support cheap fork creation and Codex native fork rollback
- Cover fork idempotency and replay behavior in tests
- Track remaining projection, context transfer, rollback, capability, and subagent work
- Clarify current V2 baseline and debugger-only follow-ups
- Map fork and merge-back turns into stored handoffs and transfer resolutions
- Add shell snapshot projection support plus coverage tests
- Update replay fixtures and web contracts for the new turn flow
Co-authored-by: codex <codex@users.noreply.github.com>
- Move Codex replay recording into `apps/server`
- Add Claude Agent SDK replay fixtures and test harness
- Update orchestration-v2 fixture scenarios and docs
- Move Claude provider runtime logic into its own module
- Share the SDK query runner between live and replay paths
- Add replay driver error wrapping for unexpected failures
- persist the selected model on run records
- surface run model selection in the debug UI
- update replay fixtures and contracts for the new field
- Record Claude SDK transcripts across multiple prompts and restart/query modes
- Add approval and tool-call replay coverage for new orchestration fixtures
- Update Claude adapter testkit to model open/prompt/permission frames
- Derive Claude SDK query options from runtime policy
- Add read-only replay fixture and policy mapping tests
- Reuse shared approval-policy fixtures across orchestrator tests
Co-authored-by: codex <codex@users.noreply.github.com>
- add active steering and interrupt-restart replay fixtures
- update Claude adapter/orchestrator turn handling for steering
- refresh replay and integration test coverage
- add interrupt and mid-tool replay fixtures for Claude and Codex
- log Claude Agent SDK protocol frames to native event traces
- project Codex commandExecution start events into orchestration updates
- Map Cursor SDK agents and runs to V2 thread and turn lifecycles
- Update MCP capability, tool, and testing guidance for SDK-based injection
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Record provider fixtures for continued forks and sibling merge-backs, exercise context survival end to end, and stop completed run listeners from consuming later provider events.
Co-authored-by: codex <codex@users.noreply.github.com>
Allow a pending merge-back to be consumed while changing providers and deliver both the provider-switch history and fork delta to the selected provider.
Co-authored-by: codex <codex@users.noreply.github.com>
Send full canonical target-thread context when a provider switch consumes a merge-back, and cover returning to an existing provider thread after a cross-provider fork.
Co-authored-by: codex <codex@users.noreply.github.com>
CI enforces preferSchemaOverJson; the created-worktree recovery path and
its test read the stored manifest with JSON.parse. Both now go through
Schema.fromJsonString(OrchestrationV2HandoffBundleV1), the same codec
that wrote the column.
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts

@macroscopeappmacroscopeappBot left a comment

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.

Reviewed the new Effect service modules (ThreadHandoffGit, ThreadHandoffService) and their call sites against the service conventions. The service definitions, make/layer exports, dependency acquisition and error modelling all look consistent with the conventions; the remaining findings are service-boundary imports in the two new test files that alias layer and erase the module namespace.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.test.ts Outdated
- An unreadable attachments directory now fails the prepare instead of
silently shipping a conversation whose attachments never travelled;
a missing directory still just means no attachments
- recordHop keeps rollback metadata when the same attempt records its
arrival; startup recovery finishes the stash pops for an arrived hop
that stopped before handing the receiver's changes back
- Import the handoff modules as namespaces in the new tests, matching
every production consumer
@Bil0000
Bil0000 marked this pull request as ready for review August 8, 2026 18:12
juliusmarmingeand others added 2 commits August 10, 2026 03:14
- Keep success feedback visible in the Git action control for 10 seconds
- Move the running elapsed timer into the panel menu slot
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 5 times, most recently from 5aca742 to 77168d0CompareAugust 14, 2026 09:27
@Bil0000
Bil0000 marked this pull request as draft August 14, 2026 11:17
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 14, 2026
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 519c42a to 4c55679CompareAugust 17, 2026 10:28
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 13 times, most recently from ceea97b to d2f1f51CompareSeptember 2, 2026 18:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@Bil0000@juliusmarminge@maria-rcks@mwolson@PixPMusic@nsxdavid@Yusuf007R
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(orchestrator): move a running thread between devices - #5544

Draft
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff
Draft

feat(orchestrator): move a running thread between devices#5544
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff

Conversation

@Bil0000

@Bil0000Bil0000 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Closes#5363.

Stacked on #2829 (t3code/codex-turn-mapping). main and the orchestrator branch share no common ancestor; every primitive this builds on exists only there.

What this does

Run on picks the machine a thread starts on and locks once it has a message — a thread is born on one device and dies there. This makes that reversible: pick another connected environment from the same control, and the conversation, the provider continuation, and the git working state move there. Pull it back later from the same control, which shows where the thread originally ran.

Verified live: real threads moved repeatedly between a MacBook (desktop app) and a Linux server and continued on each side, through the actual relay-free client-brokered transfer — including round trips, pull-backs, and the failure modes listed under Verification.

The safety model

  • A branch tip only ever moves to a descendant of itself, on either machine. Diverged or unrelated history parks the sender's commits at refs/handoff/<environment>/<handoff>/<branch> and stops with both machines untouched.
  • Exactly one side is live.thread.handoff.depart locks the giving side before any bundle is applied anywhere; mutations on an away thread are refused in the orchestrator (a small allowlist of local-copy lifecycle verbs excepted). An interrupted transfer releases the lock; a stranded one has a Continue here escape hatch, offered only while the transfer never landed. A completed handoff can no longer be aborted or repointed.
  • Old tip tagged and dirty worktrees stashed (root and destination worktree independently) before any pointer moves; patches dry-run first; a failed apply rolls the repository back and cleans up everything it extracted. Rollback metadata (pre-tag, stashes, apply directory) is persisted before the patch lands, so startup recovery can restore the tree from any crash point — including a first arrival that died before its thread projection existed.
  • A busy thread is interrupted first and the transfer starts when it goes idle; the depart command re-checks for active runs under the dispatch lock, so a racing message cannot slip a live run past the snapshot.
  • Payload ceilings (200 MB warn / 1 GB refuse) enforced during prepare and again per chunk on the receiving side. First hops cut the bundle against the repo's remote-tracking refs; a fully pushed branch ships no bundle at all.
  • Untracked files that would overwrite tracked files on the receiver refuse the hop (checked on real extracted paths in a staging directory, immune to path tricks). ThreadHandoffId is schema-restricted to a URL- and git-ref-safe alphabet.

What travels

Branch + unpushed commits (git bundle, checkpoint refs ride along so revert keeps working), tracked changes (binary patch), untracked files (null-delimited tar), the conversation (turn items + covered run ordinals), attachments (original names, which the items reference), terminal scrollback (restored on first open like after a restart), workspace shape (worktree threads land in worktrees — reuse the branch's worktree or provision one, detached when git forbids attaching). A target that lacks the repository entirely clones it from the bundle — no remote, credentials, or network needed on the receiving machine.

How it moves

Manifest inline, large parts content-addressed and fetched by digest in resumable 4 MB chunks; the receiver rejects a chunk that does not continue the staged bytes and treats an exact re-send of already-staged bytes as a no-op, so interrupted uploads resume. The client brokers the copy — the only component authenticated to both environments — and only offers targets that are connected and advertise the capability. Arrivals go through the event sink, so the thread appears in every connected client's sidebar the moment it lands.

Lineage, not direction

Each hop records the previous hop in a lineage table; a return trip is an ordinary hop toward an environment already in the chain, and the receiver revives the prior thread pair even when the client no longer remembers it — one conversation, one sidebar row, wherever it currently runs. A caller-named return target is validated against that lineage before anything is applied. Provenance survives round trips: every arrival keeps a link recording where the thread came from. Lifecycle actions (delete, archive, rename) apply to the pair once.

Version skew

threadHandoff capability follows the threadSettlement contract: absent means the environment is not offered as a destination.

Verification

  • contracts 266 · client-runtime 621 · web 118 · server suites green (orchestration-v2, persistence, environment, ws, startup, terminal) · monorepo typecheck, lint, fmt clean.
  • New coverage: tip-classification table, ref-name sanitization, full dispatch-path depart/refuse/complete/abort with reason surfacing, payload ceilings, chunk windows, sidebar pair-visibility rules, migration idempotency and mid-apply recovery.
  • Live smoke: repeated MacBook ↔ Linux server transfers of real threads, including the failure modes found and fixed along the way (full-history bundle timeout, empty bundle on a fully pushed branch, missing dispatch routing, JSON codec gap on the handoff timestamp, arrivals invisible to connected clients, post-push navigation, pair dedup after round trips).
  • Code review: all Macroscope findings across the review rounds addressed and resolved, including the crash-recovery, payload-abuse, and single-live-side race classes.

Related

Migrations

047 (orchestration_v2_thread_handoffs lineage table), 048 (apply_cwd column for projection-independent crash recovery) and 049 (root_stash_ref/root_cwd so root and worktree stashes recover independently). 047 was renumbered from 046 after the base branch inserted a migration.

Note

Add thread handoff to move a running thread between devices

  • Introduces an end-to-end thread handoff system allowing a running thread to be transferred between execution environments (devices), preserving git state, conversation history, terminals, and attachments.
  • Adds OrchestrationV2HandoffBundleV1 manifest contract, typed part kinds (git-bundle, tracked-patch, untracked-tar, etc.), and versioned DB migrations (047–050) for the orchestration_v2_thread_handoffs table.
  • Exposes readHandoffPart/writeHandoffPart HTTP endpoints on the environment API with offset-validated, idempotent chunked transfer and a 4 MB chunk size.
  • Adds prepareThreadHandoff and receiveThreadHandoff WebSocket RPCs (requiring AuthOrchestrationOperateScope) handled in the server WS layer and wired into production startup with a handoff recovery phase.
  • The web UI gains a ThreadHandoffDialog for progress-tracked moves, a banner for threads running elsewhere (with pull-back and continue-here actions), and sidebar deduplication hiding the 'away' copy when the 'here' peer is visible.
  • The mobile UI gains a ThreadHandoffBanner to surface away threads and resume them locally.
  • Archiving, unarchiving, and deleting a thread now best-effort mirrors the operation to the handoff peer thread.
  • Risk: handoff recovery runs at server startup; interrupted handoffs from a prior session are automatically resumed, which may replay partial git or file operations.

Macroscope summarized 0250f02.

@coderabbitai

coderabbitaiBot commented Aug 6, 2026

Copy link
Copy Markdown

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 Plus

Run ID: f38d45d4-abf4-498e-bf1f-f44233f84eeb

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@macroscopeappmacroscopeappBot left a comment

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.

One finding on the new handoff error contract; the migration, its tests, and the bundle schemas otherwise follow the Effect conventions (subpath namespace imports, no service-instance injection, no ManagedRuntime/runPromise, no Layer.succeed shortcuts).

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/contracts/src/orchestrationV2.ts Outdated

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review of the two new service modules. Three findings, all in apps/server/src/orchestration-v2/. Earlier feedback on OrchestrationV2HandoffError.message still stands and is not repeated here.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
@Bil0000Bil0000 changed the title feat(contracts): thread handoff bundle contractfeat(orchestrator): move a running thread between devicesAug 6, 2026
Comment threadpackages/client-runtime/src/state/threadHandoffTransfer.ts
Comment threadapps/web/src/components/ThreadHandoffDialog.tsx
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadpackages/contracts/src/environmentHttp.ts
Comment threadapps/web/src/components/BranchToolbar.tsx
juliusmarmingeand others added 23 commits August 7, 2026 13:17
Co-authored-by: codex <codex@users.noreply.github.com>
- Document the target orchestration graph, IDs, lifecycles, and capability model
- Add Codex app-server probe fixtures and update the probe test harness
- Introduce orchestration v2 service interfaces and error types
- Add replay runtime, fixtures, and integration coverage
- Update shared contracts and probe transcripts
Co-authored-by: codex <codex@users.noreply.github.com>
- Add Codex adapter and replay harness wiring
- Introduce in-memory orchestration projections and provider registry
- Expand orchestration contracts for turn and runtime events
Co-authored-by: codex <codex@users.noreply.github.com>
- Add context transfer IDs, schemas, and projections
- Support cheap fork creation and Codex native fork rollback
- Cover fork idempotency and replay behavior in tests
- Track remaining projection, context transfer, rollback, capability, and subagent work
- Clarify current V2 baseline and debugger-only follow-ups
- Map fork and merge-back turns into stored handoffs and transfer resolutions
- Add shell snapshot projection support plus coverage tests
- Update replay fixtures and web contracts for the new turn flow
Co-authored-by: codex <codex@users.noreply.github.com>
- Move Codex replay recording into `apps/server`
- Add Claude Agent SDK replay fixtures and test harness
- Update orchestration-v2 fixture scenarios and docs
- Move Claude provider runtime logic into its own module
- Share the SDK query runner between live and replay paths
- Add replay driver error wrapping for unexpected failures
- persist the selected model on run records
- surface run model selection in the debug UI
- update replay fixtures and contracts for the new field
- Record Claude SDK transcripts across multiple prompts and restart/query modes
- Add approval and tool-call replay coverage for new orchestration fixtures
- Update Claude adapter testkit to model open/prompt/permission frames
- Derive Claude SDK query options from runtime policy
- Add read-only replay fixture and policy mapping tests
- Reuse shared approval-policy fixtures across orchestrator tests
Co-authored-by: codex <codex@users.noreply.github.com>
- add active steering and interrupt-restart replay fixtures
- update Claude adapter/orchestrator turn handling for steering
- refresh replay and integration test coverage
- add interrupt and mid-tool replay fixtures for Claude and Codex
- log Claude Agent SDK protocol frames to native event traces
- project Codex commandExecution start events into orchestration updates
- Map Cursor SDK agents and runs to V2 thread and turn lifecycles
- Update MCP capability, tool, and testing guidance for SDK-based injection
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Record provider fixtures for continued forks and sibling merge-backs, exercise context survival end to end, and stop completed run listeners from consuming later provider events.
Co-authored-by: codex <codex@users.noreply.github.com>
Allow a pending merge-back to be consumed while changing providers and deliver both the provider-switch history and fork delta to the selected provider.
Co-authored-by: codex <codex@users.noreply.github.com>
Send full canonical target-thread context when a provider switch consumes a merge-back, and cover returning to an existing provider thread after a cross-provider fork.
Co-authored-by: codex <codex@users.noreply.github.com>
CI enforces preferSchemaOverJson; the created-worktree recovery path and
its test read the stored manifest with JSON.parse. Both now go through
Schema.fromJsonString(OrchestrationV2HandoffBundleV1), the same codec
that wrote the column.
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts

@macroscopeappmacroscopeappBot left a comment

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.

Reviewed the new Effect service modules (ThreadHandoffGit, ThreadHandoffService) and their call sites against the service conventions. The service definitions, make/layer exports, dependency acquisition and error modelling all look consistent with the conventions; the remaining findings are service-boundary imports in the two new test files that alias layer and erase the module namespace.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.test.ts Outdated
- An unreadable attachments directory now fails the prepare instead of
silently shipping a conversation whose attachments never travelled;
a missing directory still just means no attachments
- recordHop keeps rollback metadata when the same attempt records its
arrival; startup recovery finishes the stash pops for an arrived hop
that stopped before handing the receiver's changes back
- Import the handoff modules as namespaces in the new tests, matching
every production consumer
@Bil0000
Bil0000 marked this pull request as ready for review August 8, 2026 18:12
juliusmarmingeand others added 2 commits August 10, 2026 03:14
- Keep success feedback visible in the Git action control for 10 seconds
- Move the running elapsed timer into the panel menu slot
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 5 times, most recently from 5aca742 to 77168d0CompareAugust 14, 2026 09:27
@Bil0000
Bil0000 marked this pull request as draft August 14, 2026 11:17
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 14, 2026
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 519c42a to 4c55679CompareAugust 17, 2026 10:28
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 13 times, most recently from ceea97b to d2f1f51CompareSeptember 2, 2026 18:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@Bil0000@juliusmarminge@maria-rcks@mwolson@PixPMusic@nsxdavid@Yusuf007R
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(orchestrator): move a running thread between devices - #5544

Draft
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff
Draft

feat(orchestrator): move a running thread between devices#5544
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff

Conversation

@Bil0000

@Bil0000Bil0000 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Closes#5363.

Stacked on #2829 (t3code/codex-turn-mapping). main and the orchestrator branch share no common ancestor; every primitive this builds on exists only there.

What this does

Run on picks the machine a thread starts on and locks once it has a message — a thread is born on one device and dies there. This makes that reversible: pick another connected environment from the same control, and the conversation, the provider continuation, and the git working state move there. Pull it back later from the same control, which shows where the thread originally ran.

Verified live: real threads moved repeatedly between a MacBook (desktop app) and a Linux server and continued on each side, through the actual relay-free client-brokered transfer — including round trips, pull-backs, and the failure modes listed under Verification.

The safety model

  • A branch tip only ever moves to a descendant of itself, on either machine. Diverged or unrelated history parks the sender's commits at refs/handoff/<environment>/<handoff>/<branch> and stops with both machines untouched.
  • Exactly one side is live.thread.handoff.depart locks the giving side before any bundle is applied anywhere; mutations on an away thread are refused in the orchestrator (a small allowlist of local-copy lifecycle verbs excepted). An interrupted transfer releases the lock; a stranded one has a Continue here escape hatch, offered only while the transfer never landed. A completed handoff can no longer be aborted or repointed.
  • Old tip tagged and dirty worktrees stashed (root and destination worktree independently) before any pointer moves; patches dry-run first; a failed apply rolls the repository back and cleans up everything it extracted. Rollback metadata (pre-tag, stashes, apply directory) is persisted before the patch lands, so startup recovery can restore the tree from any crash point — including a first arrival that died before its thread projection existed.
  • A busy thread is interrupted first and the transfer starts when it goes idle; the depart command re-checks for active runs under the dispatch lock, so a racing message cannot slip a live run past the snapshot.
  • Payload ceilings (200 MB warn / 1 GB refuse) enforced during prepare and again per chunk on the receiving side. First hops cut the bundle against the repo's remote-tracking refs; a fully pushed branch ships no bundle at all.
  • Untracked files that would overwrite tracked files on the receiver refuse the hop (checked on real extracted paths in a staging directory, immune to path tricks). ThreadHandoffId is schema-restricted to a URL- and git-ref-safe alphabet.

What travels

Branch + unpushed commits (git bundle, checkpoint refs ride along so revert keeps working), tracked changes (binary patch), untracked files (null-delimited tar), the conversation (turn items + covered run ordinals), attachments (original names, which the items reference), terminal scrollback (restored on first open like after a restart), workspace shape (worktree threads land in worktrees — reuse the branch's worktree or provision one, detached when git forbids attaching). A target that lacks the repository entirely clones it from the bundle — no remote, credentials, or network needed on the receiving machine.

How it moves

Manifest inline, large parts content-addressed and fetched by digest in resumable 4 MB chunks; the receiver rejects a chunk that does not continue the staged bytes and treats an exact re-send of already-staged bytes as a no-op, so interrupted uploads resume. The client brokers the copy — the only component authenticated to both environments — and only offers targets that are connected and advertise the capability. Arrivals go through the event sink, so the thread appears in every connected client's sidebar the moment it lands.

Lineage, not direction

Each hop records the previous hop in a lineage table; a return trip is an ordinary hop toward an environment already in the chain, and the receiver revives the prior thread pair even when the client no longer remembers it — one conversation, one sidebar row, wherever it currently runs. A caller-named return target is validated against that lineage before anything is applied. Provenance survives round trips: every arrival keeps a link recording where the thread came from. Lifecycle actions (delete, archive, rename) apply to the pair once.

Version skew

threadHandoff capability follows the threadSettlement contract: absent means the environment is not offered as a destination.

Verification

  • contracts 266 · client-runtime 621 · web 118 · server suites green (orchestration-v2, persistence, environment, ws, startup, terminal) · monorepo typecheck, lint, fmt clean.
  • New coverage: tip-classification table, ref-name sanitization, full dispatch-path depart/refuse/complete/abort with reason surfacing, payload ceilings, chunk windows, sidebar pair-visibility rules, migration idempotency and mid-apply recovery.
  • Live smoke: repeated MacBook ↔ Linux server transfers of real threads, including the failure modes found and fixed along the way (full-history bundle timeout, empty bundle on a fully pushed branch, missing dispatch routing, JSON codec gap on the handoff timestamp, arrivals invisible to connected clients, post-push navigation, pair dedup after round trips).
  • Code review: all Macroscope findings across the review rounds addressed and resolved, including the crash-recovery, payload-abuse, and single-live-side race classes.

Related

Migrations

047 (orchestration_v2_thread_handoffs lineage table), 048 (apply_cwd column for projection-independent crash recovery) and 049 (root_stash_ref/root_cwd so root and worktree stashes recover independently). 047 was renumbered from 046 after the base branch inserted a migration.

Note

Add thread handoff to move a running thread between devices

  • Introduces an end-to-end thread handoff system allowing a running thread to be transferred between execution environments (devices), preserving git state, conversation history, terminals, and attachments.
  • Adds OrchestrationV2HandoffBundleV1 manifest contract, typed part kinds (git-bundle, tracked-patch, untracked-tar, etc.), and versioned DB migrations (047–050) for the orchestration_v2_thread_handoffs table.
  • Exposes readHandoffPart/writeHandoffPart HTTP endpoints on the environment API with offset-validated, idempotent chunked transfer and a 4 MB chunk size.
  • Adds prepareThreadHandoff and receiveThreadHandoff WebSocket RPCs (requiring AuthOrchestrationOperateScope) handled in the server WS layer and wired into production startup with a handoff recovery phase.
  • The web UI gains a ThreadHandoffDialog for progress-tracked moves, a banner for threads running elsewhere (with pull-back and continue-here actions), and sidebar deduplication hiding the 'away' copy when the 'here' peer is visible.
  • The mobile UI gains a ThreadHandoffBanner to surface away threads and resume them locally.
  • Archiving, unarchiving, and deleting a thread now best-effort mirrors the operation to the handoff peer thread.
  • Risk: handoff recovery runs at server startup; interrupted handoffs from a prior session are automatically resumed, which may replay partial git or file operations.

Macroscope summarized 0250f02.

@coderabbitai

coderabbitaiBot commented Aug 6, 2026

Copy link
Copy Markdown

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 Plus

Run ID: f38d45d4-abf4-498e-bf1f-f44233f84eeb

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@macroscopeappmacroscopeappBot left a comment

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.

One finding on the new handoff error contract; the migration, its tests, and the bundle schemas otherwise follow the Effect conventions (subpath namespace imports, no service-instance injection, no ManagedRuntime/runPromise, no Layer.succeed shortcuts).

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/contracts/src/orchestrationV2.ts Outdated

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review of the two new service modules. Three findings, all in apps/server/src/orchestration-v2/. Earlier feedback on OrchestrationV2HandoffError.message still stands and is not repeated here.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
@Bil0000Bil0000 changed the title feat(contracts): thread handoff bundle contractfeat(orchestrator): move a running thread between devicesAug 6, 2026
Comment threadpackages/client-runtime/src/state/threadHandoffTransfer.ts
Comment threadapps/web/src/components/ThreadHandoffDialog.tsx
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadpackages/contracts/src/environmentHttp.ts
Comment threadapps/web/src/components/BranchToolbar.tsx
juliusmarmingeand others added 23 commits August 7, 2026 13:17
Co-authored-by: codex <codex@users.noreply.github.com>
- Document the target orchestration graph, IDs, lifecycles, and capability model
- Add Codex app-server probe fixtures and update the probe test harness
- Introduce orchestration v2 service interfaces and error types
- Add replay runtime, fixtures, and integration coverage
- Update shared contracts and probe transcripts
Co-authored-by: codex <codex@users.noreply.github.com>
- Add Codex adapter and replay harness wiring
- Introduce in-memory orchestration projections and provider registry
- Expand orchestration contracts for turn and runtime events
Co-authored-by: codex <codex@users.noreply.github.com>
- Add context transfer IDs, schemas, and projections
- Support cheap fork creation and Codex native fork rollback
- Cover fork idempotency and replay behavior in tests
- Track remaining projection, context transfer, rollback, capability, and subagent work
- Clarify current V2 baseline and debugger-only follow-ups
- Map fork and merge-back turns into stored handoffs and transfer resolutions
- Add shell snapshot projection support plus coverage tests
- Update replay fixtures and web contracts for the new turn flow
Co-authored-by: codex <codex@users.noreply.github.com>
- Move Codex replay recording into `apps/server`
- Add Claude Agent SDK replay fixtures and test harness
- Update orchestration-v2 fixture scenarios and docs
- Move Claude provider runtime logic into its own module
- Share the SDK query runner between live and replay paths
- Add replay driver error wrapping for unexpected failures
- persist the selected model on run records
- surface run model selection in the debug UI
- update replay fixtures and contracts for the new field
- Record Claude SDK transcripts across multiple prompts and restart/query modes
- Add approval and tool-call replay coverage for new orchestration fixtures
- Update Claude adapter testkit to model open/prompt/permission frames
- Derive Claude SDK query options from runtime policy
- Add read-only replay fixture and policy mapping tests
- Reuse shared approval-policy fixtures across orchestrator tests
Co-authored-by: codex <codex@users.noreply.github.com>
- add active steering and interrupt-restart replay fixtures
- update Claude adapter/orchestrator turn handling for steering
- refresh replay and integration test coverage
- add interrupt and mid-tool replay fixtures for Claude and Codex
- log Claude Agent SDK protocol frames to native event traces
- project Codex commandExecution start events into orchestration updates
- Map Cursor SDK agents and runs to V2 thread and turn lifecycles
- Update MCP capability, tool, and testing guidance for SDK-based injection
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Record provider fixtures for continued forks and sibling merge-backs, exercise context survival end to end, and stop completed run listeners from consuming later provider events.
Co-authored-by: codex <codex@users.noreply.github.com>
Allow a pending merge-back to be consumed while changing providers and deliver both the provider-switch history and fork delta to the selected provider.
Co-authored-by: codex <codex@users.noreply.github.com>
Send full canonical target-thread context when a provider switch consumes a merge-back, and cover returning to an existing provider thread after a cross-provider fork.
Co-authored-by: codex <codex@users.noreply.github.com>
CI enforces preferSchemaOverJson; the created-worktree recovery path and
its test read the stored manifest with JSON.parse. Both now go through
Schema.fromJsonString(OrchestrationV2HandoffBundleV1), the same codec
that wrote the column.
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts

@macroscopeappmacroscopeappBot left a comment

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.

Reviewed the new Effect service modules (ThreadHandoffGit, ThreadHandoffService) and their call sites against the service conventions. The service definitions, make/layer exports, dependency acquisition and error modelling all look consistent with the conventions; the remaining findings are service-boundary imports in the two new test files that alias layer and erase the module namespace.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.test.ts Outdated
- An unreadable attachments directory now fails the prepare instead of
silently shipping a conversation whose attachments never travelled;
a missing directory still just means no attachments
- recordHop keeps rollback metadata when the same attempt records its
arrival; startup recovery finishes the stash pops for an arrived hop
that stopped before handing the receiver's changes back
- Import the handoff modules as namespaces in the new tests, matching
every production consumer
@Bil0000
Bil0000 marked this pull request as ready for review August 8, 2026 18:12
juliusmarmingeand others added 2 commits August 10, 2026 03:14
- Keep success feedback visible in the Git action control for 10 seconds
- Move the running elapsed timer into the panel menu slot
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 5 times, most recently from 5aca742 to 77168d0CompareAugust 14, 2026 09:27
@Bil0000
Bil0000 marked this pull request as draft August 14, 2026 11:17
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 14, 2026
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 519c42a to 4c55679CompareAugust 17, 2026 10:28
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 13 times, most recently from ceea97b to d2f1f51CompareSeptember 2, 2026 18:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@Bil0000@juliusmarminge@maria-rcks@mwolson@PixPMusic@nsxdavid@Yusuf007R
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(orchestrator): move a running thread between devices - #5544

Draft
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff
Draft

feat(orchestrator): move a running thread between devices#5544
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff

Conversation

@Bil0000

@Bil0000Bil0000 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Closes#5363.

Stacked on #2829 (t3code/codex-turn-mapping). main and the orchestrator branch share no common ancestor; every primitive this builds on exists only there.

What this does

Run on picks the machine a thread starts on and locks once it has a message — a thread is born on one device and dies there. This makes that reversible: pick another connected environment from the same control, and the conversation, the provider continuation, and the git working state move there. Pull it back later from the same control, which shows where the thread originally ran.

Verified live: real threads moved repeatedly between a MacBook (desktop app) and a Linux server and continued on each side, through the actual relay-free client-brokered transfer — including round trips, pull-backs, and the failure modes listed under Verification.

The safety model

  • A branch tip only ever moves to a descendant of itself, on either machine. Diverged or unrelated history parks the sender's commits at refs/handoff/<environment>/<handoff>/<branch> and stops with both machines untouched.
  • Exactly one side is live.thread.handoff.depart locks the giving side before any bundle is applied anywhere; mutations on an away thread are refused in the orchestrator (a small allowlist of local-copy lifecycle verbs excepted). An interrupted transfer releases the lock; a stranded one has a Continue here escape hatch, offered only while the transfer never landed. A completed handoff can no longer be aborted or repointed.
  • Old tip tagged and dirty worktrees stashed (root and destination worktree independently) before any pointer moves; patches dry-run first; a failed apply rolls the repository back and cleans up everything it extracted. Rollback metadata (pre-tag, stashes, apply directory) is persisted before the patch lands, so startup recovery can restore the tree from any crash point — including a first arrival that died before its thread projection existed.
  • A busy thread is interrupted first and the transfer starts when it goes idle; the depart command re-checks for active runs under the dispatch lock, so a racing message cannot slip a live run past the snapshot.
  • Payload ceilings (200 MB warn / 1 GB refuse) enforced during prepare and again per chunk on the receiving side. First hops cut the bundle against the repo's remote-tracking refs; a fully pushed branch ships no bundle at all.
  • Untracked files that would overwrite tracked files on the receiver refuse the hop (checked on real extracted paths in a staging directory, immune to path tricks). ThreadHandoffId is schema-restricted to a URL- and git-ref-safe alphabet.

What travels

Branch + unpushed commits (git bundle, checkpoint refs ride along so revert keeps working), tracked changes (binary patch), untracked files (null-delimited tar), the conversation (turn items + covered run ordinals), attachments (original names, which the items reference), terminal scrollback (restored on first open like after a restart), workspace shape (worktree threads land in worktrees — reuse the branch's worktree or provision one, detached when git forbids attaching). A target that lacks the repository entirely clones it from the bundle — no remote, credentials, or network needed on the receiving machine.

How it moves

Manifest inline, large parts content-addressed and fetched by digest in resumable 4 MB chunks; the receiver rejects a chunk that does not continue the staged bytes and treats an exact re-send of already-staged bytes as a no-op, so interrupted uploads resume. The client brokers the copy — the only component authenticated to both environments — and only offers targets that are connected and advertise the capability. Arrivals go through the event sink, so the thread appears in every connected client's sidebar the moment it lands.

Lineage, not direction

Each hop records the previous hop in a lineage table; a return trip is an ordinary hop toward an environment already in the chain, and the receiver revives the prior thread pair even when the client no longer remembers it — one conversation, one sidebar row, wherever it currently runs. A caller-named return target is validated against that lineage before anything is applied. Provenance survives round trips: every arrival keeps a link recording where the thread came from. Lifecycle actions (delete, archive, rename) apply to the pair once.

Version skew

threadHandoff capability follows the threadSettlement contract: absent means the environment is not offered as a destination.

Verification

  • contracts 266 · client-runtime 621 · web 118 · server suites green (orchestration-v2, persistence, environment, ws, startup, terminal) · monorepo typecheck, lint, fmt clean.
  • New coverage: tip-classification table, ref-name sanitization, full dispatch-path depart/refuse/complete/abort with reason surfacing, payload ceilings, chunk windows, sidebar pair-visibility rules, migration idempotency and mid-apply recovery.
  • Live smoke: repeated MacBook ↔ Linux server transfers of real threads, including the failure modes found and fixed along the way (full-history bundle timeout, empty bundle on a fully pushed branch, missing dispatch routing, JSON codec gap on the handoff timestamp, arrivals invisible to connected clients, post-push navigation, pair dedup after round trips).
  • Code review: all Macroscope findings across the review rounds addressed and resolved, including the crash-recovery, payload-abuse, and single-live-side race classes.

Related

Migrations

047 (orchestration_v2_thread_handoffs lineage table), 048 (apply_cwd column for projection-independent crash recovery) and 049 (root_stash_ref/root_cwd so root and worktree stashes recover independently). 047 was renumbered from 046 after the base branch inserted a migration.

Note

Add thread handoff to move a running thread between devices

  • Introduces an end-to-end thread handoff system allowing a running thread to be transferred between execution environments (devices), preserving git state, conversation history, terminals, and attachments.
  • Adds OrchestrationV2HandoffBundleV1 manifest contract, typed part kinds (git-bundle, tracked-patch, untracked-tar, etc.), and versioned DB migrations (047–050) for the orchestration_v2_thread_handoffs table.
  • Exposes readHandoffPart/writeHandoffPart HTTP endpoints on the environment API with offset-validated, idempotent chunked transfer and a 4 MB chunk size.
  • Adds prepareThreadHandoff and receiveThreadHandoff WebSocket RPCs (requiring AuthOrchestrationOperateScope) handled in the server WS layer and wired into production startup with a handoff recovery phase.
  • The web UI gains a ThreadHandoffDialog for progress-tracked moves, a banner for threads running elsewhere (with pull-back and continue-here actions), and sidebar deduplication hiding the 'away' copy when the 'here' peer is visible.
  • The mobile UI gains a ThreadHandoffBanner to surface away threads and resume them locally.
  • Archiving, unarchiving, and deleting a thread now best-effort mirrors the operation to the handoff peer thread.
  • Risk: handoff recovery runs at server startup; interrupted handoffs from a prior session are automatically resumed, which may replay partial git or file operations.

Macroscope summarized 0250f02.

@coderabbitai

coderabbitaiBot commented Aug 6, 2026

Copy link
Copy Markdown

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 Plus

Run ID: f38d45d4-abf4-498e-bf1f-f44233f84eeb

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@macroscopeappmacroscopeappBot left a comment

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.

One finding on the new handoff error contract; the migration, its tests, and the bundle schemas otherwise follow the Effect conventions (subpath namespace imports, no service-instance injection, no ManagedRuntime/runPromise, no Layer.succeed shortcuts).

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/contracts/src/orchestrationV2.ts Outdated

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review of the two new service modules. Three findings, all in apps/server/src/orchestration-v2/. Earlier feedback on OrchestrationV2HandoffError.message still stands and is not repeated here.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
@Bil0000Bil0000 changed the title feat(contracts): thread handoff bundle contractfeat(orchestrator): move a running thread between devicesAug 6, 2026
Comment threadpackages/client-runtime/src/state/threadHandoffTransfer.ts
Comment threadapps/web/src/components/ThreadHandoffDialog.tsx
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadpackages/contracts/src/environmentHttp.ts
Comment threadapps/web/src/components/BranchToolbar.tsx
juliusmarmingeand others added 23 commits August 7, 2026 13:17
Co-authored-by: codex <codex@users.noreply.github.com>
- Document the target orchestration graph, IDs, lifecycles, and capability model
- Add Codex app-server probe fixtures and update the probe test harness
- Introduce orchestration v2 service interfaces and error types
- Add replay runtime, fixtures, and integration coverage
- Update shared contracts and probe transcripts
Co-authored-by: codex <codex@users.noreply.github.com>
- Add Codex adapter and replay harness wiring
- Introduce in-memory orchestration projections and provider registry
- Expand orchestration contracts for turn and runtime events
Co-authored-by: codex <codex@users.noreply.github.com>
- Add context transfer IDs, schemas, and projections
- Support cheap fork creation and Codex native fork rollback
- Cover fork idempotency and replay behavior in tests
- Track remaining projection, context transfer, rollback, capability, and subagent work
- Clarify current V2 baseline and debugger-only follow-ups
- Map fork and merge-back turns into stored handoffs and transfer resolutions
- Add shell snapshot projection support plus coverage tests
- Update replay fixtures and web contracts for the new turn flow
Co-authored-by: codex <codex@users.noreply.github.com>
- Move Codex replay recording into `apps/server`
- Add Claude Agent SDK replay fixtures and test harness
- Update orchestration-v2 fixture scenarios and docs
- Move Claude provider runtime logic into its own module
- Share the SDK query runner between live and replay paths
- Add replay driver error wrapping for unexpected failures
- persist the selected model on run records
- surface run model selection in the debug UI
- update replay fixtures and contracts for the new field
- Record Claude SDK transcripts across multiple prompts and restart/query modes
- Add approval and tool-call replay coverage for new orchestration fixtures
- Update Claude adapter testkit to model open/prompt/permission frames
- Derive Claude SDK query options from runtime policy
- Add read-only replay fixture and policy mapping tests
- Reuse shared approval-policy fixtures across orchestrator tests
Co-authored-by: codex <codex@users.noreply.github.com>
- add active steering and interrupt-restart replay fixtures
- update Claude adapter/orchestrator turn handling for steering
- refresh replay and integration test coverage
- add interrupt and mid-tool replay fixtures for Claude and Codex
- log Claude Agent SDK protocol frames to native event traces
- project Codex commandExecution start events into orchestration updates
- Map Cursor SDK agents and runs to V2 thread and turn lifecycles
- Update MCP capability, tool, and testing guidance for SDK-based injection
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Record provider fixtures for continued forks and sibling merge-backs, exercise context survival end to end, and stop completed run listeners from consuming later provider events.
Co-authored-by: codex <codex@users.noreply.github.com>
Allow a pending merge-back to be consumed while changing providers and deliver both the provider-switch history and fork delta to the selected provider.
Co-authored-by: codex <codex@users.noreply.github.com>
Send full canonical target-thread context when a provider switch consumes a merge-back, and cover returning to an existing provider thread after a cross-provider fork.
Co-authored-by: codex <codex@users.noreply.github.com>
CI enforces preferSchemaOverJson; the created-worktree recovery path and
its test read the stored manifest with JSON.parse. Both now go through
Schema.fromJsonString(OrchestrationV2HandoffBundleV1), the same codec
that wrote the column.
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts

@macroscopeappmacroscopeappBot left a comment

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.

Reviewed the new Effect service modules (ThreadHandoffGit, ThreadHandoffService) and their call sites against the service conventions. The service definitions, make/layer exports, dependency acquisition and error modelling all look consistent with the conventions; the remaining findings are service-boundary imports in the two new test files that alias layer and erase the module namespace.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.test.ts Outdated
- An unreadable attachments directory now fails the prepare instead of
silently shipping a conversation whose attachments never travelled;
a missing directory still just means no attachments
- recordHop keeps rollback metadata when the same attempt records its
arrival; startup recovery finishes the stash pops for an arrived hop
that stopped before handing the receiver's changes back
- Import the handoff modules as namespaces in the new tests, matching
every production consumer
@Bil0000
Bil0000 marked this pull request as ready for review August 8, 2026 18:12
juliusmarmingeand others added 2 commits August 10, 2026 03:14
- Keep success feedback visible in the Git action control for 10 seconds
- Move the running elapsed timer into the panel menu slot
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 5 times, most recently from 5aca742 to 77168d0CompareAugust 14, 2026 09:27
@Bil0000
Bil0000 marked this pull request as draft August 14, 2026 11:17
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 14, 2026
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 519c42a to 4c55679CompareAugust 17, 2026 10:28
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 13 times, most recently from ceea97b to d2f1f51CompareSeptember 2, 2026 18:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@Bil0000@juliusmarminge@maria-rcks@mwolson@PixPMusic@nsxdavid@Yusuf007R
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(orchestrator): move a running thread between devices - #5544

Draft
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff
Draft

feat(orchestrator): move a running thread between devices#5544
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff

Conversation

@Bil0000

@Bil0000Bil0000 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Closes#5363.

Stacked on #2829 (t3code/codex-turn-mapping). main and the orchestrator branch share no common ancestor; every primitive this builds on exists only there.

What this does

Run on picks the machine a thread starts on and locks once it has a message — a thread is born on one device and dies there. This makes that reversible: pick another connected environment from the same control, and the conversation, the provider continuation, and the git working state move there. Pull it back later from the same control, which shows where the thread originally ran.

Verified live: real threads moved repeatedly between a MacBook (desktop app) and a Linux server and continued on each side, through the actual relay-free client-brokered transfer — including round trips, pull-backs, and the failure modes listed under Verification.

The safety model

  • A branch tip only ever moves to a descendant of itself, on either machine. Diverged or unrelated history parks the sender's commits at refs/handoff/<environment>/<handoff>/<branch> and stops with both machines untouched.
  • Exactly one side is live.thread.handoff.depart locks the giving side before any bundle is applied anywhere; mutations on an away thread are refused in the orchestrator (a small allowlist of local-copy lifecycle verbs excepted). An interrupted transfer releases the lock; a stranded one has a Continue here escape hatch, offered only while the transfer never landed. A completed handoff can no longer be aborted or repointed.
  • Old tip tagged and dirty worktrees stashed (root and destination worktree independently) before any pointer moves; patches dry-run first; a failed apply rolls the repository back and cleans up everything it extracted. Rollback metadata (pre-tag, stashes, apply directory) is persisted before the patch lands, so startup recovery can restore the tree from any crash point — including a first arrival that died before its thread projection existed.
  • A busy thread is interrupted first and the transfer starts when it goes idle; the depart command re-checks for active runs under the dispatch lock, so a racing message cannot slip a live run past the snapshot.
  • Payload ceilings (200 MB warn / 1 GB refuse) enforced during prepare and again per chunk on the receiving side. First hops cut the bundle against the repo's remote-tracking refs; a fully pushed branch ships no bundle at all.
  • Untracked files that would overwrite tracked files on the receiver refuse the hop (checked on real extracted paths in a staging directory, immune to path tricks). ThreadHandoffId is schema-restricted to a URL- and git-ref-safe alphabet.

What travels

Branch + unpushed commits (git bundle, checkpoint refs ride along so revert keeps working), tracked changes (binary patch), untracked files (null-delimited tar), the conversation (turn items + covered run ordinals), attachments (original names, which the items reference), terminal scrollback (restored on first open like after a restart), workspace shape (worktree threads land in worktrees — reuse the branch's worktree or provision one, detached when git forbids attaching). A target that lacks the repository entirely clones it from the bundle — no remote, credentials, or network needed on the receiving machine.

How it moves

Manifest inline, large parts content-addressed and fetched by digest in resumable 4 MB chunks; the receiver rejects a chunk that does not continue the staged bytes and treats an exact re-send of already-staged bytes as a no-op, so interrupted uploads resume. The client brokers the copy — the only component authenticated to both environments — and only offers targets that are connected and advertise the capability. Arrivals go through the event sink, so the thread appears in every connected client's sidebar the moment it lands.

Lineage, not direction

Each hop records the previous hop in a lineage table; a return trip is an ordinary hop toward an environment already in the chain, and the receiver revives the prior thread pair even when the client no longer remembers it — one conversation, one sidebar row, wherever it currently runs. A caller-named return target is validated against that lineage before anything is applied. Provenance survives round trips: every arrival keeps a link recording where the thread came from. Lifecycle actions (delete, archive, rename) apply to the pair once.

Version skew

threadHandoff capability follows the threadSettlement contract: absent means the environment is not offered as a destination.

Verification

  • contracts 266 · client-runtime 621 · web 118 · server suites green (orchestration-v2, persistence, environment, ws, startup, terminal) · monorepo typecheck, lint, fmt clean.
  • New coverage: tip-classification table, ref-name sanitization, full dispatch-path depart/refuse/complete/abort with reason surfacing, payload ceilings, chunk windows, sidebar pair-visibility rules, migration idempotency and mid-apply recovery.
  • Live smoke: repeated MacBook ↔ Linux server transfers of real threads, including the failure modes found and fixed along the way (full-history bundle timeout, empty bundle on a fully pushed branch, missing dispatch routing, JSON codec gap on the handoff timestamp, arrivals invisible to connected clients, post-push navigation, pair dedup after round trips).
  • Code review: all Macroscope findings across the review rounds addressed and resolved, including the crash-recovery, payload-abuse, and single-live-side race classes.

Related

Migrations

047 (orchestration_v2_thread_handoffs lineage table), 048 (apply_cwd column for projection-independent crash recovery) and 049 (root_stash_ref/root_cwd so root and worktree stashes recover independently). 047 was renumbered from 046 after the base branch inserted a migration.

Note

Add thread handoff to move a running thread between devices

  • Introduces an end-to-end thread handoff system allowing a running thread to be transferred between execution environments (devices), preserving git state, conversation history, terminals, and attachments.
  • Adds OrchestrationV2HandoffBundleV1 manifest contract, typed part kinds (git-bundle, tracked-patch, untracked-tar, etc.), and versioned DB migrations (047–050) for the orchestration_v2_thread_handoffs table.
  • Exposes readHandoffPart/writeHandoffPart HTTP endpoints on the environment API with offset-validated, idempotent chunked transfer and a 4 MB chunk size.
  • Adds prepareThreadHandoff and receiveThreadHandoff WebSocket RPCs (requiring AuthOrchestrationOperateScope) handled in the server WS layer and wired into production startup with a handoff recovery phase.
  • The web UI gains a ThreadHandoffDialog for progress-tracked moves, a banner for threads running elsewhere (with pull-back and continue-here actions), and sidebar deduplication hiding the 'away' copy when the 'here' peer is visible.
  • The mobile UI gains a ThreadHandoffBanner to surface away threads and resume them locally.
  • Archiving, unarchiving, and deleting a thread now best-effort mirrors the operation to the handoff peer thread.
  • Risk: handoff recovery runs at server startup; interrupted handoffs from a prior session are automatically resumed, which may replay partial git or file operations.

Macroscope summarized 0250f02.

@coderabbitai

coderabbitaiBot commented Aug 6, 2026

Copy link
Copy Markdown

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 Plus

Run ID: f38d45d4-abf4-498e-bf1f-f44233f84eeb

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@macroscopeappmacroscopeappBot left a comment

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.

One finding on the new handoff error contract; the migration, its tests, and the bundle schemas otherwise follow the Effect conventions (subpath namespace imports, no service-instance injection, no ManagedRuntime/runPromise, no Layer.succeed shortcuts).

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/contracts/src/orchestrationV2.ts Outdated

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review of the two new service modules. Three findings, all in apps/server/src/orchestration-v2/. Earlier feedback on OrchestrationV2HandoffError.message still stands and is not repeated here.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
@Bil0000Bil0000 changed the title feat(contracts): thread handoff bundle contractfeat(orchestrator): move a running thread between devicesAug 6, 2026
Comment threadpackages/client-runtime/src/state/threadHandoffTransfer.ts
Comment threadapps/web/src/components/ThreadHandoffDialog.tsx
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadpackages/contracts/src/environmentHttp.ts
Comment threadapps/web/src/components/BranchToolbar.tsx
juliusmarmingeand others added 23 commits August 7, 2026 13:17
Co-authored-by: codex <codex@users.noreply.github.com>
- Document the target orchestration graph, IDs, lifecycles, and capability model
- Add Codex app-server probe fixtures and update the probe test harness
- Introduce orchestration v2 service interfaces and error types
- Add replay runtime, fixtures, and integration coverage
- Update shared contracts and probe transcripts
Co-authored-by: codex <codex@users.noreply.github.com>
- Add Codex adapter and replay harness wiring
- Introduce in-memory orchestration projections and provider registry
- Expand orchestration contracts for turn and runtime events
Co-authored-by: codex <codex@users.noreply.github.com>
- Add context transfer IDs, schemas, and projections
- Support cheap fork creation and Codex native fork rollback
- Cover fork idempotency and replay behavior in tests
- Track remaining projection, context transfer, rollback, capability, and subagent work
- Clarify current V2 baseline and debugger-only follow-ups
- Map fork and merge-back turns into stored handoffs and transfer resolutions
- Add shell snapshot projection support plus coverage tests
- Update replay fixtures and web contracts for the new turn flow
Co-authored-by: codex <codex@users.noreply.github.com>
- Move Codex replay recording into `apps/server`
- Add Claude Agent SDK replay fixtures and test harness
- Update orchestration-v2 fixture scenarios and docs
- Move Claude provider runtime logic into its own module
- Share the SDK query runner between live and replay paths
- Add replay driver error wrapping for unexpected failures
- persist the selected model on run records
- surface run model selection in the debug UI
- update replay fixtures and contracts for the new field
- Record Claude SDK transcripts across multiple prompts and restart/query modes
- Add approval and tool-call replay coverage for new orchestration fixtures
- Update Claude adapter testkit to model open/prompt/permission frames
- Derive Claude SDK query options from runtime policy
- Add read-only replay fixture and policy mapping tests
- Reuse shared approval-policy fixtures across orchestrator tests
Co-authored-by: codex <codex@users.noreply.github.com>
- add active steering and interrupt-restart replay fixtures
- update Claude adapter/orchestrator turn handling for steering
- refresh replay and integration test coverage
- add interrupt and mid-tool replay fixtures for Claude and Codex
- log Claude Agent SDK protocol frames to native event traces
- project Codex commandExecution start events into orchestration updates
- Map Cursor SDK agents and runs to V2 thread and turn lifecycles
- Update MCP capability, tool, and testing guidance for SDK-based injection
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Record provider fixtures for continued forks and sibling merge-backs, exercise context survival end to end, and stop completed run listeners from consuming later provider events.
Co-authored-by: codex <codex@users.noreply.github.com>
Allow a pending merge-back to be consumed while changing providers and deliver both the provider-switch history and fork delta to the selected provider.
Co-authored-by: codex <codex@users.noreply.github.com>
Send full canonical target-thread context when a provider switch consumes a merge-back, and cover returning to an existing provider thread after a cross-provider fork.
Co-authored-by: codex <codex@users.noreply.github.com>
CI enforces preferSchemaOverJson; the created-worktree recovery path and
its test read the stored manifest with JSON.parse. Both now go through
Schema.fromJsonString(OrchestrationV2HandoffBundleV1), the same codec
that wrote the column.
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts

@macroscopeappmacroscopeappBot left a comment

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.

Reviewed the new Effect service modules (ThreadHandoffGit, ThreadHandoffService) and their call sites against the service conventions. The service definitions, make/layer exports, dependency acquisition and error modelling all look consistent with the conventions; the remaining findings are service-boundary imports in the two new test files that alias layer and erase the module namespace.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.test.ts Outdated
- An unreadable attachments directory now fails the prepare instead of
silently shipping a conversation whose attachments never travelled;
a missing directory still just means no attachments
- recordHop keeps rollback metadata when the same attempt records its
arrival; startup recovery finishes the stash pops for an arrived hop
that stopped before handing the receiver's changes back
- Import the handoff modules as namespaces in the new tests, matching
every production consumer
@Bil0000
Bil0000 marked this pull request as ready for review August 8, 2026 18:12
juliusmarmingeand others added 2 commits August 10, 2026 03:14
- Keep success feedback visible in the Git action control for 10 seconds
- Move the running elapsed timer into the panel menu slot
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 5 times, most recently from 5aca742 to 77168d0CompareAugust 14, 2026 09:27
@Bil0000
Bil0000 marked this pull request as draft August 14, 2026 11:17
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 14, 2026
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 519c42a to 4c55679CompareAugust 17, 2026 10:28
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 13 times, most recently from ceea97b to d2f1f51CompareSeptember 2, 2026 18:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@Bil0000@juliusmarminge@maria-rcks@mwolson@PixPMusic@nsxdavid@Yusuf007R
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(orchestrator): move a running thread between devices - #5544

Draft
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff
Draft

feat(orchestrator): move a running thread between devices#5544
Bil0000 wants to merge 262 commits into
pingdotgg:t3code/codex-turn-mappingfrom
Bil0000:feat/thread-handoff

Conversation

@Bil0000

@Bil0000Bil0000 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Closes#5363.

Stacked on #2829 (t3code/codex-turn-mapping). main and the orchestrator branch share no common ancestor; every primitive this builds on exists only there.

What this does

Run on picks the machine a thread starts on and locks once it has a message — a thread is born on one device and dies there. This makes that reversible: pick another connected environment from the same control, and the conversation, the provider continuation, and the git working state move there. Pull it back later from the same control, which shows where the thread originally ran.

Verified live: real threads moved repeatedly between a MacBook (desktop app) and a Linux server and continued on each side, through the actual relay-free client-brokered transfer — including round trips, pull-backs, and the failure modes listed under Verification.

The safety model

  • A branch tip only ever moves to a descendant of itself, on either machine. Diverged or unrelated history parks the sender's commits at refs/handoff/<environment>/<handoff>/<branch> and stops with both machines untouched.
  • Exactly one side is live.thread.handoff.depart locks the giving side before any bundle is applied anywhere; mutations on an away thread are refused in the orchestrator (a small allowlist of local-copy lifecycle verbs excepted). An interrupted transfer releases the lock; a stranded one has a Continue here escape hatch, offered only while the transfer never landed. A completed handoff can no longer be aborted or repointed.
  • Old tip tagged and dirty worktrees stashed (root and destination worktree independently) before any pointer moves; patches dry-run first; a failed apply rolls the repository back and cleans up everything it extracted. Rollback metadata (pre-tag, stashes, apply directory) is persisted before the patch lands, so startup recovery can restore the tree from any crash point — including a first arrival that died before its thread projection existed.
  • A busy thread is interrupted first and the transfer starts when it goes idle; the depart command re-checks for active runs under the dispatch lock, so a racing message cannot slip a live run past the snapshot.
  • Payload ceilings (200 MB warn / 1 GB refuse) enforced during prepare and again per chunk on the receiving side. First hops cut the bundle against the repo's remote-tracking refs; a fully pushed branch ships no bundle at all.
  • Untracked files that would overwrite tracked files on the receiver refuse the hop (checked on real extracted paths in a staging directory, immune to path tricks). ThreadHandoffId is schema-restricted to a URL- and git-ref-safe alphabet.

What travels

Branch + unpushed commits (git bundle, checkpoint refs ride along so revert keeps working), tracked changes (binary patch), untracked files (null-delimited tar), the conversation (turn items + covered run ordinals), attachments (original names, which the items reference), terminal scrollback (restored on first open like after a restart), workspace shape (worktree threads land in worktrees — reuse the branch's worktree or provision one, detached when git forbids attaching). A target that lacks the repository entirely clones it from the bundle — no remote, credentials, or network needed on the receiving machine.

How it moves

Manifest inline, large parts content-addressed and fetched by digest in resumable 4 MB chunks; the receiver rejects a chunk that does not continue the staged bytes and treats an exact re-send of already-staged bytes as a no-op, so interrupted uploads resume. The client brokers the copy — the only component authenticated to both environments — and only offers targets that are connected and advertise the capability. Arrivals go through the event sink, so the thread appears in every connected client's sidebar the moment it lands.

Lineage, not direction

Each hop records the previous hop in a lineage table; a return trip is an ordinary hop toward an environment already in the chain, and the receiver revives the prior thread pair even when the client no longer remembers it — one conversation, one sidebar row, wherever it currently runs. A caller-named return target is validated against that lineage before anything is applied. Provenance survives round trips: every arrival keeps a link recording where the thread came from. Lifecycle actions (delete, archive, rename) apply to the pair once.

Version skew

threadHandoff capability follows the threadSettlement contract: absent means the environment is not offered as a destination.

Verification

  • contracts 266 · client-runtime 621 · web 118 · server suites green (orchestration-v2, persistence, environment, ws, startup, terminal) · monorepo typecheck, lint, fmt clean.
  • New coverage: tip-classification table, ref-name sanitization, full dispatch-path depart/refuse/complete/abort with reason surfacing, payload ceilings, chunk windows, sidebar pair-visibility rules, migration idempotency and mid-apply recovery.
  • Live smoke: repeated MacBook ↔ Linux server transfers of real threads, including the failure modes found and fixed along the way (full-history bundle timeout, empty bundle on a fully pushed branch, missing dispatch routing, JSON codec gap on the handoff timestamp, arrivals invisible to connected clients, post-push navigation, pair dedup after round trips).
  • Code review: all Macroscope findings across the review rounds addressed and resolved, including the crash-recovery, payload-abuse, and single-live-side race classes.

Related

Migrations

047 (orchestration_v2_thread_handoffs lineage table), 048 (apply_cwd column for projection-independent crash recovery) and 049 (root_stash_ref/root_cwd so root and worktree stashes recover independently). 047 was renumbered from 046 after the base branch inserted a migration.

Note

Add thread handoff to move a running thread between devices

  • Introduces an end-to-end thread handoff system allowing a running thread to be transferred between execution environments (devices), preserving git state, conversation history, terminals, and attachments.
  • Adds OrchestrationV2HandoffBundleV1 manifest contract, typed part kinds (git-bundle, tracked-patch, untracked-tar, etc.), and versioned DB migrations (047–050) for the orchestration_v2_thread_handoffs table.
  • Exposes readHandoffPart/writeHandoffPart HTTP endpoints on the environment API with offset-validated, idempotent chunked transfer and a 4 MB chunk size.
  • Adds prepareThreadHandoff and receiveThreadHandoff WebSocket RPCs (requiring AuthOrchestrationOperateScope) handled in the server WS layer and wired into production startup with a handoff recovery phase.
  • The web UI gains a ThreadHandoffDialog for progress-tracked moves, a banner for threads running elsewhere (with pull-back and continue-here actions), and sidebar deduplication hiding the 'away' copy when the 'here' peer is visible.
  • The mobile UI gains a ThreadHandoffBanner to surface away threads and resume them locally.
  • Archiving, unarchiving, and deleting a thread now best-effort mirrors the operation to the handoff peer thread.
  • Risk: handoff recovery runs at server startup; interrupted handoffs from a prior session are automatically resumed, which may replay partial git or file operations.

Macroscope summarized 0250f02.

@coderabbitai

coderabbitaiBot commented Aug 6, 2026

Copy link
Copy Markdown

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 Plus

Run ID: f38d45d4-abf4-498e-bf1f-f44233f84eeb

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@macroscopeappmacroscopeappBot left a comment

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.

One finding on the new handoff error contract; the migration, its tests, and the bundle schemas otherwise follow the Effect conventions (subpath namespace imports, no service-instance injection, no ManagedRuntime/runPromise, no Layer.succeed shortcuts).

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/contracts/src/orchestrationV2.ts Outdated

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review of the two new service modules. Three findings, all in apps/server/src/orchestration-v2/. Earlier feedback on OrchestrationV2HandoffError.message still stands and is not repeated here.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts Outdated
@Bil0000Bil0000 changed the title feat(contracts): thread handoff bundle contractfeat(orchestrator): move a running thread between devicesAug 6, 2026
Comment threadpackages/client-runtime/src/state/threadHandoffTransfer.ts
Comment threadapps/web/src/components/ThreadHandoffDialog.tsx
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadapps/server/src/orchestration-v2/http.ts Outdated
Comment threadpackages/contracts/src/environmentHttp.ts
Comment threadapps/web/src/components/BranchToolbar.tsx
juliusmarmingeand others added 23 commits August 7, 2026 13:17
Co-authored-by: codex <codex@users.noreply.github.com>
- Document the target orchestration graph, IDs, lifecycles, and capability model
- Add Codex app-server probe fixtures and update the probe test harness
- Introduce orchestration v2 service interfaces and error types
- Add replay runtime, fixtures, and integration coverage
- Update shared contracts and probe transcripts
Co-authored-by: codex <codex@users.noreply.github.com>
- Add Codex adapter and replay harness wiring
- Introduce in-memory orchestration projections and provider registry
- Expand orchestration contracts for turn and runtime events
Co-authored-by: codex <codex@users.noreply.github.com>
- Add context transfer IDs, schemas, and projections
- Support cheap fork creation and Codex native fork rollback
- Cover fork idempotency and replay behavior in tests
- Track remaining projection, context transfer, rollback, capability, and subagent work
- Clarify current V2 baseline and debugger-only follow-ups
- Map fork and merge-back turns into stored handoffs and transfer resolutions
- Add shell snapshot projection support plus coverage tests
- Update replay fixtures and web contracts for the new turn flow
Co-authored-by: codex <codex@users.noreply.github.com>
- Move Codex replay recording into `apps/server`
- Add Claude Agent SDK replay fixtures and test harness
- Update orchestration-v2 fixture scenarios and docs
- Move Claude provider runtime logic into its own module
- Share the SDK query runner between live and replay paths
- Add replay driver error wrapping for unexpected failures
- persist the selected model on run records
- surface run model selection in the debug UI
- update replay fixtures and contracts for the new field
- Record Claude SDK transcripts across multiple prompts and restart/query modes
- Add approval and tool-call replay coverage for new orchestration fixtures
- Update Claude adapter testkit to model open/prompt/permission frames
- Derive Claude SDK query options from runtime policy
- Add read-only replay fixture and policy mapping tests
- Reuse shared approval-policy fixtures across orchestrator tests
Co-authored-by: codex <codex@users.noreply.github.com>
- add active steering and interrupt-restart replay fixtures
- update Claude adapter/orchestrator turn handling for steering
- refresh replay and integration test coverage
- add interrupt and mid-tool replay fixtures for Claude and Codex
- log Claude Agent SDK protocol frames to native event traces
- project Codex commandExecution start events into orchestration updates
- Map Cursor SDK agents and runs to V2 thread and turn lifecycles
- Update MCP capability, tool, and testing guidance for SDK-based injection
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Record provider fixtures for continued forks and sibling merge-backs, exercise context survival end to end, and stop completed run listeners from consuming later provider events.
Co-authored-by: codex <codex@users.noreply.github.com>
Allow a pending merge-back to be consumed while changing providers and deliver both the provider-switch history and fork delta to the selected provider.
Co-authored-by: codex <codex@users.noreply.github.com>
Send full canonical target-thread context when a provider switch consumes a merge-back, and cover returning to an existing provider thread after a cross-provider fork.
Co-authored-by: codex <codex@users.noreply.github.com>
CI enforces preferSchemaOverJson; the created-worktree recovery path and
its test read the stored manifest with JSON.parse. Both now go through
Schema.fromJsonString(OrchestrationV2HandoffBundleV1), the same codec
that wrote the column.
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts
Comment threadapps/server/src/orchestration-v2/ThreadHandoffService.ts

@macroscopeappmacroscopeappBot left a comment

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.

Reviewed the new Effect service modules (ThreadHandoffGit, ThreadHandoffService) and their call sites against the service conventions. The service definitions, make/layer exports, dependency acquisition and error modelling all look consistent with the conventions; the remaining findings are service-boundary imports in the two new test files that alias layer and erase the module namespace.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ThreadHandoffGit.test.ts Outdated
- An unreadable attachments directory now fails the prepare instead of
silently shipping a conversation whose attachments never travelled;
a missing directory still just means no attachments
- recordHop keeps rollback metadata when the same attempt records its
arrival; startup recovery finishes the stash pops for an arrived hop
that stopped before handing the receiver's changes back
- Import the handoff modules as namespaces in the new tests, matching
every production consumer
@Bil0000
Bil0000 marked this pull request as ready for review August 8, 2026 18:12
juliusmarmingeand others added 2 commits August 10, 2026 03:14
- Keep success feedback visible in the Git action control for 10 seconds
- Move the running elapsed timer into the panel menu slot
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 5 times, most recently from 5aca742 to 77168d0CompareAugust 14, 2026 09:27
@Bil0000
Bil0000 marked this pull request as draft August 14, 2026 11:17
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 14, 2026
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 519c42a to 4c55679CompareAugust 17, 2026 10:28
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 13 times, most recently from ceea97b to d2f1f51CompareSeptember 2, 2026 18:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@Bil0000@juliusmarminge@maria-rcks@mwolson@PixPMusic@nsxdavid@Yusuf007R