# How Pi Web Differentiates Fork and In‑Session Branches: Implementation Guide

> Understand how Pi Web differentiates Fork and In-Session branches. Learn implementation details for managing JSONL session files and navigating conversation paths.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: how-to-guide
- Published: 2026-07-30

---

**Pi Web distinguishes Forks as new `.jsonl` session files created via `SessionManager.createBranchedSession`, while In‑Session Branches are alternate conversation paths stored as leaf nodes within the same file, navigated via `AgentSession.navigateTree` without disk changes.**

The [agegr/pi-web](https://github.com/agegr/pi-web) codebase implements two distinct models for branching conversations. Understanding the technical differences between **Forks** and **In‑Session Branches** is essential for developers building custom RPC clients or extending the session management system.

## Core Architectural Differences

### Fork Creates a New Session File

A **Fork** generates a completely separate `.jsonl` session file that lives beside the original. When you fork from a specific entry point, the system either creates an empty session (if forking before the first message) or copies the conversation history up to the fork point using `SessionManager.createBranchedSession`.

The forked session maintains lineage through a `parentSession` header in its metadata, allowing the UI to render it as a child node in the session sidebar. After creation, the current `AgentSessionWrapper` is **destroyed** and unloaded, forcing subsequent requests to load the new file.

### In‑Session Branch Updates the Active Leaf

An **In‑Session Branch** creates no new files. Instead, it adds a new leaf node to the existing session tree structure stored within the current `.jsonl` file. The `navigate_tree` RPC command simply moves the session’s *active leaf* pointer to the target entry, altering only the context (the set of messages displayed) without persisting additional files to disk.

## Backend Implementation in rpc‑manager.ts

The [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) file handles both operations through distinct RPC command cases.

### Fork Handling

When the `fork` command is received, the manager validates the entry and checks if a bash command is currently running (forking is prohibited during active shell execution). It then branches based on whether you are forking before the first message or after existing history:

```typescript
// lib/rpc-manager.ts – fork implementation
// https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts#L399-L432
case "fork": {
  if (this.inner.isBashRunning) {
    throw new Error("Cannot fork while a shell command is running");
  }
  const entryId = command.entryId as string;
  // ... validation logic ...
  
  if (!entry.parentId) {
    // Fork before the first message: create empty session with parent reference
    const newManager = SessionManager.create(sessionManager.getCwd(), sessionDir);
    newManager.newSession({ parentSession: currentSessionFile });
    newSessionFile = newManager.getSessionFile() as string;
  } else {
    // Fork after some history: copy history up to fork point
    const sourceManager = SessionManager.open(currentSessionFile, sessionDir);
    const forkedPath = sourceManager.createBranchedSession(entry.parentId);
    if (!forkedPath) throw new Error("Failed to create forked session");
    newSessionFile = forkedPath;
  }
  
  this.destroy();  // Critical: unload the old wrapper
  return { cancelled: false, newSessionId };
}

```

### Navigate Tree Handling

In contrast, the `navigate_tree` case delegates directly to the inner session’s tree navigation method:

```typescript
// lib/rpc-manager.ts – navigate_tree implementation
// https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts#L36-L42
case "navigate_tree": {
  if (this.inner.isBashRunning) {
    throw new Error("Cannot navigate while a shell command is running");
  }
  const result = await this.inner.navigateTree(command.targetId as string, {});
  return { cancelled: result.cancelled };
}

```

This call updates the active leaf pointer without creating files or destroying the session wrapper.

## Frontend UI Representation

### Fork Button in MessageView.tsx

The **Fork** interaction originates in the message view component, where users can click a fork icon on any user message to spawn a new session:

```typescript
// components/MessageView.tsx
// Invokes the fork RPC with the specific entry ID
onFork(entryId: string) {
  rpc.send({ type: "fork", entryId });
}

```

### BranchNavigator Component

**In‑Session Branches** are selected through the `BranchNavigator` component, which renders the tree of available leaves:

```typescript
// components/BranchNavigator.tsx – leaf selection
// https://github.com/agegr/pi-web/blob/main/components/BranchNavigator.tsx#L10-L18
<TreeNodeView 
  // ... props ...
  onSelect={rep.entry.id} 
/>

```

Selecting a leaf triggers the `navigate_tree` RPC, updating the chat view to show only messages along that branch path.

### SessionSidebar Display

Forked sessions appear as expandable child nodes beneath their parent session in the sidebar, controlled via toggle states:

```typescript
// components/SessionSidebar.tsx – fork expansion control
// https://github.com/agegr/pi-web/blob/main/components/SessionSidebar.tsx#L2001
title={collapsed ? "Expand forks" : "Collapse forks"}

```

## Programmatic Usage Examples

**Triggering a Fork:**

```typescript
// Creates a new .jsonl file and switches to it
await sessionRpc.send({ 
  type: "fork", 
  entryId: "8a3f2c1e" // ID of the message to fork from
});

```

**Navigating to an In‑Session Branch:**

```typescript
// Switches context within the same file
await sessionRpc.send({ 
  type: "navigate_tree", 
  targetId: "leaf-42" 
});

```

## Summary

- **Forks** create new `.jsonl` files via `SessionManager.createBranchedSession`, persist a `parentSession` reference, and require destroying the current `AgentSessionWrapper` to load the new session.
- **In‑Session Branches** use `AgentSession.navigateTree` to switch the active leaf pointer within the existing file, making no disk changes and preserving the current session context.
- Both operations block execution if a bash command is running (`isBashRunning` check).
- The UI represents Forks as sidebar child nodes and In‑Session Branches as selectable leaves in the `BranchNavigator` dropdown.

## Frequently Asked Questions

### What happens to the original session file when I create a Fork?

The original session file remains unchanged on disk. The fork operation creates a **new** `.jsonl` file (either empty with a `parentSession` header or containing copied history up to the fork point) alongside the original. The [`SessionSidebar.tsx`](https://github.com/agegr/pi-web/blob/main/SessionSidebar.tsx) component uses the `parentSession` metadata to render the relationship as a parent-child tree structure.

### Can I switch between In‑Session Branches without creating new files?

Yes. In‑Session Branches are navigation operations within the same file. Calling `navigate_tree` with a target leaf ID updates the session’s internal pointer to determine which entries are included in the active context. This operation is lightweight and instantaneous because it only modifies runtime state, not disk storage.

### Why does the Fork command destroy the AgentSessionWrapper?

The `destroy()` call in [`rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/rpc-manager.ts) unloads the current session wrapper to force a clean state transition. Since Forks create entirely new session files with their own independent histories, the RPC manager must release resources associated with the original file and initialize a new wrapper pointing to the forked `.jsonl` file before returning the `newSessionId` to the client.

### How does Pi Web ensure Fork and Branch targets remain valid?

The [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) module maintains the session tree structure and entry integrity. When navigating branches or creating forks, the system validates entry IDs against this structure to ensure the target leaf or fork point exists within the current session context before executing the operation.