Fork vs In-Session Branching in Pi-Web: A Technical Comparison

Forking creates a new independent .jsonl session file with a unique sessionId, while in-session branching maintains multiple conversation paths within the same file using parentId relationships.

Pi-web, the open-source conversation management system, provides two distinct mechanisms for diverging from the main conversation thread. Understanding how fork vs in-session branching work in pi-web is essential for managing complex dialogue trees and maintaining organized session history. Both approaches handle conversation divergence differently at the architectural level, impacting file storage, session lifecycle, and UI representation.

Session Forking: Creating Independent Conversation Copies

Session forking generates a completely new session file that begins from a specific point in the conversation history. This mechanism is ideal when you need to persist a divergent conversation path as a standalone entity with its own lifecycle.

The Fork Command Implementation

The core logic resides in lib/rpc-manager.ts within the "fork" command handler (lines 393-426). When initiated, the system validates the entry ID and checks whether the current session is persisted. It then invokes SessionManager.createBranchedSession() to copy the existing conversation history up to the fork point into a new .jsonl file.

Key characteristics of the fork implementation:

  • New Session Identity: The forked session receives a fresh sessionId and stores a parentSession header field linking back to the original
  • File System Impact: Creates a physical copy of the conversation history up to the fork point in a separate .jsonl file
  • Lifecycle Management: The original AgentSessionWrapper is explicitly destroyed via this.destroy() to prevent stale state, forcing future requests to initialize a fresh wrapper for the new session
  • Sidebar Representation: Appears as a child node of the original session in the UI sidebar

Client-Side Fork Implementation

To initiate a fork from the client interface (such as when a user clicks the Fork button):

// Send a fork request from the UI
await rpcSession.send({
  type: "fork",
  entryId: entryIdOfMessageToForkFrom, // the entry id displayed in the UI
});

The server-side handling processes this request in lib/rpc-manager.ts (lines 393-426), creating the branched session file and managing the wrapper transition.

In-Session Branching: Navigating Within a Single File

In-session branching keeps all conversation variations within the original .jsonl file. This approach treats divergent paths as different "leaves" of the same conversation tree, allowing rapid switching without file system overhead.

The Navigate Tree Command

The branch navigation logic is implemented in lib/rpc-manager.ts (lines 430-436) under the "navigate_tree" command. This command forwards to inner.navigateTree(targetId, {}), which updates the internal cursor to point to the requested leaf message.

Key characteristics of in-session branching:

  • Single File Storage: All branches persist within the same .jsonl file using parentId relationships to track divergence points
  • No Wrapper Destruction: Unlike forking, branching does not destroy the AgentSessionWrapper because the session continues running in the same process
  • Context Retrieval: The UI fetches specific branch context via /api/sessions/[id]/context?leafId= endpoints
  • UI Representation: Branches appear as selectable "tabs" or leaves within the BranchNavigator component (components/BranchNavigator.tsx), not as separate sidebar entries

Switching Branches Programmatically

To navigate to a different branch within the current 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
});

The underlying SDK (SessionManager) updates the parentId relationships to reflect the new active leaf without touching the file system.

Key Architectural Differences

File System and Storage Semantics

Forking creates a new physical .jsonl file on disk, copying the conversation history up to the fork point. This results in duplicate storage for the shared history but complete isolation of subsequent conversation development.

In-session branching maintains all messages in a single file, using metadata fields (parentId) to track divergence relationships. This approach minimizes storage overhead but keeps all conversation data in one location.

Session Lifecycle and State Management

When forking, rpc-manager.ts explicitly destroys the original session wrapper to prevent state contamination between the parent and child sessions. This ensures the new session starts with a clean state manager.

In-session branching preserves the existing wrapper and simply updates the internal navigation cursor. The AgentSessionWrapper continues running, maintaining in-memory state across branch switches.

UI Integration Patterns

Forked sessions integrate with the sidebar as distinct child nodes linked via the parentSession header field. The hooks/useAgentSession.ts file manages UI state for forking operations through the forkingEntryId state variable.

In-session branches render within the BranchNavigator component, presenting different leaves as selectable options without cluttering the session sidebar. This provides a lightweight mechanism for exploring "what-if" scenarios within a single conversation context.

When to Fork vs Branch

Use Session Forking when you need to create a permanent, independent copy of a conversation that may evolve separately from the original, or when different users need to work on divergent paths simultaneously.

Use In-Session Branching when exploring temporary alternatives within a single conversation context, comparing different AI responses to the same prompt, or when storage efficiency and rapid context switching take priority over isolation.

Summary

  • Session forking creates a new .jsonl file with a unique sessionId, destroys the original AgentSessionWrapper, and appears as a child node in the sidebar.
  • In-session branching keeps all paths in one file using parentId relationships, preserves the session wrapper, and displays branches as selectable leaves in the BranchNavigator component.
  • The "fork" command in lib/rpc-manager.ts (lines 393-426) handles file creation via SessionManager.createBranchedSession().
  • The "navigate_tree" command in lib/rpc-manager.ts (lines 430-436) handles branch switching via inner.navigateTree().
  • Forking is persistent and resource-intensive; branching is lightweight and transient.

Frequently Asked Questions

Does forking delete the original session?

No, forking does not delete the original session. The source .jsonl file remains unchanged while the system creates a new independent file containing the forked history. The parentSession header field in the new file maintains a reference to the original session ID for organizational purposes.

Can I merge a forked session back into the original?

The pi-web source code does not provide a built-in merge mechanism for forked sessions. Since forking creates separate .jsonl files with independent sessionId values, merging would require manual file manipulation or custom implementation using the lib/session-reader.ts utilities to combine the divergent histories.

How does the UI represent forked sessions versus branches?

Forked sessions appear as separate rows in the sidebar, each displaying their own session ID and linked to the parent via the parentSession field. In-session branches appear as different "tabs" or selectable leaves within the BranchNavigator component inside the same session view, without creating new sidebar entries.

What happens to active connections during a fork operation?

During a fork, the server destroys the original AgentSessionWrapper instance to prevent stale state. This means any active connections to the original session wrapper are terminated, and subsequent requests initialize a fresh wrapper for the new forked session. In contrast, in-session branching maintains the active connection and simply updates the internal cursor position.

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 →