# How Session Branching and Compaction Work in pi-ai

> Learn how pi-ai uses session branching and compaction with its append-only JSON-L tree. Discover how branch creates new histories and appendCompaction summarizes old entries to save tokens.

- Repository: [Mario Zechner/pi-mono](https://github.com/badlogic/pi-mono)
- Tags: deep-dive
- Published: 2026-02-16

---

**Session branching and compaction in pi-ai use an append-only JSON-L tree structure where `branch()` moves the leaf pointer to create alternate conversation histories, while `appendCompaction()` replaces old entries with summaries to manage token limits.**

The pi-ai coding agent (part of the [badlogic/pi-mono](https://github.com/badlogic/pi-mono) repository) implements a sophisticated session management system that treats conversation history as a version-controlled tree rather than a linear log. This design enables non-destructive branching and intelligent context compression through compaction.

## Understanding the Append-Only Session Tree

pi-ai stores every interaction in an **append-only JSON-L file** that represents a tree of session entries. Each entry contains an `id` and a `parentId`, forming a directed acyclic graph capable of maintaining multiple conversation branches simultaneously.

The `SessionManager` class in [[`packages/coding-agent/src/core/session-manager.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/session-manager.ts)](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/session-manager.ts) maintains a `leafId` pointer that marks the current active node. All append operations create children of this leaf, extending the conversation history from the current position.

Entry types include:

- `message` – User, assistant, or custom messages
- `branch_summary` – Human-readable summary of abandoned branches
- `compaction` – Summary replacing a long history segment
- `model_change`, `thinking_level_change` – Meta-information influencing LLM calls

## Session Branching in pi-ai

Branching allows users to explore alternative conversation paths without losing the original history. The system achieves this by repositioning the leaf pointer to any previous entry, causing subsequent appends to create a new branch from that point.

### Creating a Branch with branch()

The `branch()` method (lines 1110–1116 in [`session-manager.ts`](https://github.com/badlogic/pi-mono/blob/main/session-manager.ts)) validates the target entry ID and updates `leafId` to point to that node:

```typescript
// Move the leaf pointer to an earlier entry
session.branch(entryId);

```

Subsequent calls to `appendMessage()` or `appendCompaction()` create children of the repositioned leaf, effectively forking the conversation tree while preserving the original timeline intact.

### Branching with Summaries using branchWithSummary()

For better context management when abandoning a branch, `branchWithSummary()` (lines 1127–1147) creates a branch and inserts a human-readable summary of the discarded path:

```typescript
const summaryId = session.branchWithSummary(
  entryId,                     // where to branch from (or null for root)
  "User switched topic to …", // free-form summary
  { extra: "metadata" },      // optional extension data
  false                       // fromHook flag (false = core-generated)
);

```

The summary is rendered for the LLM by `createBranchSummaryMessage` in [[`packages/coding-agent/src/core/messages.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/messages.ts)](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/messages.ts) (lines 100–107), ensuring the model understands the context switch without processing the entire abandoned branch.

## Session Compaction in pi-ai

As conversations grow, they eventually exceed token limits. Compaction solves this by replacing a contiguous block of old entries with a single summary entry, preserving context while freeing up token budget for new messages.

### How Compaction Works

The `appendCompaction()` method (lines 636–665 in [`session-manager.ts`](https://github.com/badlogic/pi-mono/blob/main/session-manager.ts)) creates a `CompactionEntry` that records the summary text and the ID of the first entry to keep:

```typescript
const compactionId = session.appendCompaction(
  "Conversation was trimmed to keep recent context", // summary
  firstKeptEntryId,                                 // id of the first entry we keep
  tokensBeforeCompaction,                           // token count before compacting
  { model: "claude-3-sonnet-20240229" },           // optional hook data
  true                                              // fromHook = true (generated by extension)
);

```

The summary text is converted to LLM-compatible format by `createCompactionSummaryMessage` in [`messages.ts`](https://github.com/badlogic/pi-mono/blob/main/messages.ts) (lines 109–119), allowing the model to understand the historical context without processing the full message history.

### Reconstructing Context with Compaction

During context reconstruction, `buildSessionContext()` (lines 302–414) walks from the current leaf to the root. When encountering a `compaction` entry, it:

1. Emits the compaction summary first
2. Emits only the **kept** entries (those whose `id` ≥ `firstKeptEntryId`)
3. Emits everything after the compaction point

This ensures the LLM receives a concise representation of the full conversation while preserving the logical ordering of recent turns.

## Extracting and Managing Branches

To export only the active conversation path without abandoned branches, pi-ai provides `createBranchedSession()` (lines 1516–1588):

```typescript
const branchedFile = session.createBranchedSession(activeLeafId);
// `branchedFile` contains a new JSONL file with just the root-to-leaf path.

```

This method builds a new session file containing only the selected branch, copies relevant label entries, and updates the header to point to the original file as its parent, enabling clean exports and archival of specific conversation threads.

## Complete Workflow Example

Here is a complete example demonstrating branching and compaction in a typical interaction:

```typescript
import { SessionManager } from "@mariozechner/pi-ai";

// 1️⃣ Create or resume a session
const session = SessionManager.continueRecent(process.cwd());

// 2️⃣ Normal conversation
session.appendMessage({ role: "user", content: "Explain recursion." });
session.appendMessage({ role: "assistant", content: "Recursion is …" });

// 3️⃣ User decides to try a different approach → branch
session.branch(session.getLeafId()!);               // move leaf back
session.appendMessage({ role: "user", content: "Show me an example in Python." });

// 4️⃣ After many messages we hit a token limit → compact
const firstKept = session.appendMessage({ role: "assistant", content: "Here is the final result." });
session.appendCompaction(
  "Early discussion trimmed – see summary above.",
  firstKept,
  1200
);

// 5️⃣ Build the LLM context for the next API call
const { messages } = session.buildSessionContext(); // ← includes compaction summary + kept messages

```

## Summary

- **pi-ai uses an append-only JSON-L tree** where entries reference parents via `parentId`, enabling non-destructive branching and full history preservation.
- **Branching** repositions the `leafId` pointer to any historical entry via `branch()` or `branchWithSummary()`, creating new conversation paths while keeping original branches intact.
- **Compaction** replaces old entries with summaries using `appendCompaction()`, managed by `buildSessionContext()` to reconstruct concise LLM context that respects token limits.
- **Key implementation files** include [`packages/coding-agent/src/core/session-manager.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/session-manager.ts) for tree management and [`packages/coding-agent/src/core/messages.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/messages.ts) for summary message formatting.

## Frequently Asked Questions

### How does pi-ai handle multiple conversation branches without losing data?

pi-ai maintains every conversation branch in a single append-only JSON-L file using a directed acyclic graph structure. Each entry stores a `parentId` referencing its predecessor, allowing multiple leaf nodes to coexist. The `SessionManager` tracks only the active `leafId`, but all historical branches remain in the file and can be reactivated or exported using `createBranchedSession()`.

### What is the difference between branching and compaction in pi-ai?

**Branching** creates alternate conversation paths by moving the leaf pointer to a previous entry, enabling exploration of different approaches without destroying the original timeline. **Compaction** is a space-saving mechanism that replaces a block of old entries with a summary text to stay within token limits. While branching preserves full granularity for exploration, compaction intentionally loses individual messages in favor of a condensed representation.

### When should I use branchWithSummary() instead of branch()?

Use `branchWithSummary()` when abandoning a conversation path that contains important context the LLM should remember, such as when switching topics or discarding a failed approach. The method inserts a `branch_summary` entry containing your human-readable description, which `buildSessionContext()` renders as a system message. Use `branch()` alone when the abandoned branch requires no explanation or when you intend to return to it later without summarizing the detour.

### How does buildSessionContext() reconstruct conversation history after compaction?

`buildSessionContext()` traverses from the current leaf toward the root, building a message list in reverse chronological order. When it encounters a `compaction` entry, it inserts the compaction summary first, then filters the subsequent entries to include only those with IDs greater than or equal to the compaction's `firstKeptEntryId`. This ensures the LLM receives the summary of discarded history followed by the complete recent context, maintaining logical flow while respecting token budgets.