# How Pi Web's `.jsonl` Session Files Store Branching History: A Complete Technical Guide

> Explore how Pi Web's .jsonl session files store branching history using parentSession headers and in-file branch summaries with parentId linkage. Learn the technical details.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: technical-guide
- Published: 2026-08-18

---

**Pi Web stores branching history in `.jsonl` session files using two mechanisms: file-level forks via the `parentSession` header field and in-file branches via `branch_summary` entries with `parentId` linkage.**

Pi Web persists every chat session as a line-delimited JSON file (`.jsonl`) where each line represents a discrete entry. According to the pi-web source code, branching information is captured through distinct structures that support both independent session forks and conversational branching within a single file.

---

## Overview of the `.jsonl` Session File Format

The session file format in Pi Web uses newline-separated JSON objects. The [`lib/types.ts`](https://github.com/agegr/pi-web/blob/main/lib/types.ts) file defines the foundational types that govern how entries are structured and linked:

```typescript
// lib/types.ts – SessionEntryBase (lines 13–16)
interface SessionEntryBase {
  id: string;      // 8-character hex identifier
  parentId: string; // References the previous entry in the branch
}

```

Every entry inherits from `SessionEntryBase`, creating a chain of `parentId` references that forms a directed acyclic graph of conversation history.

---

## File-Level Forking: The `parentSession` Header

When a user clicks **Fork**, Pi Web creates a new `.jsonl` file that references the original session through its header.

### Header Structure

The session header is defined in [`lib/types.ts`](https://github.com/agegr/pi-web/blob/main/lib/types.ts) (lines 3–10):

```typescript
interface SessionHeader {
  type: "session";
  version: number;
  id: string;
  timestamp: string;
  cwd: string;
  parentSession?: string; // Optional: path to originating session file
}

```

### Fork Header Example

```json
{
  "type": "session",
  "version": 3,
  "id": "<uuid>",
  "timestamp": "2024-01-15T09:30:00Z",
  "cwd": "/home/user/project",
  "parentSession": "/home/user/.pi-web/sessions/original.jsonl"
}

```

The `parentSession` field contains an absolute path to the parent session file. This creates a child file that appears as a separate node in the sidebar hierarchy.

### Server-Side Fork Creation

From [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts), the fork operation captures the current session path:

```typescript
// Creating a fork – server side
await send("fork", { parentSession: currentSessionFile });
// The new file's header will include:
//   "parentSession": "/abs/path/to/currentSession.jsonl"

```

### Reading Fork Relationships

The `readSessionHeader` function in [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) (lines 91–115) parses the header to establish hierarchical relationships for UI rendering.

---

## In-Session Branching: The `branch_summary` Entry

When a user navigates within an existing session using **Continue** or the `BranchNavigator` component, Pi Web preserves history through `branch_summary` entries rather than creating new files.

### Branch Summary Structure

From [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) (lines 56–63), `branch_summary` entries are handled alongside other entry types:

```typescript
// Entry type discrimination in session reader
{
  "type": "branch_summary",
  "id": "a1b2c3d4",
  "parentId": "e5f6g7h8",
  "fromId": "e5f6g7h8",
  "summary": "User explored alternative approach"
}

```

### How In-Session Branching Works

1. **Divergence point**: The `parentId` references the last message before the branch split.
2. **Summary text**: Describes the branched conversation path for UI display.
3. **Navigation**: The UI reconstructs branches by following `parentId` chains.

### API Endpoint for Branch Creation

From [`components/BranchNavigator.tsx`](https://github.com/agegr/pi-web/blob/main/components/BranchNavigator.tsx):

```typescript
// Adding an in-session branch – UI component
await fetch(`/api/sessions/${sessionId}/context?leafId=${leafId}`, {
  method: "POST",
  body: JSON.stringify({ 
    type: "branch_summary", 
    summary: "User switched branch" 
  })
});

```

---

## Message Linkage and Parent ID Chains

Every message entry maintains historical connectivity through required `parentId` reference:

```json
{
  "type": "message",
  "id": "m4n5o6p7",
  "parentId": "j8k9l0m1",
  "message": {
    "role": "assistant",
    "content": [...]
  }
}

```

This creates a directed-acyclic graph structure where:
- The **root** message has no `parentId` (or references the session header)
- **Linear** conversations form a simple linked list
- **Branched** conversations split when multiple entries share the same `parentId`

---

## Comparison: Fork vs. In-Session Branch

| Mechanism | Trigger | Storage Location | Use Case |
|-----------|---------|------------------|----------|
| **Fork** | "Fork" button | `parentSession` in new file header | Independent session exploration |
| **In-session branch** | "Continue" or `BranchNavigator` | `branch_summary` entry with `parentId` | Temporary exploration within same context |
| **Linear continuation** | Standard message send | `parentId` on message entries | Normal conversation flow |

---

## Reconstructing Branch History for Navigation

The Pi Web UI rebuilds conversational trees by:

1. **Reading the header** via `readSessionHeader` to detect if this session was forked from another.
2. **Scanning entries** in [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) to collect all messages and `branch_summary` records.
3. **Building adjacency lists** from `parentId` relationships to identify branch points.
4. **Resolving leaf nodes** using the `/api/sessions/[id]/context?leafId=` endpoint to switch between branches.

---

## Summary

- **`.jsonl` format**: Line-delimited JSON where each line is an independent entry.
- **Fork branching**: Uses `parentSession` in the session header to link separate files hierarchically.
- **In-file branching**: Uses `type: "branch_summary"` entries with `parentId` pointing to divergence points.
- **Universal linkage**: All entries inherit `id` and `parentId` from `SessionEntryBase` in [`lib/types.ts`](https://github.com/agegr/pi-web/blob/main/lib/types.ts).
- **Key files**: [`lib/types.ts`](https://github.com/agegr/pi-web/blob/main/lib/types.ts) (definitions), [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) (reading/parsing), [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) (fork creation), [`components/BranchNavigator.tsx`](https://github.com/agegr/pi-web/blob/main/components/BranchNavigator.tsx) (UI branching).

---

## Frequently Asked Questions

### What does the `parentId` field reference in a Pi Web session file?

The `parentId` field references the `id` of the immediately preceding entry in the conversation tree. For messages, this is the previous message. For `branch_summary` entries, this is the last entry before the branch diverged. This linkage enables the UI to reconstruct linear and branched conversation history.

### How does Pi Web distinguish between a forked session and a branched conversation?

A **forked session** is a separate `.jsonl` file with `parentSession` in its header pointing to the original file. A **branched conversation** remains in the same file and uses `branch_summary` entries with `parentId` chains to mark divergence points. Forks appear as independent session nodes; branches are navigable within a single session view.

### Can you manually edit the `parentSession` path in a session file header?

Yes, since `parentSession` stores an absolute file path, modifying it would redirect the UI's parent-child relationship display. However, the path must remain valid and accessible, or the hierarchical relationship will break. The `readSessionHeader` function in [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) performs this resolution at load time.

### What happens to `branch_summary` entries when a session is forked?

`branch_summary` entries remain in the original file and are not copied to the fork. The fork starts with a fresh, linear history containing only the session header (with `parentSession` set). The forked session's history begins from the point of forking, not from any in-file branches that existed in the parent.