# 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/----/_.jsonl ``` Where `` 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`/`parentId` linking Existing v1 sessions are automatically migrated to v2 when loaded. ## Type Definitions - [`src/core/session-manager.ts`](../src/core/session-manager.ts) - Session entry types - [`packages/agent/src/types.ts`](../../agent/src/types.ts) - `AgentMessage`, `Attachment`, `ThinkingLevel` - [`packages/ai/src/types.ts`](../../ai/src/types.ts) - `UserMessage`, `AssistantMessage`, `ToolResultMessage`, `Usage`, `ToolCall` ## Entry Base All entries (except `SessionHeader`) extend `SessionEntryBase`: ```typescript 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`). ```json { "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 })`): ```json { "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`. ```json {"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. ```json { "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. ```json { "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. ```json { "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`: `true` if generated by a hook, `false`/`undefined` if 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. ```json { "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 implementation - `fromHook`: `true` if generated by a hook ### CustomEntry Hook state persistence. Does NOT participate in LLM context. ```json { "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. ```json { "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` = hidden - `details`: Optional hook-specific metadata (not sent to LLM) ### LabelEntry User-defined bookmark/marker on an entry. ```json { "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: 1. Collects all entries on the path 2. Extracts current model and thinking level settings 3. If a `CompactionEntry` is on the path: - Emits the summary first - Then messages from `firstKeptEntryId` to compaction - Then messages after compaction 4. Converts `BranchSummaryEntry` and `CustomMessageEntry` to appropriate message formats ## Parsing Example ```typescript import * as fs from "node:fs"; const lines = fs.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 session - `SessionManager.open(path, sessionDir?)` - Open existing - `SessionManager.continueRecent(cwd, sessionDir?)` - Continue most recent or create new - `SessionManager.inMemory(cwd?)` - No file persistence ### Appending (all return entry ID) - `appendMessage(message)` - Add message - `appendThinkingLevelChange(level)` - Record thinking change - `appendModelChange(provider, modelId)` - Record model change - `appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?)` - Add compaction - `appendCustomEntry(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 position - `getEntry(id)` - Get entry by ID - `getPath(fromId?)` - Walk from entry to root - `getTree()` - Get full tree structure - `getChildren(parentId)` - Get direct children - `getLabel(id)` - Get label for entry - `branch(entryId)` - Move leaf to earlier entry - `branchWithSummary(entryId, summary, details?, fromHook?)` - Branch with context summary ### Context - `buildSessionContext()` - Get messages for LLM - `getEntries()` - All entries (excluding header) - `getHeader()` - Session metadata