Skip to content

Let agents park a fork of the chat when they find a tangent - #301

Open
frenchie4111 wants to merge 2 commits into
mainfrom
love-expiriment-chats-being-able
Open

Let agents park a fork of the chat when they find a tangent#301
frenchie4111 wants to merge 2 commits into
mainfrom
love-expiriment-chats-being-able

Conversation

@frenchie4111

Copy link
Copy Markdown
Collaborator

Summary

Agents routinely notice a second problem while answering the first. Today they have no sanctioned move: derail into it, or bury it in a closing bullet. Both lose it. create_worktree({forkConversation}) exists but explicitly tells agents not to use it for adjacent tangents, and jsonClaude:forkAt is wired only to the right-click menu and opens empty.

This adds a fork_chat MCP tool. The agent calls it mid-answer; Ness copies the caller's transcript as it stands into a fresh session id and stops there — no tab, no subprocess, nothing running. A parked fork is a jsonl on disk plus a card in the transcript at the exact point the agent had the thought. It only becomes a chat when the user clicks Open.

That asymmetry is the whole safety story: an autonomous fork that spawned a sibling agent into the same working tree would not be acceptable; one that leaves a note behind is.

UI

  • ForkCard, inline at the point of the thought — topic, the prompt the agent queued for itself, and an Open button. Filed as a text row so it can't get buried inside a collapsed tool group.
  • Opening adds a sibling tab in the parent's pane, resumes from the copied transcript, and sends the queued prompt as a new chat-fork automated turn (renders as "Forked Thread"). The fork gets guidance telling it the transcript above is inherited context, not its task.
  • A thin "N parked forks: …" strip above the composer, visible only while forks are unopened. Clicking a topic scrolls to its card rather than opening it — the decision stays where the agent made the note. Deliberately scoped to the conversation; a global forks inbox is the thing this is trying not to become.
  • Hard cap of 3 unopened forks per conversation; the 4th call is refused with a list of what's already parked.
  • Gated on the existing conversation-forking setting. When off, fork_chat is stripped from the tool list entirely rather than advertised and then rejected.

No slice

There is deliberately no state backing a parked fork. Everything is already durable and already synced:

what where
topic, prompt the tool_use input
fork session id the tool_result text (parseable marker)
"has it been opened" the panes tree — opening is what creates the tab

Panes persist across restart, so this stays correct with zero bookkeeping. A parallel record would be a second source of truth a restart could disagree with. The cap derives from the same walk.

Test plan

  • npm run typecheck
  • npx electron-vite build
  • npx vitest run — 14 new tests in src/shared/fork-chat.test.ts, 6 new in src/main/control-server.test.ts (55 pass in those two files). The 3 unrelated failures in a full parallel run (git-ops-state, worktree-watcher.integration) are load flakes — both pass in isolation, with and without this branch.
  • End-to-end click-through: agent calls fork_chat → card renders → Open promotes it to a sibling tab that resumes with the queued prompt. Not yet verified at runtime.
  • Cap refusal at the 4th unopened fork.
  • Tool absent from tools/list when conversation forking is disabled.

🤖 Generated with Claude Code

Agents routinely notice a second problem while answering the first, and
today they have no sanctioned move: derail into it, or bury it in a
closing bullet. The new fork_chat MCP tool copies the caller's transcript
into a second session and stops there — no tab, no subprocess, nothing
running until the user clicks the card. That asymmetry is the safety
story; an autonomous fork that spawned a sibling agent in the same
working tree would not be.

There is deliberately no slice behind a parked fork. The topic and prompt
live in the tool_use input, the fork's session id in the tool_result, and
"has it been opened" is answered by the panes tree — all already durable
and already synced, so a restart can't disagree with a parallel record.
The cap of 3 unopened forks per conversation derives from the same walk.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@frenchie4111
frenchie4111 force-pushed the love-expiriment-chats-being-able branch from 8b93a11 to c851b42 Compare August 27, 2026 13:19
The agent-initiated fork_chat tool only fires when the model happens to
notice a tangent, which is rare. This adds the user-initiated half: an
"ask in fork" button (Cmd/Ctrl+Alt+Enter) next to Send that copies the
conversation into a sibling tab, asks the draft there, and switches to
it — leaving the original untouched even mid-stream, so a side question
no longer costs an interrupt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.

1 participant