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

> Understand session forking vs in-session branching in Pi-Web. Learn how to manage conversation paths with independent files or parent links for effective data organization.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: deep-dive
- Published: 2026-07-26

---

**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`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) (lines 393‑426), the `"fork"` command implements this logic:

```typescript
// 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`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) (lines 430‑436) under the `"navigate_tree"` command:

```typescript
// 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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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:

```typescript
// 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`](https://github.com/agegr/pi-web/blob/main/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.

### Navigating Between Branches

To switch between existing branches within the same session:

```typescript
// 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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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.