In-Session Branching vs Forking in Pi-Web: How navigate_tree Differs from New Session Files
In-session branching updates the leaf pointer within the same .jsonl file, while forking creates an entirely new session file with its own conversation history.
Pi-Web implements two distinct mechanisms for diverging from the main conversation flow. Understanding how in-session branching (navigate_tree) differs from forking new session files is essential for developers building on the agegr/pi-web codebase. Both commands are handled in lib/rpc-manager.ts, but they produce fundamentally different storage and lifecycle outcomes.
What Is In-Session Branching (navigate_tree)?
In-session branching allows users to continue a conversation from any previous point without creating a new file. When a user clicks Continue in the Branch Navigator, the front-end dispatches a navigate_tree command.
How navigate_tree Works
The navigate_tree command updates the session's internal leaf pointer to a different entry in the same conversation tree:
// Sent from the UI via useAgentSession.ts
await sendAgentCommand(sessionId, {
type: "navigate_tree",
targetId: entryId // entryId of the leaf to continue from
});
In lib/rpc-manager.ts, this case simply switches the leaf:
case "navigate_tree":
// Switch the current leaf to targetId within the same .jsonl file
session.setLeafId(cmd.targetId);
break;
Storage Impact of In-Session Branching
- The same
.jsonlfile continues to store all messages - New
messageentries receive aparentIdpointing to the selected branch point - The
AgentSessionWrapperremains alive inglobalThis.__piSessions - File size grows incrementally as new branches are added
What Is Forking New Session Files?
Forking creates a completely independent session starting from the current conversation state. When a user clicks Fork on a user message, the back-end generates a fresh session file.
How Forking Works
The front-end sends a fork command:
// Triggered by the "Fork" button on a user message
await sendAgentCommand(sessionId, {
type: "fork"
});
The back-end handling in lib/rpc-manager.ts performs multiple operations:
case "fork":
// Create fresh .jsonl file, copy header, destroy old wrapper
const newId = await session.fork(); // creates new file & wrapper
this.destroyWrapper(session.id); // removes old wrapper
return { newSessionId: newId };
Storage Impact of Forking
- A new file is created at
~/.pi/agent/sessions/<timestamp>_<uuid>.jsonl - The original session file remains untouched
- The new file's header contains
parentSessionmetadata pointing to the source (display-only) - The
systemPromptis cleared in the forked session - The original
AgentSessionWrapperis destroyed and replaced
Key Differences: navigate_tree vs Fork
| Aspect | In-Session Branching | Forking |
|---|---|---|
| File created? | No — same .jsonl file |
Yes — new .jsonl file |
| Wrapper lifecycle | Preserved, leaf updated | Destroyed and replaced |
| Parent metadata | Unchanged | parentSession field added |
| Session ID | Unchanged | New UUID generated |
| UI trigger | "Continue" in Branch Navigator | "Fork" button on message |
| Back-end case | "navigate_tree" |
"fork" |
Core Files Implementing Both Mechanisms
Understanding the codebase requires familiarity with these locations:
lib/rpc-manager.ts— Command router implementing both"navigate_tree"and"fork"caseshooks/useAgentSession.ts— Front-end hook dispatching commands to the back-endlib/session-reader.ts— Reads.jsonlfiles and normalizes branches for display
When to Use Each Approach
Use in-session branching (navigate_tree) when you want to:
- Explore alternate conversation paths within the same saved session
- Keep all variants accessible from a single file
- Minimize disk usage and session management overhead
Use forking when you want to:
- Create a permanent split with independent evolution
- Start fresh with a cleared
systemPrompt - Preserve the original conversation exactly as it was
Summary
- In-session branching (
navigate_tree) switches theleafIdpointer inlib/rpc-manager.tswithout creating new files — the sameAgentSessionWrapperpersists and new messages attach to alternate parents within the existing.jsonlstructure. - Forking creates a brand-new session file with its own ID, copies the header with
parentSessionmetadata, destroys the original wrapper, and clears the system prompt for a fresh start. - Both commands are handled in
lib/rpc-manager.tsbut differ fundamentally in storage model, lifecycle, and intended use case.
Frequently Asked Questions
Does forking delete the original session file?
No. The original session file remains completely untouched. Forking only creates a new file and updates the in-memory wrapper registry. The parentSession metadata in the new file's header is for UI display purposes only and does not link the files at the storage level.
Can you merge branches back together after using navigate_tree?
No native merge operation exists in the current Pi-Web implementation. Once you branch via navigate_tree, each leaf continues independently within the same file. The lib/session-reader.ts normalizes branches for display, but there is no mechanism to recombine divergent conversation paths.
What happens to the system prompt when you fork versus branch?
Forking explicitly clears the systemPrompt in the new session file's header. In-session branching preserves all existing session state including the system prompt, since the same file and wrapper continue to be used.
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 →