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 .jsonl file continues to store all messages
  • New message entries receive a parentId pointing to the selected branch point
  • The AgentSessionWrapper remains alive in globalThis.__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 parentSession metadata pointing to the source (display-only)
  • The systemPrompt is cleared in the forked session
  • The original AgentSessionWrapper is 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:

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 the leafId pointer in lib/rpc-manager.ts without creating new files — the same AgentSessionWrapper persists and new messages attach to alternate parents within the existing .jsonl structure.
  • Forking creates a brand-new session file with its own ID, copies the header with parentSession metadata, destroys the original wrapper, and clears the system prompt for a fresh start.
  • Both commands are handled in lib/rpc-manager.ts but 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →