Session File Format

July 21, 2026 · View on GitHub

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

~/.prime/agent/sessions/<session-id>.jsonl

The header records the working directory. Current releases keep sessions in a flat directory; older per-project directories are migrated automatically.

Deleting Sessions

Sessions can be removed by deleting their .jsonl files under ~/.prime/agent/sessions/.

Prime Agent also supports deleting sessions interactively from /resume (select a session and press Ctrl+D, then confirm). When available, Prime Agent uses the trash CLI to avoid permanent deletion.

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
  • Version 3: Renamed hookMessage role to custom (extensions unification)

Existing sessions are automatically migrated to the current version (v3) when loaded.

Source Files

For TypeScript definitions in your project, inspect node_modules/@earendil-works/pi-coding-agent/dist/ and node_modules/@earendil-works/pi-ai/dist/.

Message Types

Session entries contain AgentMessage objects. Understanding these types is essential for parsing sessions and writing extensions.

Content Blocks

Messages contain arrays of typed content blocks:

interface TextContent {
  type: "text";
  text: string;
}

interface ImageContent {
  type: "image";
  data: string;      // base64 encoded
  mimeType: string;  // e.g., "image/jpeg", "image/png"
}

interface ThinkingContent {
  type: "thinking";
  thinking: string;
}

interface ToolCall {
  type: "toolCall";
  id: string;
  name: string;
  arguments: Record<string, any>;
}

Base Message Types (from Prime Agent AI)

interface UserMessage {
  role: "user";
  content: string | (TextContent | ImageContent)[];
  timestamp: number;  // Unix ms
}

interface AssistantMessage {
  role: "assistant";
  content: (TextContent | ThinkingContent | ToolCall)[];
  api: string;
  provider: string;
  model: string;
  usage: Usage;
  stopReason: "stop" | "length" | "toolUse" | "error" | "aborted";
  errorMessage?: string;
  timestamp: number;
}

interface ToolResultMessage {
  role: "toolResult";
  toolCallId: string;
  toolName: string;
  content: (TextContent | ImageContent)[];
  details?: any;      // Tool-specific metadata
  isError: boolean;
  timestamp: number;
}

interface Usage {
  input: number;
  output: number;
  cacheRead: number;
  cacheWrite: number;
  totalTokens: number;
  cost: {
    input: number;
    output: number;
    cacheRead: number;
    cacheWrite: number;
    total: number;
  };
}

Extended Message Types (from Prime Agent Coding Agent)

interface BashExecutionMessage {
  role: "bashExecution";
  command: string;
  output: string;
  exitCode: number | undefined;
  cancelled: boolean;
  truncated: boolean;
  fullOutputPath?: string;
  excludeFromContext?: boolean;  // true for !! prefix commands
  timestamp: number;
}

interface CustomMessage {
  role: "custom";
  customType: string;            // Extension identifier
  content: string | (TextContent | ImageContent)[];
  display: boolean;              // Show in TUI
  details?: any;                 // Extension-specific metadata
  timestamp: number;
}

interface BranchSummaryMessage {
  role: "branchSummary";
  summary: string;
  fromId: string;                // Entry we branched from
  timestamp: number;
}

interface CompactionSummaryMessage {
  role: "compactionSummary";
  summary: string;
  tokensBefore: number;
  timestamp: number;
}

AgentMessage Union

type AgentMessage =
  | UserMessage
  | AssistantMessage
  | ToolResultMessage
  | BashExecutionMessage
  | CustomMessage
  | BranchSummaryMessage
  | CompactionSummaryMessage;

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":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project"}

For sessions with a parent (created via /fork, /clone, or newSession({ parentSession })):

{"type":"session","version":3,"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"}

ServiceTierChangeEntry

Emitted when the user changes provider service tier.

{"type":"service_tier_change","id":"e6f7g8h9","parentId":"e5f6g7h8","timestamp":"2024-12-03T14:07:00.000Z","serviceTier":"priority"}

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: Implementation-specific data (e.g., { readFiles: string[], modifiedFiles: string[] } for default, or custom data for extensions)
  • fromHook: true if generated by an extension, false/undefined if generated by Prime Agent (legacy field name)

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, or custom data for extensions
  • fromHook: true if generated by an extension, false/undefined if generated by Prime Agent (legacy field name)

CustomEntry

Extension state persistence. Does NOT participate in LLM context.

{"type":"custom","id":"h8i9j0k1","parentId":"g7h8i9j0","timestamp":"2024-12-03T14:20:00.000Z","customType":"my-extension","data":{"count":42}}

Use customType to identify your extension's entries on reload.

ChildUsageAttributionEntry

Records RLM child usage folded into a parent assistant message. This entry is daemon bookkeeping and does not enter model context.

interface ChildUsageAttributionEntry extends SessionEntryBase {
  type: "child_usage_attributed";
  targetId: string;       // Parent assistant message entry
  childUsage: Usage;      // Usage added by one child
  aggregateUsage: Usage;  // Updated parent aggregate
}

Reload applies aggregateUsage to the target assistant message. Context-tree accounting can then subtract childUsage when reporting the parent node's own usage.

CustomMessageEntry

Extension-injected messages that DO participate in LLM context.

{"type":"custom_message","id":"i9j0k1l2","parentId":"h8i9j0k1","timestamp":"2024-12-03T14:25:00.000Z","customType":"my-extension","content":"Injected context...","display":true}

Fields:

  • content: String or (TextContent | ImageContent)[] (same as UserMessage)
  • display: true = show in TUI with distinct styling, false = hidden
  • details: Optional extension-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.

SessionInfoEntry

Session metadata (e.g., user-defined display name). Set via /name command or pi.setSessionName() in extensions.

{"type":"session_info","id":"k1l2m3n4","parentId":"j0k1l2m3","timestamp":"2024-12-03T14:35:00.000Z","name":"Refactor auth module"}

The session name is displayed in the session selector (/resume) instead of the first message when set.

SessionStateEntry

Stores daemon-managed lifecycle state. Current persisted states are active, archived, and legacy crash; older sleep values normalize to archived when read.

AgentStatusEntry

Stores the latest short agent status shown in the agents view, including its summary, optional task state, and source message count. It does not enter model context.

GitStateEntry

Stores an append-only repository-state snapshot for agent status and recovery views. It does not enter model context.

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

Bookkeeping entries such as child usage attribution, session lifecycle, agent status, and git state are ignored when building model context.

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}] Extension 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.

Static Creation Methods

  • SessionManager.create(cwd, sessionDir?) - New session
  • SessionManager.open(path, sessionDir?) - Open existing session file
  • SessionManager.continueRecent(cwd, sessionDir?) - Continue most recent or create new
  • SessionManager.inMemory(cwd?) - No file persistence
  • SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?) - Fork session from another project

Static Listing Methods

  • SessionManager.list(cwd, sessionDir?, callbacks?) - List sessions for a directory
  • SessionManager.listAll(callbacks?, sessionDir?) - List all sessions across all projects

callbacks can provide onProgress(loaded, total) and onSession(session) handlers.

Instance Methods - Session Management

  • newSession(options?) - Start a new session (options: { parentSession?: string })
  • setSessionFile(path) - Switch to a different session file
  • createBranchedSession(leafId) - Extract branch to new session file

Instance Methods - Appending (all return entry ID)

  • appendMessage(message) - Add message
  • appendThinkingLevelChange(level) - Record thinking change
  • appendServiceTierChange(tier) - Record provider service-tier change
  • appendModelChange(provider, modelId) - Record model change
  • appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?, customInstructions?) - Add compaction
  • appendCustomEntry(customType, data?) - Extension state (not in context)
  • appendChildUsageAttribution(targetId, childUsage, aggregateUsage) - Persist RLM child usage folded into a parent assistant message
  • appendSessionInfo(name) - Set session display name
  • appendSessionState(state) - Record daemon-managed lifecycle state
  • appendAgentStatus(status) - Record an agents-view status summary
  • appendGitState(git) - Record repository state
  • appendCustomMessageEntry(customType, content, display, details?) - Extension message (in context)
  • appendLabelChange(targetId, label) - Set/clear label

Instance Methods - Tree Navigation

  • getLeafId() - Current position
  • getLeafEntry() - Get current leaf entry
  • getEntry(id) - Get entry by ID
  • getBranch(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
  • resetLeaf() - Reset leaf to null (before any entries)
  • branchWithSummary(entryId, summary, details?, fromHook?) - Branch with context summary

Instance Methods - Context & Info

  • buildSessionContext() - Get messages, thinkingLevel, and model for LLM
  • getEntries() - All entries (excluding header)
  • getHeader() - Session header metadata
  • getSessionName() - Get display name from latest session_info entry
  • getCwd() - Working directory
  • getSessionDir() - Session storage directory
  • getSessionId() - Session UUID
  • getSessionFile() - Session file path (undefined for in-memory)
  • isPersisted() - Whether session is saved to disk