- Renamed CLI binary and package from 'pi' to 'omp' across all platforms and installers. - Changed environment variable prefix from PI_* to OMP_* with automatic migration support. - Changed configuration directory from ~/.pi/ to ~/.omp/ with multi-path fallback discovery. - Added getConfigDirPaths(), findConfigFile(), and readConfigFile() functions for config resolution across .omp, .pi, and .claude directories.
8.7 KiB
Session File Format
Sessions are stored as JSONL (JSON Lines) files. Each line is a JSON object with a type field. Session entries form a tree structure via id/parentId fields, enabling in-place branching without creating new files.
File Location
~/.omp/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl
Where <path> is the working directory with / replaced by -.
Session Version
Sessions have a version field in the header:
- Version 1: Linear entry sequence (legacy, auto-migrated on load)
- Version 2: Tree structure with
id/parentIdlinking
Existing v1 sessions are automatically migrated to v2 when loaded.
Type Definitions
src/core/session-manager.ts- Session entry typespackages/agent/src/types.ts-AgentMessage,Attachment,ThinkingLevelpackages/ai/src/types.ts-UserMessage,AssistantMessage,ToolResultMessage,Usage,ToolCall
Entry Base
All entries (except SessionHeader) extend SessionEntryBase:
interface SessionEntryBase {
type: string;
id: string; // 8-char hex ID
parentId: string | null; // Parent entry ID (null for first entry)
timestamp: string; // ISO timestamp
}
Entry Types
SessionHeader
First line of the file. Metadata only, not part of the tree (no id/parentId).
{"type":"session","version":2,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project"}
For sessions with a parent (created via /branch or newSession({ parentSession })):
{"type":"session","version":2,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project","parentSession":"/path/to/original/session.jsonl"}
SessionMessageEntry
A message in the conversation. The message field contains an AgentMessage.
{"type":"message","id":"a1b2c3d4","parentId":"prev1234","timestamp":"2024-12-03T14:00:01.000Z","message":{"role":"user","content":"Hello"}}
{"type":"message","id":"b2c3d4e5","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:00:02.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Hi!"}],"provider":"anthropic","model":"claude-sonnet-4-5","usage":{...},"stopReason":"stop"}}
{"type":"message","id":"c3d4e5f6","parentId":"b2c3d4e5","timestamp":"2024-12-03T14:00:03.000Z","message":{"role":"toolResult","toolCallId":"call_123","toolName":"bash","content":[{"type":"text","text":"output"}],"isError":false}}
ModelChangeEntry
Emitted when the user switches models mid-session.
{"type":"model_change","id":"d4e5f6g7","parentId":"c3d4e5f6","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"gpt-4o"}
ThinkingLevelChangeEntry
Emitted when the user changes the thinking/reasoning level.
{"type":"thinking_level_change","id":"e5f6g7h8","parentId":"d4e5f6g7","timestamp":"2024-12-03T14:06:00.000Z","thinkingLevel":"high"}
CompactionEntry
Created when context is compacted. Stores a summary of earlier messages.
{"type":"compaction","id":"f6g7h8i9","parentId":"e5f6g7h8","timestamp":"2024-12-03T14:10:00.000Z","summary":"User discussed X, Y, Z...","firstKeptEntryId":"c3d4e5f6","tokensBefore":50000}
Optional fields:
details: Compaction-implementation specific data (e.g., file operations for default implementation, or custom data for custom hook implementations)fromHook:trueif generated by a hook,false/undefinedif omp-generated
BranchSummaryEntry
Created when switching branches via /tree with an LLM generated summary of the left branch up to the common ancestor. Captures context from the abandoned path.
{"type":"branch_summary","id":"g7h8i9j0","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:15:00.000Z","fromId":"f6g7h8i9","summary":"Branch explored approach A..."}
Optional fields:
details: File tracking data ({ readFiles: string[], modifiedFiles: string[] }) for default implementation, arbitrary for custom implementationfromHook:trueif generated by a hook
CustomEntry
Hook state persistence. Does NOT participate in LLM context.
{"type":"custom","id":"h8i9j0k1","parentId":"g7h8i9j0","timestamp":"2024-12-03T14:20:00.000Z","customType":"my-hook","data":{"count":42}}
Use customType to identify your hook's entries on reload.
CustomMessageEntry
Hook-injected messages that DO participate in LLM context.
{"type":"custom_message","id":"i9j0k1l2","parentId":"h8i9j0k1","timestamp":"2024-12-03T14:25:00.000Z","customType":"my-hook","content":"Injected context...","display":true}
Fields:
content: String or(TextContent | ImageContent)[](same as UserMessage)display:true= show in TUI with purple styling,false= hiddendetails: Optional hook-specific metadata (not sent to LLM)
LabelEntry
User-defined bookmark/marker on an entry.
{"type":"label","id":"j0k1l2m3","parentId":"i9j0k1l2","timestamp":"2024-12-03T14:30:00.000Z","targetId":"a1b2c3d4","label":"checkpoint-1"}
Set label to undefined to clear a label.
Tree Structure
Entries form a tree:
- First entry has
parentId: null - Each subsequent entry points to its parent via
parentId - Branching creates new children from an earlier entry
- The "leaf" is the current position in the tree
[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf
│
└─ [branch_summary] ─── [user msg] ← alternate branch
Context Building
buildSessionContext() walks from the current leaf to the root, producing the message list for the LLM:
- Collects all entries on the path
- Extracts current model and thinking level settings
- If a
CompactionEntryis on the path:- Emits the summary first
- Then messages from
firstKeptEntryIdto compaction - Then messages after compaction
- Converts
BranchSummaryEntryandCustomMessageEntryto appropriate message formats
Parsing Example
import { readFileSync } from "fs";
const lines = readFileSync("session.jsonl", "utf8").trim().split("\n");
for (const line of lines) {
const entry = JSON.parse(line);
switch (entry.type) {
case "session":
console.log(`Session v${entry.version ?? 1}: ${entry.id}`);
break;
case "message":
console.log(`[${entry.id}] ${entry.message.role}: ${JSON.stringify(entry.message.content)}`);
break;
case "compaction":
console.log(`[${entry.id}] Compaction: ${entry.tokensBefore} tokens summarized`);
break;
case "branch_summary":
console.log(`[${entry.id}] Branch from ${entry.fromId}`);
break;
case "custom":
console.log(`[${entry.id}] Custom (${entry.customType}): ${JSON.stringify(entry.data)}`);
break;
case "custom_message":
console.log(`[${entry.id}] Hook message (${entry.customType}): ${entry.content}`);
break;
case "label":
console.log(`[${entry.id}] Label "${entry.label}" on ${entry.targetId}`);
break;
case "model_change":
console.log(`[${entry.id}] Model: ${entry.provider}/${entry.modelId}`);
break;
case "thinking_level_change":
console.log(`[${entry.id}] Thinking: ${entry.thinkingLevel}`);
break;
}
}
SessionManager API
Key methods for working with sessions programmatically:
Creation
SessionManager.create(cwd, sessionDir?)- New sessionSessionManager.open(path, sessionDir?)- Open existingSessionManager.continueRecent(cwd, sessionDir?)- Continue most recent or create newSessionManager.inMemory(cwd?)- No file persistence
Appending (all return entry ID)
appendMessage(message)- Add messageappendThinkingLevelChange(level)- Record thinking changeappendModelChange(provider, modelId)- Record model changeappendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?)- Add compactionappendCustomEntry(customType, data?)- Hook state (not in context)appendCustomMessageEntry(customType, content, display, details?)- Hook message (in context)appendLabelChange(targetId, label)- Set/clear label
Tree Navigation
getLeafId()- Current positiongetEntry(id)- Get entry by IDgetPath(fromId?)- Walk from entry to rootgetTree()- Get full tree structuregetChildren(parentId)- Get direct childrengetLabel(id)- Get label for entrybranch(entryId)- Move leaf to earlier entrybranchWithSummary(entryId, summary, details?, fromHook?)- Branch with context summary
Context
buildSessionContext()- Get messages for LLMgetEntries()- All entries (excluding header)getHeader()- Session metadata