Skip to content

Fork & compare

A fork is a new session that starts from a point in an existing one. It gets the conversation up to that point and the code as it stood at that point, on its own branch in its own worktree. The original keeps running untouched.

Fork once to try a different idea without losing the current one. Fork three times with different instructions to get three attempts at the same problem, then keep the best.

You can fork from any of these places:

From How
The session on screen ⌘⇧F (Ctrl+Shift+F off macOS), or Fork this session in the ⌘K palette.
The sidebar The ⑂ button on a session’s row.
The Fleet Select a session and press f.
A specific turn Open the Inspector’s Transcript tab and hover a message. ⑂ Fork on an assistant message forks after that turn. ⑂ Fork before on one of your prompts forks just before it.

The sidebar’s ⑂ is enabled once the session has started a conversation.

All of these open the same Fork dialog. It names the point you’re forking from, such as after turn 3 · “Add the retry wrapper”, or from the latest turn.

  • How many: choose 1, 2, 3 or 5, or type any number up to 8.
  • First message (optional): the fork’s opening prompt, sent once its agent is up.
  • Send the message right away: on by default. Turn it off to leave the message in the fork’s composer for you to send.

Press ⌘↵ to fork. With one fork, spwn opens the new session. With several, they open side by side in Compare.

⑂ Fork before on one of your own messages forks the conversation as it was just before you sent it. The prompt comes back pre-filled in the dialog, so you can edit it and send a different version. It’s the quickest way to ask “what if I’d said it differently?”

A fork from the latest turn resumes the parent’s full conversation, and its branch starts from the parent’s branch as it is now.

A fork from an earlier turn moves both halves back together:

  • The conversation. spwn writes a copy of the parent’s transcript cut off at the end of that turn, and the fork resumes the copy. The parent’s own history is never edited. An anchor in the middle of a turn is moved forward to the end of that turn, because a tool call without its result can’t be resumed. The copy is deleted once every fork has a transcript of its own.
  • The code. The fork’s worktree starts at the commit that turn left behind, not at the parent’s latest commit. spwn records the HEAD of each session when a turn’s hooks finish. For older turns it falls back to the spwn turn <id> commit subjects, and then to the nearest earlier turn that has either. If no turn up to that point changed anything, the fork starts where the parent started.

So a fork from turn 3 sees turn 3’s code and remembers turn 3’s conversation, not turn 9’s code with turn 3’s memory.

A fork’s base branch is its parent’s branch. It merges back into the session it came from, not straight into main. See Forks merge one rung up.

Set How many above 1 and every fork gets the same first message by default. The forks are named after the parent with a letter: auth-fix ⑂a, auth-fix ⑂b, auth-fix ⑂c. (A single fork is auth-fix ⑂.)

Untick Same prompt for every fork to give each fork its own row:

  • Title: optional. Leave it blank to keep the lettered name.
  • Agent: defaults to the parent’s agent. If you choose a different agent, that fork starts fresh, with no history. It gets the code at that turn, but another agent can’t read this one’s conversation.
  • Prompt: what this fork should try.
⑂a "Keep the old API and add an adapter."
⑂b "Change the API and update every caller."
⑂c "Find out whether we need this endpoint at all."

Forks made together share a batch. That’s what Compare opens, and what pick chooses among.

Forks nest under the session they came from. A root session has a ✦ and each fork has a ↳, indented one level per generation. A session with forks shows how many and has a twisty to collapse them.

The Inspector’s Overview tab draws the whole Fork family. Each row shows the fork point (@t3 means forked after turn 3), a ★ on the picked attempt, and what each one is doing. When there’s more than one attempt to compare, the header has a Compare N button.

Compare opens:

  • automatically, after you fork more than one at a time;
  • from Compare N in the Fork family;
  • from the Fleet: select a session and press c.

Each lane is one attempt. A lane shows:

  • the title and fork point;
  • its cost so far;
  • what it’s doing now, and its last reply;
  • ±N files, ↑ahead, and ↓behind its base;
  • ⚠ conflicts with base when merging it would conflict;
  • ◑ for another session changing the same files;
  • a diff.

The diff starts against the base. Switch it to any sibling to read how two attempts differ, instead of reading each one in full.

Check it merges & passes merges the attempt with its base in a scratch worktree and runs your session-integrate hooks on the result. A check that ran before the base moved is marked (stale). See Verify.

Key Action
h / ←, l / → Previous / next lane
p Pick the focused attempt
v Check it merges and passes
d Cycle the diff: against the base, then each sibling in turn
↵ / o Open the focused attempt
  1. Pick the attempt you want with p or its ☆. Picking one un-picks the others in the same batch.
  2. Land it with the Land button on its lane. This puts it in the land order, which syncs it with its base, hands any conflict back to its agent, runs the checks and merges it. The button says In the land order once it’s queued.
  3. Discard the others… in the Compare header deletes every attempt except the picked one. Each delete asks first, and names any unmerged commits or uncommitted changes it would throw away. Cancel one and the rest stop there.

Because a fork’s base is its parent’s branch, landing a fork merges it into the parent session’s branch. To get it into main, land the parent afterwards. For a fork, the Merge dialog says so and shows the full route, such as spwn/9b41e0d2 → spwn/3f9c2a1b → main. Folding each fork into its parent keeps every merge small.

If you want an attempt to go straight to main, fork from a session whose base is main. Or merge the fork’s branch by hand:

Terminal window
git checkout main
git merge spwn/9b41e0d2