feat(relay-node): fork a session into a new spawned agent (spawn-fork:v1)

Merged
AG
2h ago
+873-25

Summary

Lets a person fork one of their agents' sessions from the Agent Relay phone app. The phone sends the existing node spawn action with a new fork_from: "<agent>" input. The node starts a new headless agent in the same folder from a copy of the source conversation, using each harness's native fork, so it really continues the session and doesn't just replay a summary. The original agent keeps running.

  • Claude: claude -p ... --session-id <new> --resume <source> --fork-session. The fork runs in the source transcript's cwd.
  • Codex: app-server thread/fork instead of thread/start. The fork runs in the source thread's directory. Archived or unowned threads are refused.
  • Capability: nodes advertise relay:spawn-fork:v1 (kind capacity) with metadata.harnesses and metadata.machine. Older nodes would ignore fork_from and start an empty agent, so clients must check for the capability first.
  • Who may fork: only the computer's verified owner (spawn_fork_refused otherwise). A fork may not name a different folder from its source (spawn_invalid_input).
  • Other errors: spawn_fork_not_found (the agent isn't on this node; clients try the next installation on the same machine), spawn_fork_unsupported, and spawn_fork_unavailable (the source lookup timed out after 10 seconds).
  • First message: the fork is told it is a fork of @source with its own name, followed by the optional task.

Based on main. It was stacked on #2139, which has since merged.

The iOS client is on AgentWorkforce/relay-desktop branch review/cursor-ios-fork-session (relay-desktop takes no pull requests; it lands through its review flow). Its agent-relay-node pin moves only after this merges.

Test Plan

  • Tests added/updated: wire (fork_from parsing, capability advertisement), spawn::fork (owner check, folder rule, not found, unsupported, timeout, first task), Claude arguments for --resume --fork-session, and Codex thread/fork against the fake app server
  • cargo test --lib -- spawn:: codex:: harness::claude harness::codex: 703 passed after the review fixes. cargo fmt --check is clean; clippy is clean on the touched files
  • Core-level tests for the owner check and the 10-second lookup timeout (follow-up)
  • Manual: claude -p --resume <src> --fork-session --session-id <new> produced a new session that remembered the source conversation
  • iOS UI tests (iPhone 17 Pro, iOS 27 simulator, offline fixture backend): the three placements, the full fork flow, and a computer that cannot fork all pass

Screenshots

Three candidate placements for the fork entry point (iOS). The header button is the current default:

1. Header button beside Call2. Actions menu (⋯)3. Swipe on the agent row

The flow:

Fork sheetForked agent continuesBoth agents in the rosterComputer on an older relay

Video of the whole iOS experience (96 seconds: a computer that can't fork, the full fork flow, then each placement): ios-fork-session-walkthrough.mp4

The recording uses the app's offline UI-test fixture backend, not a live node.


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.