How Pi Web Differentiates Fork and In‑Session Branches: Implementation Guide
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 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 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:
// 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:
// 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:
// 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:
// 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:
// 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:
// 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:
// Switches context within the same file
await sessionRpc.send({
type: "navigate_tree",
targetId: "leaf-42"
});
Summary
- Forks create new
.jsonlfiles viaSessionManager.createBranchedSession, persist aparentSessionreference, and require destroying the currentAgentSessionWrapperto load the new session. - In‑Session Branches use
AgentSession.navigateTreeto 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 (
isBashRunningcheck). - The UI represents Forks as sidebar child nodes and In‑Session Branches as selectable leaves in the
BranchNavigatordropdown.
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 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 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 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.
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 →