Skip to content

feat(desktop): continue a session in another harness - #1148

Draft
eric-liu-nvidia wants to merge 35 commits into
mainfrom
eric-liu/desktop-handoff-b
Draft

eric-liu-nvidia wants to merge 35 commits into
mainfrom
eric-liu/desktop-handoff-b

Conversation

@eric-liu-nvidia

@eric-liu-nvidia eric-liu-nvidia commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

What

Lets a session continue in the other harness: a Codex CLI session in Claude Code, or a Claude Code session in Codex CLI. The app renders a bounded Markdown hand-off from the source session's own transcript (goal, compaction summary, last turns, files, commands, last error; ≤ 32 KiB), shows the whole text for review, then writes it to ~/.switchyard/handoffs/<id>/HANDOFF.md (0700/0600) and starts the destination harness in the same directory with an instruction to read the file, summarize the state in five lines, and continue. The destination model does the summarizing under its own login; the app makes no model call and shares no credential.

Three ways to start one, all through the same controller actions:

  • Sessions page: a Switch harness card with one dropdown per harness (Codex session, Claude Code session), newest first, read from the harness's own transcripts, and one button under each: Switch to Claude Code and Switch to Codex. Route and account default to the first route that harness can use and the current login; Options overrides them per destination. A button that cannot work is greyed out with the reason under it (harness not detected, no usable route, no sessions). Sessions whose directory is not on this computer, such as remote sessions mirrored by the Claude desktop app, are left out and counted. The list reloads when the page opens and when the window regains focus. Recorded-session rows carry the same button, and a Recent hand-offs card lists what was written.
  • Menu bar popup: a Continue a session section with the newest three rows and the same action; choosing one opens the window on the review.
  • Terminal: switchyard-desktop handoff --to claude|codex finds the newest transcript recorded in the current directory, prints the review, asks for confirmation, writes the hand-off, and replaces itself with the destination harness in that terminal (--transcript, --session, --route, --account, --source-account, --show, --yes).
  • TUI: c continues the selected recorded session, x continues a transcript file, h lists the selected harness's sessions; the review ends with the full hand-off text and asks for HANDOFF.

Code:

  • controller.rs: PreviewHandoff / Handoff (preview token bound to server config, app settings, and the transcript's size + mtime; compatibility, login-mode and binary checks; warnings for a possibly running source, a branch mismatch, a cut excerpt; one review opens one session; attach returns the harness command instead of opening a terminal), RecentSessions + recent_sessions() (on-demand listing across both harnesses and saved accounts, each session with its default destination and, for managed worktrees, the session ID it keeps; sessions whose directory is not on this computer are counted as elsewhere and left out), Snapshot.handoffs plus a once-per-run purge of records older than seven days.
  • handoff_cli.rs (new): the handoff subcommand; Unix exec, Windows spawn-and-wait.
  • handoffs.rs (new): private store, symlink refusal, listing, age-based purge.
  • sessions.rs: open_harness is built from an Invocation so the review shows the exact command; new OpenRequest fields for developer_instructions (Codex), --append-system-prompt + --add-dir (Claude) and a first prompt; append_launch records the hand-off in the session receipt; script_account factored out of launch.
  • transcripts.rs: detect tells a rollout from a Claude transcript.
  • gui.rs: tray_continue command and session rows in the tray view (re-read at most every 30 s). ui/app.js, ui/tray.*: the card (two dropdowns and two buttons), the popup rows, and a new-session route that follows the chosen harness.

Stacked on #1115 (Elyas's stack, rebased onto its current tip) and includes the commits of #1079 (A1) and #1146 (A2); the diff shrinks to this feature once those land. The terminal app's hand-off reply arms follow the stack's current conventions (set_popup and the popup_matches guard). Draft until the macOS checks below are done.

Why

Switching harnesses today costs the whole conversation. This lets a user park a session in one CLI and pick it up in another through the app, without replaying transcripts across providers or crossing logins, which is the "context hand-off" step of the agentic-memory design.

Notes for reviewers

Start with controller.rs Action::PreviewHandoff / Action::Handoff and the privacy decisions: the preview reply carries the full HANDOFF.md text (the user sees exactly what is written), the excerpt never goes on the command line (only a < 1 KB pointer does), transcript sources never get a worktrees/<id>-settings/session.json so cleanup can never reach a real checkout, and a hand-off that fails to open removes its directory. Then sessions::invocation (the open_harness refactor keeps the generated script semantically identical; the existing fixture test still passes), recent_sessions(), handoff_cli.rs, and handoffs.rs.

An earlier revision took the GUI destination from the new-session form, which left a Codex route selected after switching the harness to Claude Code; the controller rejected it and the only feedback was a toast. Every entry point now computes a compatible default and checks it before the review.

Confirmed on macOS: Claude Code's --add-dir takes every following word as a directory, so the Claude command lists --add-dir first and keeps the prompt as the last word (sessions::tests::the_first_prompt_is_the_last_word_and_no_list_option_precedes_it). A hand-off into Claude Code launched the harness in the right directory through the claude route with the login forwarded, and a Codex → Claude Code switch from the Sessions card ran end to end: Claude Code read the hand-off on its own and answered with the summary.

Known gaps for follow-ups: Codex -c developer_instructions and the Seatbelt read of ~/.switchyard/handoffs/… are unverified on a real binary (a positional first prompt naming the file is always passed as the fallback); Pi and Codex app are not destinations; Windows passes the same arguments through TerminalJob but was not exercised; Claude's settings.json for a hand-off lives in the hand-off directory and is purged with it after seven days.

Evidence

  • cargo fmt --all -- --check, cargo clippy -p switchyard-desktop --all-targets -- -D warnings: clean.
  • cargo test -p switchyard-desktop: 15 lib + 31 bin + 6 integration tests pass. New: controller::tests::session_handoff_requires_an_unchanged_review_and_opens_the_other_harness (child-process fixture with stub codex/claude/terminal: both directions, wrong/missing token, stale transcript after preview, 0700/0600 modes, receipt launches[1].handoff, destination argv contains --add-dir/--settings/--append-system-prompt/developer_instructions and never the excerpt, no receipt for transcript sources, merged RecentSessions with per-session destinations and managed_id, a transcript recorded on another computer counted and left out), handoff_cli::tests::handoff_command_writes_the_file_and_runs_the_destination_here (the subcommand picks the rollout recorded in the current directory and execs a stub claude; argv, environment, cwd, 0600 file and record are checked), tui::tests::continue_keys_hand_off_the_selected_session_and_a_transcript_file, handoffs::tests::handoffs_are_private_listed_newest_first_and_purged_by_age; frontend tests hand-off previews and session listings read without taking the busy lock and a hand-off destination defaults to the first route its harness can use (need Node ≥ 18; not run on the Linux box).
  • Headless Chromium (Python Playwright with a mocked Tauri bridge) drove index.html and tray.html: each dropdown preselects the newest session of its harness, Switch to Claude Code opens the review and sends handoff with the managed session ID, Switch to Codex previews a transcript source, an incompatible route override and a missing harness grey the button out with the reason under it, a harness without sessions disables its dropdown, the list reloads on window focus, the new-session route follows the harness, and the popup forwards {source, destination} to tray_continue; zero page or console errors in every state.
  • Manual macOS pass, installed with cargo desktop install from this branch: Codex → Claude Code from the card works end to end. Still pending: Claude Code → Codex, the popup and the subcommand end to end, /var vs /private/var, quoting, origin split in the routing log.

🤖 Generated with Claude Code

@elyasmnvidian
elyasmnvidian force-pushed the eric-liu/desktop-handoff-b branch from 48eb9ce to c572cc6 Compare October 26, 2026 19:20
@elyasmnvidian
elyasmnvidian force-pushed the eric-liu/desktop-handoff-b branch 2 times, most recently from 288ff99 to 44b7b29 Compare October 26, 2026 21:11
Base automatically changed from emehtabuddin/s39-desktop-cargo-install to emehtabuddin/s34-desktop-gui-tray October 27, 2026 21:34
…ite_tokens

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
…rminal app

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
… app (#1095)

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
… app

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
…reporting

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
…figs

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
…rminal app

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
…ute flows

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
…1109)

* feat(desktop): add the Sessions page and recorded session inventory

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>

* feat(desktop): launch isolated worktree sessions from the terminal app (#1110)

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>

---------

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
…olation (#1112)

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
…ytics (#1138)

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
* test(desktop): verify the desktop app frontend and route drafts

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>

* docs(desktop): add the desktop and terminal app quickstart

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>

---------

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
* test(desktop): verify the desktop app frontend and route drafts

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>

* docs(desktop): add the desktop and terminal app quickstart

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>

* chore(desktop): simplify installation

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>

---------

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
… worktrees

Signed-off-by: Zengyuan Liu <zengyuanl@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Zengyuan Liu <zengyuanl@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
@eric-liu-nvidia
eric-liu-nvidia force-pushed the eric-liu/desktop-handoff-b branch from 07dedf2 to 7d980da Compare October 28, 2026 12:28
Signed-off-by: Zengyuan Liu <zengyuanl@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
…his terminal

Signed-off-by: Zengyuan Liu <zengyuanl@nvidia.com>
…e and the menu bar

Signed-off-by: Zengyuan Liu <zengyuanl@nvidia.com>
…ssions page

Signed-off-by: Zengyuan Liu <zengyuanl@nvidia.com>
… list

Signed-off-by: Zengyuan Liu <zengyuanl@nvidia.com>
@elyasmnvidian
elyasmnvidian added this pull request to stack #1181 October 29, 2026 23:22
Base automatically changed from emehtabuddin/s34-desktop-gui-tray to main October 30, 2026 18:15

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants