How pi-coding-agent Handles Session Management for Resuming, Forking, and Continuing Tasks

pi-coding-agent uses an append-only JSON-L tree managed by SessionManager to persist every interaction, enabling automatic resumption of recent sessions, explicit forking across projects, and branching within conversations.

The pi-coding-agent package in the badlogic/pi-mono repository implements a robust session management system that treats conversation history as a versioned, navigable tree. By storing each message as an immutable entry in a JSON-L file, the system guarantees data integrity while supporting complex workflows like resuming interrupted tasks, forking conversations into new projects, and branching to explore alternative solutions.

The Append-Only Session Tree Architecture

At the core of pi-coding-agent session management is the SessionManager class defined in packages/coding-agent/src/core/session-manager.ts. This manager treats each session as an append-only log where every entry is a JSON object written to a .jsonl file.

The session file begins with a header entry containing:

  • A UUID for the session
  • Timestamp and working directory (cwd)
  • Optional parentSession field when forking

As interactions occur, the manager appends message entries (user, assistant, system) and metadata entries (branch_summary, compaction_summary) without ever modifying previous lines. This immutability ensures that historical states remain recoverable at any time.

Creating and Persisting Sessions

Starting a New Session

You can initialize a session in two modes: in-memory for ephemeral conversations or file-backed for persistence.

To create a transient session that never touches disk:

import { SessionManager } from "@pi/coding-agent";

const mem = SessionManager.inMemory();
mem.appendMessage({ role: "user", content: "Hello" });
mem.appendMessage({ role: "assistant", content: "Hi!" });

To create a persisted session with a new file on disk:

const session = SessionManager.create(process.cwd());
// Creates: ~/.pi/agent/sessions/<cwd>/<timestamp>_<uuid>.jsonl

The default session directory is computed by helpers in packages/coding-agent/src/config.ts using getDefaultAgentDir and getSessionsDir.

The Session File Format

Each session maintains an in-memory index (byId map and leafId) that mirrors the file structure. When loading, SessionManager uses loadEntriesFromFile to parse each line, migrateToCurrentVersion to upgrade legacy formats, and _buildIndex to reconstruct the navigable tree. This index enables O(1) lookups for any message ID and constant-time traversal from leaf to root.

Resuming Sessions

Automatic Resumption of Recent Sessions

The SessionManager.continueRecent(cwd, sessionDir?) static method implements the "resume" workflow. It scans the default sessions folder for the most recently modified .jsonl file, loads it, and rebuilds the in-memory index.

When you run the pi CLI without explicit flags, it automatically invokes this method to pick up where you left off:

const cwd = process.cwd();
const recent = SessionManager.continueRecent(cwd);
// Loads: ~/.pi/agent/sessions/<cwd>/latest.jsonl
recent.appendMessage({ role: "user", content: "What's the status?" });

New messages are appended to the same logical conversation, preserving the full history.

Explicit Session Selection

For advanced workflows, the CLI supports the --session flag parsed in packages/coding-agent/src/cli/args.ts. This triggers sessionManager.setSessionFile(path), which swaps the manager to an arbitrary file:

const mgr = new SessionManager();
mgr.setSessionFile("/path/to/specific_session.jsonl");
// If file is empty: creates fresh session header
// If file exists: loads entries, runs migrations, rebuilds tree

This mechanism enables users to maintain multiple parallel conversations within the same project or to open a fork of another project's session.

Forking and Branching Workflows

Forking Sessions Across Projects

The SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?) static method enables cross-project session inheritance. It copies the entire history from a source session file into a new file located under a different working directory:

const sourceFile = "/home/alice/.pi/agent/sessions/oldproj/2024-10-01_abc123.jsonl";
const targetCwd = "/home/alice/workspaces/newproj";

const forked = SessionManager.forkFrom(sourceFile, targetCwd);
// Creates new file in newproj with parentSession: sourceFile

The new session header records parentSession: sourcePath, creating a provenance chain. This allows users to migrate context from one codebase to another without losing conversational history.

Branching Within a Session

Inside a single session, users can explore alternative solutions by branching to an earlier message. The branch(id) method moves the internal leafId pointer to a specific entry ID, effectively rewinding the conversation:

const mgr = SessionManager.continueRecent(cwd);
const earlyId = "a1b2c3d4"; // ID of a previous user message
mgr.branch(earlyId);
mgr.appendMessage({ role: "user", content: "Let's try a different approach." });

To document why a branch was taken, use branchWithSummary():

mgr.branchWithSummary(earlyId, "Switched to alternative plan", { reason: "timeout" });

This inserts a branch_summary entry into the log, providing metadata for future context building.

Exporting Branched Sessions

The createBranchedSession(leafId) method generates a compact session file containing only the path from the specified leaf back to the root:

const mgr = SessionManager.continueRecent(cwd);
const leaf = mgr.getLeafId()!;
const newFile = mgr.createBranchedSession(leaf);
// Returns path to new compact session file

This is useful for sharing specific conversation branches or archiving successful solution paths without the full tree.

Building Context for the LLM

When preparing context for the language model, the static helper buildSessionContext() walks the tree from the current leafId to the root, handling special entries:

  • Compaction entries: Summarize pruned branches to save tokens
  • Branch summaries: Provide context for why alternative paths were taken
  • Labels: Attach metadata to specific messages

The function returns a linearized array of AgentMessage objects that represent the current conversation path, regardless of how many branches exist in the underlying tree. Special entry types are defined in packages/coding-agent/src/core/messages.ts, including createBranchSummaryMessage and createCompactionSummaryMessage.

Summary

  • pi-coding-agent session management relies on an append-only JSON-L tree stored in packages/coding-agent/src/core/session-manager.ts.
  • Session creation uses SessionManager.create() or inMemory() for ephemeral chats, writing headers with UUID, timestamp, and optional parentSession.
  • Resuming happens automatically via continueRecent() or explicitly via setSessionFile(), rebuilding the in-memory index (byId, leafId) from disk.
  • Forking copies entire histories across projects using forkFrom(), recording provenance in the new session header.
  • Branching rewinds the conversation pointer with branch() or branchWithSummary(), enabling exploration of alternative solutions without losing history.
  • Context building linearizes the current branch for the LLM using buildSessionContext(), handling compaction and branch summaries defined in messages.ts.

Frequently Asked Questions

How does pi-coding-agent automatically resume my last conversation?

When you run the CLI without specifying a session file, SessionManager.continueRecent(cwd) scans the default sessions directory (computed by getSessionsDir in packages/coding-agent/src/config.ts), identifies the most recently modified .jsonl file, and rebuilds the in-memory tree index. New messages are then appended to this existing file, creating a seamless continuation of your previous work.

Can I fork a conversation from one project to another?

Yes, the SessionManager.forkFrom(sourcePath, targetCwd) static method copies the entire conversation history from a source session file into a new session file located under a different working directory. The new session header records the original file path in its parentSession field, creating a provenance chain that allows you to migrate context across projects while preserving the full interaction history.

What is the difference between branching and forking in pi-coding-agent?

Branching occurs within a single session file using branch(id) or branchWithSummary(), which moves the internal leafId pointer to an earlier message ID. This allows you to explore alternative solutions from a specific point in the conversation without affecting the original timeline. Forking, conversely, creates an entirely new session file in a different directory, copying the complete history from a source session. Forking is used for cross-project continuation, while branching is used for intra-conversation exploration.

How does the system handle old session file formats?

When loading a session via setSessionFile() or continueRecent(), the SessionManager invokes migrateToCurrentVersion() on each entry after parsing with loadEntriesFromFile(). This migration system upgrades legacy formats to the current schema before _buildIndex() reconstructs the in-memory maps (byId, leafId). This ensures backward compatibility while maintaining the integrity of the append-only log structure.

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 →