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
parentSessionfield 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()orinMemory()for ephemeral chats, writing headers with UUID, timestamp, and optionalparentSession. - Resuming happens automatically via
continueRecent()or explicitly viasetSessionFile(), 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()orbranchWithSummary(), 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 inmessages.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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →