How Pi Web's `.jsonl` Session Files Store Branching History: A Complete Technical Guide
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 file defines the foundational types that govern how entries are structured and linked:
// 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 (lines 3–10):
interface SessionHeader {
type: "session";
version: number;
id: string;
timestamp: string;
cwd: string;
parentSession?: string; // Optional: path to originating session file
}
Fork Header Example
{
"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, the fork operation captures the current session path:
// 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 (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 (lines 56–63), branch_summary entries are handled alongside other entry types:
// 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
- Divergence point: The
parentIdreferences the last message before the branch split. - Summary text: Describes the branched conversation path for UI display.
- Navigation: The UI reconstructs branches by following
parentIdchains.
API Endpoint for Branch Creation
From components/BranchNavigator.tsx:
// 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:
{
"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:
- Reading the header via
readSessionHeaderto detect if this session was forked from another. - Scanning entries in
lib/session-reader.tsto collect all messages andbranch_summaryrecords. - Building adjacency lists from
parentIdrelationships to identify branch points. - Resolving leaf nodes using the
/api/sessions/[id]/context?leafId=endpoint to switch between branches.
Summary
.jsonlformat: Line-delimited JSON where each line is an independent entry.- Fork branching: Uses
parentSessionin the session header to link separate files hierarchically. - In-file branching: Uses
type: "branch_summary"entries withparentIdpointing to divergence points. - Universal linkage: All entries inherit
idandparentIdfromSessionEntryBaseinlib/types.ts. - Key files:
lib/types.ts(definitions),lib/session-reader.ts(reading/parsing),lib/rpc-manager.ts(fork creation),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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →