# How the Fork Operation Works in Pi Web: Session Branching Explained

> Understand the Pi Web Fork operation. Learn how it creates independent session branches by copying conversation history and preventing registry contamination.

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

---

**The Fork operation in Pi Web creates an independent session branch by copying the conversation history up to a specific point into a new `.jsonl` file, then immediately destroying the original `AgentSessionWrapper` to prevent registry contamination.**

The Pi Web repository (`agegr/pi-web`) implements session forking through a coordinated sequence between the RPC manager and session reader. When triggered from the chat interface, the system validates the current state, generates a branched session file, and clears the original wrapper from the global registry to ensure clean separation between parent and child sessions.

## The Fork Execution Flow

When a user initiates a fork from the UI, the system executes three distinct phases to ensure atomic session branching.

### 1. Triggering the Fork Command

The process begins when the UI sends a JSON-RPC command with `type: "fork"` to the agent endpoint. In [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts), the handler first validates that no shell command is currently executing (`shellRunning`), then retrieves the target entry using `this.sessionEntry(entryId)` to identify the exact branching point.

```typescript
// components/ChatWindow.tsx - UI triggering the fork
await fetch(`/api/agent/${sessionId}`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ type: "fork", entryId: "8a3f..." })
});

```

According to the design notes in [`AGENTS.md`](https://github.com/agegr/pi-web/blob/main/AGENTS.md), the **Fork must destroy the wrapper immediately** after creating the branched file to avoid leaking stale state into the global session registry (`globalThis.__piSessions`).

### 2. Creating the Branched Session File

Once validated, the RPC manager calls `sourceManager.createBranchedSession(entry.parentId)` which performs the actual branching operation. This function:

- Copies the original session's history up to (but not including) the selected fork point
- Writes the copied content to a new `.jsonl` file under `~/.pi/agent/sessions/...`
- Returns the `forkedPath` pointing to the newly created session file

The new session file contains a header with a `parentSession` field pointing to the original session file, establishing the lineage while maintaining physical separation.

### 3. Destroying the Original Wrapper

Immediately after the new file is created, the system calls `this.destroy()` on the current `AgentSessionWrapper`. This removal from `globalThis.__piSessions` is critical because:

- It prevents the original session ID from resolving to a wrapper containing forked state
- It ensures subsequent requests for the original session load a fresh wrapper from the original file
- It eliminates the risk of corrupted `parentSession` chains in the registry

As implemented in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts), the handler returns the `newSessionFile` path only after the destruction is complete, guaranteeing the atomicity of the operation.

## Key Implementation Details

### Session Type Definitions

In [`lib/types.ts`](https://github.com/agegr/pi-web/blob/main/lib/types.ts), the session metadata includes an optional `parentSessionId?: string` field. This property flags sessions that were created through forking, allowing the UI to render the correct conversation tree structure while maintaining the integrity of the parent-child relationship.

### Navigation Alignment

The [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) module handles the complex task of aligning fork and navigation targets while preserving Pi's compaction ordering. After a fork occurs, the session's entry list is rebuilt to reflect the new child session's isolated history, ensuring the UI tree displays the branch correctly without mixing entries from the original session.

### Validation Constraints

The fork operation includes strict guards against invalid states:

- **Shell execution blocking**: If `shellRunning` is true, the fork throws an error to prevent branching during active command execution
- **Entry validation**: The system verifies the `entryId` exists via `this.sessionEntry()` before attempting to branch
- **File creation verification**: The code validates that `sourceManager.createBranchedSession()` returns a valid path before proceeding with wrapper destruction

## Code Example: Complete Fork Handler

```typescript
// lib/rpc-manager.ts - Core fork implementation
case "fork": {
  if (shellRunning) {
    throw new Error("Cannot fork while a shell command is running");
  }
  
  const entry = this.sessionEntry(entryId);
  if (!entry) {
    throw new Error("Invalid entry ID for forking");
  }

  // Create the branched session file
  const forkedPath = sourceManager.createBranchedSession(entry.parentId);
  if (!forkedPath) {
    throw new Error("Failed to create forked session");
  }
  
  const newSessionFile = forkedPath;

  // Critical: Destroy wrapper to clear registry
  this.destroy();
  
  return { newSessionFile };
}

```

## Summary

- **Fork creates isolation**: The operation generates a new session file containing only the history up to the fork point, leaving the original session untouched.
- **Wrapper destruction is mandatory**: According to [`AGENTS.md`](https://github.com/agegr/pi-web/blob/main/AGENTS.md) and the implementation in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts), the original `AgentSessionWrapper` must be destroyed immediately after forking to prevent registry pollution.
- **Registry clearing ensures integrity**: Removing the wrapper from `globalThis.__piSessions` guarantees that subsequent access to the original session loads fresh state from the source file.
- **Parent relationships are preserved**: The `parentSessionId` field in [`lib/types.ts`](https://github.com/agegr/pi-web/blob/main/lib/types.ts) and the `parentSession` header in the new file maintain the conversation lineage without creating circular dependencies.

## Frequently Asked Questions

### Where is the fork logic implemented in the Pi Web codebase?

The primary fork logic resides in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) within the RPC command handler switch statement. This file contains the `case "fork"` block that coordinates validation, calls `sourceManager.createBranchedSession()`, and manages the wrapper destruction sequence. Supporting logic for session structures appears in [`lib/types.ts`](https://github.com/agegr/pi-web/blob/main/lib/types.ts), while [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) handles the navigation alignment after branching.

### Why must the wrapper be destroyed immediately after forking?

The immediate destruction of the `AgentSessionWrapper` via `this.destroy()` is required to remove the stale wrapper from `globalThis.__piSessions`. If the wrapper persisted, subsequent requests for the original session ID would resolve to a wrapper containing references to the forked state, potentially corrupting the `parentSession` chain and causing the UI to display incorrect conversation history. This requirement is explicitly documented in the [`AGENTS.md`](https://github.com/agegr/pi-web/blob/main/AGENTS.md) design notes.

### How does the new session reference its parent after a fork?

The new session file created under `~/.pi/agent/sessions/...` contains a header with a `parentSession` field pointing to the original session file path. Additionally, [`lib/types.ts`](https://github.com/agegr/pi-web/blob/main/lib/types.ts) defines an optional `parentSessionId?: string` property in the session metadata that flags forked sessions. This dual reference system allows the UI to render conversation trees while keeping the session files physically separate.

### Can I fork a session while a shell command is running?

No. The implementation in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) explicitly checks the `shellRunning` state before executing a fork. If a shell command is active, the operation throws an error with the message "Cannot fork while a shell command is running." This prevents branching during incomplete operations that might leave the session in an inconsistent state.