Session Forking vs In‑Session Branching in Pi‑Web: Key Differences and Implementation

Session forking creates a new independent .jsonl file with its own sessionId, while in‑session branching keeps multiple conversation paths within a single file using parentId relationships to link messages.

Pi‑Web, the open-source conversation management framework in the agegr/pi-web repository, provides two distinct architectural patterns for exploring alternative conversation paths. Understanding how session forking and in‑session branching differ in storage mechanics, lifecycle handling, and UI representation helps developers choose the right approach for persisting conversation history.

What Is Session Forking?

Session forking creates a completely new conversation file that branches from a specific point in the original session. When you fork, the system generates a new .jsonl session file with a unique sessionId, copies the conversation history up to the fork point, and leaves the original session file untouched.

In lib/rpc-manager.ts (lines 393‑426), the "fork" command implements this logic:

// Server-side handling in rpc-manager.ts
case "fork":
  // Validates entryId and checks if session is persisted
  // Creates new file via SessionManager.createBranchedSession()
  // Destroys the original AgentSessionWrapper
  this.destroy();
  break;

The forked session maintains a reference to its origin through the parentSession header field, causing it to appear as a child node in the sidebar beneath the original session. Because forking creates a new file on disk, it requires the original session to be persisted before the operation can proceed.

What Is In‑Session Branching?

In‑session branching keeps all alternative conversation paths within the same .jsonl file. Instead of creating new files, this mechanism uses parentId relationships to link messages, allowing the conversation tree to have multiple "leaves" or endpoints.

The navigation logic lives in lib/rpc-manager.ts (lines 430‑436) under the "navigate_tree" command:

// Switching branches within the same session
case "navigate_tree":
  await inner.navigateTree(targetId, {});
  break;

The UI represents these branches as selectable leaves via the BranchNavigator component (components/BranchNavigator.tsx). When you switch branches, the system updates its internal cursor to point to the requested leaf without touching the file system, allowing you to fetch different conversation contexts via /api/sessions/[id]/context?leafId=.

Key Architectural Differences

File System Impact

Forking creates a new physical .jsonl file on disk. The system copies existing history up to the fork point using SessionManager.createBranchedSession(), resulting in two independent session files that can evolve separately.

Branching modifies only the internal state of the existing session. The SDK updates parentId pointers within the same file, meaning all branches share the same underlying storage and sessionId.

Session Lifecycle and State Management

When you fork a session, the AgentSessionWrapper for the original session is explicitly destroyed (this.destroy();) to prevent stale state from lingering in memory. Future requests instantiate a fresh wrapper for the new session file.

In contrast, in‑session branching preserves the existing wrapper because the session continues running in the same process. The "navigate_tree" command simply updates the internal cursor position without terminating the session context.

UI Representation

  • Forked sessions appear as separate rows in the sidebar, each with distinct session IDs and a parent-child visual hierarchy.
  • In‑session branches appear as tabs or selectable options within the same session view, managed by hooks/useAgentSession.ts (which tracks forkingEntryId for forks and leaf selection for branches).

How to Use Each Feature

Forking a Session (Client-Side)

To create a new independent session from a specific message:

// Triggered when user clicks the Fork button
await rpcSession.send({
  type: "fork",
  entryId: entryIdOfMessageToForkFrom, // The entry ID displayed in the UI
});

The server-side implementation in rpc-manager.ts validates the entry ID, ensures the session is persisted, and either creates an empty session (if forking before the first message) or copies the full history up to that point.

To switch between existing branches within the same session:

// Switch to a different leaf inside the current session
await rpcSession.send({
  type: "navigate_tree",
  targetId: leafIdToShow, // The entry ID of the desired branch
});

This calls inner.navigateTree() in the underlying SDK, updating the active leaf without creating new files or destroying the session wrapper.

Summary

  • Session forking creates new .jsonl files with independent sessionId values, destroys the original session wrapper, and displays forks as child nodes in the sidebar.
  • In‑session branching uses parentId relationships to maintain multiple paths within a single file, preserves the session wrapper, and displays branches as selectable leaves via the BranchNavigator component.
  • File operations differ significantly: forking duplicates history to a new file via createBranchedSession(), while branching updates internal cursors via navigateTree().
  • The "fork" and "navigate_tree" commands in lib/rpc-manager.ts serve as the primary RPC endpoints for these respective behaviors.

Frequently Asked Questions

When should I use session forking versus in‑session branching?

Use session forking when you need long‑term independence between conversation paths, such as creating a permanent snapshot for external review or starting a completely unrelated conversation thread that happens to share initial context. Use in‑session branching when exploring temporary "what‑if" scenarios or alternative responses within the same ongoing conversation, since it keeps related branches organized in one file without cluttering the session list.

Do forked sessions share data with their parent sessions?

No. Once created, a forked session operates as a completely independent entity with its own .jsonl file and sessionId. Changes made to the fork do not reflect in the parent session, and vice versa, because they are separate physical files managed by distinct AgentSessionWrapper instances.

Can I convert an in‑session branch into a forked session later?

Yes. You can convert a branch to a fork by using the "fork" command with the entryId of the branch's leaf node. This creates a new session file containing the history of that specific branch, effectively extracting it from the shared file into its own independent session.

Why does forking destroy the session wrapper while branching does not?

The this.destroy() call in the fork command (line 426 in rpc-manager.ts) prevents the original AgentSessionWrapper from maintaining stale references to the old session file. Since forking switches the client to an entirely new file, the old wrapper becomes irrelevant. Branching maintains the wrapper because you remain within the same file and session context, merely changing which leaf is currently active.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →