Fork vs In‑Session Branch in Pi Web: Key Differences Explained
Forks create new .jsonl session files on disk, while in‑session branches navigate between leaves within the same file without creating new storage.
Pi Web, an open-source AI conversation interface from agegr/pi-web, offers two distinct mechanisms for exploring alternative conversation paths. Understanding the difference between fork and in‑session branch helps you choose the right workflow for managing multi-turn conversations.
What a Fork Creates
A fork produces a completely new session file that exists alongside the original.
When you click the Fork button on a user message in MessageView.tsx, or call the fork RPC command, Pi Web invokes SessionManager.createBranchedSession in lib/rpc-manager.ts. This operation copies conversation history up to the fork point into a fresh .jsonl file.
// lib/rpc-manager.ts – fork handling
// 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: empty session
const newManager = SessionManager.create(sessionManager.getCwd(), sessionDir);
newManager.newSession({ parentSession: currentSessionFile });
newSessionFile = newManager.getSessionFile() as string;
} else {
// Fork after some history: copy 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(); // unload the old wrapper
return { cancelled: false, newSessionId };
}
Critical behaviors of forks:
- New file on disk: Each fork gets its own
.jsonlstorage - Parent pointer: The
parentSessionheader links back to the original - Wrapper destruction:
rpc-manager.tscallsthis.destroy()to unload the current session before loading the new one - Sidebar visibility: Forks appear as child nodes under the original session in
SessionSidebar.tsx, with expand/collapse controls
What an In‑Session Branch Does
An in‑session branch navigates between leaf nodes inside the same session file.
When you select a leaf in the BranchNavigator UI, or call the navigate_tree RPC command, Pi Web updates the session's active leaf pointer without touching disk storage.
// lib/rpc-manager.ts – navigate_tree handling
// 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 };
}
Critical behaviors of in‑session branches:
- No new file: The
.jsonlremains unchanged; only context shifts - Leaf ID targeting:
inner.navigateTree(targetId, {})moves the active pointer - Memory-only operation: Faster than fork since no file I/O occurs
- UI representation: Shown as selectable leaves in
BranchNavigator.tsxwith path highlighting
Side-by-Side Comparison
| Aspect | Fork | In‑Session Branch |
|---|---|---|
| Storage | New .jsonl file created |
Same file, new leaf pointer |
| Trigger | fork RPC or UI button |
navigate_tree RPC or leaf selection |
| Backend method | createBranchedSession then destroy() |
navigateTree(targetId, {}) |
| Parent relationship | parentSession header stored |
None (same file) |
| UI location | SessionSidebar.tsx child nodes |
BranchNavigator.tsx dropdown |
| Performance | Slower (file creation + copy) | Faster (in-memory pointer update) |
| Use case | Long-term alternative experiments | Quick exploration within a session |
How to Trigger Each Mechanism
Programmatic Fork
// Fork at a specific message entry
await sessionRpc.send({ type: "fork", entryId: "8a3f2c1e" });
Programmatic Branch Navigation
// Switch to a different leaf in the same session
await sessionRpc.send({ type: "navigate_tree", targetId: "leaf-42" });
UI Workflows
- Fork: Hover over any user message → click the Fork icon → new child session appears in sidebar
- In‑session branch: Click Branch in top bar → select leaf from
BranchNavigatordropdown → chat view updates instantly
Key Source Files
Understanding the implementation requires examining these files from agegr/pi-web:
lib/rpc-manager.ts– Core logic for both fork and navigate_tree operationscomponents/MessageView.tsx– Fork button UIcomponents/BranchNavigator.tsx– In‑session leaf selectorcomponents/SessionSidebar.tsx– Forked session display with expand/collapselib/session-reader.ts– Context building for consistent fork/navigation targets
Both mechanisms enforce the same safety check: isBashRunning must be false to prevent mid-command state corruption.
Summary
- Forks create persistent, independent session files with parent-child relationships visible in the sidebar
- In‑session branches provide lightweight, temporary navigation between conversation leaves without file overhead
- Choose fork for saved parallel experiments; choose in‑session branch for rapid exploration within a single conversation
- Both operations are implemented in
lib/rpc-manager.tsand blocked during active shell commands
Frequently Asked Questions
Can I convert an in‑session branch into a fork?
Not directly through the UI. To persist a branch as its own file, manually fork at the desired leaf point using the Fork button, which triggers createBranchedSession as shown in rpc-manager.ts lines 399–432.
Why does forking destroy the current session wrapper?
The this.destroy() call in the fork handler ensures clean unloading of the original AgentSessionWrapper. This forces the next request to load the newly created session file rather than maintaining stale state.
Do forked sessions share any storage with their parent?
No. Forked sessions copy history up to the fork point but maintain independent .jsonl files. The only link is the parentSession metadata header used for sidebar visualization.
What happens to bash processes during navigation?
Both operations check this.inner.isBashRunning and throw if a shell command is active. This prevents state inconsistency when the conversation context changes mid-execution.
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 →