Session Header ├─ Entry A (parentId: null) │ ├─ Entry B (parentId: A) │ │ └─ Entry C (parentId: B) ← leaf │ └─ Entry D (parentId: A) ← branch point │ └─ Entry E (parentId: D) ← alternate branch └─ Entry F (parentId: null) ← separate root
The leaf pointer tracks the current position. Appending creates a child of the current leaf.
Branching moves the leaf pointer without modifying history:
import { SessionManager } from "@mariozechner/pi-coding-agent";const session = SessionManager.create(process.cwd());// Append some messagessession.appendMessage(userMessage1); // Entry Asession.appendMessage(assistantMsg1); // Entry B (child of A)session.appendMessage(userMessage2); // Entry C (child of B)// Branch from Entry Asession.branch("entry-a-id");// Next append creates a child of A (not C)session.appendMessage(userMessage3); // Entry D (child of A)
The session manager builds the LLM context by walking from the leaf to the root:
const { messages, thinkingLevel, model } = session.buildSessionContext();// Or build from a specific entryconst context = session.buildSessionContext(entryId);
Emit kept messages (from firstKeptEntryId up to the compaction)
Emit messages after the compaction
// Example path with compaction:[ Entry A (user), Entry B (assistant), Entry C (user), Entry D (compaction, firstKeptEntryId: C), ← summary point Entry E (user), Entry F (assistant)]// Resulting messages:[ { role: "compactionSummary", summary: "..." }, // From Entry D { role: "user", ... }, // Entry C { role: "user", ... }, // Entry E { role: "assistant", ... } // Entry F]
Find cut point: Walk backwards from newest, accumulate message sizes until reaching keepRecentTokens
Generate summary: Use LLM to create structured summary:
## Goal[What is the user trying to accomplish?]## Constraints & Preferences- [User requirements]## Progress### Done- [x] Completed tasks### In Progress- [ ] Current work## Key Decisions- **Decision**: Rationale## Next Steps1. What should happen next## Critical Context- Important data/examples## Files- Read: file1.ts, file2.ts- Modified: file3.ts
Create compaction entry: Append to session with summary and firstKeptEntryId
// Get the full tree structureconst tree = session.getTree();// Walk from specific entry to rootconst path = session.getBranch(entryId);// Get all entries (flat list)const entries = session.getEntries();// Get specific entryconst entry = session.getEntry(entryId);// Get direct childrenconst children = session.getChildren(entryId);
const sessions = await SessionManager.list(process.cwd());for (const info of sessions) { console.log(info.name); // Display name console.log(info.path); // File path console.log(info.cwd); // Working directory console.log(info.created); // Creation date console.log(info.modified); // Last activity console.log(info.messageCount); // Total messages console.log(info.firstMessage); // First user message console.log(info.parentSessionPath); // Parent (if forked)}
// Fork session from another project into current projectconst session = SessionManager.forkFrom( "/other/project/sessions/session.jsonl", process.cwd(), "/path/to/target/sessions" // optional);