24  Session File Format

24.1 File Location

Sessions are stored as JSONL (JSON Lines) files:

~/.pi/agent/sessions/<encoded-cwd>/<session-id>.jsonl

The working directory path is encoded by replacing / with -. For example:

/Users/wuzhiguo/projects/my-app
→ Users-wuzhiguo-projects-my-app

Session files can also have custom names when set via --name or /name.

24.2 Session Versions

The current session format version is 1. Each JSONL file starts with a header entry:

{"type":"session_header","version":1,"createdAt":"2026-07-31T12:00:00Z","cwd":"/Users/wuzhiguo/projects/my-app"}

24.3 Message Types

Each line in the JSONL file is a JSON object representing a session entry. All entries share common fields:

Field Type Description
id string Unique entry ID (UUID or sequential)
parentId string\|null Parent entry ID (for tree structure)
type string Entry type
timestamp string ISO 8601 timestamp
modelId string\|null Model used (for model-generated entries)

24.3.1 Common Entry Types

Type Description
session_header File header with metadata
user_message User input
assistant_message Model response
tool_call Tool invocation by the model
tool_result Result of a tool call
compaction Summarized context
branch_summary Summary of an abandoned branch
thinking Model thinking/reasoning content

24.4 Content Blocks

Messages contain an array of content blocks:

{
  "type": "assistant_message",
  "id": "entry-003",
  "parentId": "entry-002",
  "timestamp": "2026-07-31T12:00:02Z",
  "modelId": "claude-sonnet-4-20250514",
  "content": [
    {
      "type": "text",
      "text": "I'll create a new file for you."
    }
  ],
  "usage": {
    "inputTokens": 1500,
    "outputTokens": 45,
    "cacheReadTokens": 200,
    "cacheWriteTokens": 0
  },
  "cost": 0.0023,
  "durationMs": 1200
}

24.4.1 Content Block Types

24.4.1.1 Text Block

{
  "type": "text",
  "text": "The content text"
}

24.4.1.2 Image Block

{
  "type": "image",
  "source": {
    "type": "base64",
    "mediaType": "image/png",
    "data": "iVBORw0KGgo..."
  }
}

24.4.1.3 Tool Use Block

{
  "type": "tool_use",
  "id": "tc_001",
  "name": "bash",
  "input": { "command": "ls -la" }
}

24.4.1.4 Tool Result Block

{
  "type": "tool_result",
  "toolUseId": "tc_001",
  "content": [
    { "type": "text", "text": "total 48\n..." }
  ],
  "isError": false
}

24.4.1.5 Thinking Block

{
  "type": "thinking",
  "text": "The user wants to list files. I should use ls.",
  "signature": "..."
}

24.5 Entry Types (Detailed)

24.5.1 session_header

{
  "type": "session_header",
  "version": 1,
  "createdAt": "2026-07-31T12:00:00Z",
  "cwd": "/Users/wuzhiguo/projects/my-app",
  "provider": "anthropic",
  "model": "claude-sonnet-4-20250514",
  "thinkingLevel": "medium"
}

24.5.2 user_message

{
  "type": "user_message",
  "id": "entry-001",
  "parentId": null,
  "timestamp": "2026-07-31T12:00:01Z",
  "content": [
    { "type": "text", "text": "Create a REST API endpoint for users" }
  ]
}

24.5.3 assistant_message

{
  "type": "assistant_message",
  "id": "entry-002",
  "parentId": "entry-001",
  "timestamp": "2026-07-31T12:00:03Z",
  "modelId": "claude-sonnet-4-20250514",
  "content": [
    { "type": "text", "text": "I'll create the endpoint." },
    { "type": "tool_use", "id": "tc_001", "name": "write", "input": { "path": "src/routes/users.ts", "content": "..." } }
  ],
  "usage": {
    "inputTokens": 1500,
    "outputTokens": 120,
    "cacheReadTokens": 200,
    "cacheWriteTokens": 0
  },
  "cost": 0.005,
  "durationMs": 2300,
  "thinkingLevel": "medium"
}

24.5.4 tool_call

{
  "type": "tool_call",
  "id": "entry-003",
  "parentId": "entry-002",
  "timestamp": "2026-07-31T12:00:03Z",
  "toolName": "write",
  "toolCallId": "tc_001",
  "input": {
    "path": "src/routes/users.ts",
    "content": "import express from 'express';\n..."
  }
}

24.5.5 tool_result

{
  "type": "tool_result",
  "id": "entry-004",
  "parentId": "entry-003",
  "timestamp": "2026-07-31T12:00:04Z",
  "toolCallId": "tc_001",
  "toolName": "write",
  "output": {
    "content": [
      { "type": "text", "text": "File written successfully: src/routes/users.ts (45 lines)" }
    ]
  },
  "details": {
    "bytesWritten": 1200,
    "lines": 45
  },
  "isError": false,
  "durationMs": 15
}

24.5.6 compaction

{
  "type": "compaction",
  "id": "entry-010",
  "parentId": "entry-009",
  "timestamp": "2026-07-31T12:30:00Z",
  "modelId": "claude-sonnet-4-20250514",
  "summary": "The user asked to create a REST API for users. We created src/routes/users.ts with endpoints for GET, POST, PUT, DELETE. We also created a User model in src/models/User.ts. Tests were written in src/routes/users.test.ts and all passing. The current task is adding authentication middleware.",
  "summarizedEntryIds": ["entry-001", "entry-002", "entry-003", "entry-004", "entry-005", "entry-006", "entry-007", "entry-008"],
  "filesMentioned": [
    "src/routes/users.ts",
    "src/models/User.ts",
    "src/routes/users.test.ts"
  ],
  "tokenCount": 350
}

24.5.7 branch_summary

{
  "type": "branch_summary",
  "id": "entry-015",
  "parentId": "entry-005",
  "timestamp": "2026-07-31T13:00:00Z",
  "modelId": "claude-sonnet-4-20250514",
  "summary": "Explored using GraphQL instead of REST. Decided against it due to project constraints.",
  "summarizedEntryIds": ["entry-006", "entry-007"],
  "branchRootId": "entry-005"
}

24.5.8 thinking

{
  "type": "thinking",
  "id": "entry-002a",
  "parentId": "entry-001",
  "timestamp": "2026-07-31T12:00:02Z",
  "modelId": "claude-sonnet-4-20250514",
  "content": [
    { "type": "thinking", "text": "The user wants a REST API. I should check the existing project structure first.", "signature": "..." }
  ],
  "thinkingLevel": "medium"
}

24.6 Tree Structure

The session tree is defined by id and parentId fields. Each entry (except the root) has exactly one parent. Multiple entries can share the same parent, creating branches.

entry-001 (user_message, parentId: null)
├── entry-002 (assistant_message, parentId: entry-001)
│   ├── entry-003 (tool_call, parentId: entry-002)
│   │   └── entry-004 (tool_result, parentId: entry-003)
│   └── entry-005 (user_message, parentId: entry-002)
│       ├── entry-006 (assistant_message, parentId: entry-005) [ACTIVE]
│       └── entry-008 (assistant_message, parentId: entry-005) [ABANDONED]
│           └── entry-009 (branch_summary, parentId: entry-008)
└── entry-010 (compaction, parentId: entry-001)

24.6.1 Building the Tree

To reconstruct the tree from a JSONL file:

  1. Parse each line as a JSON object
  2. Build a map of entries by ID
  3. For each entry, find its parent via parentId
  4. Group entries by parent to form children arrays
  5. The root entry has parentId: null

24.7 Context Building

When Pi prepares context for a model API call, it follows this process:

graph TD
    A[Start from active leaf] --> B[Walk up to root]
    B --> C[Collect entries in reverse]
    C --> D[Reverse to chronological order]
    D --> E[Apply compaction]
    E --> F[Apply tool result truncation]
    F --> G[Build content blocks]
    G --> H[Send to model]

24.7.1 Steps

  1. Walk the active path — from the current position up to the root
  2. Collect entries — gather all entries on the active path
  3. Handle compaction — if a CompactionEntry is encountered, use its summary and stop walking
  4. Truncate tool results — truncate large tool results based on settings
  5. Build messages — convert entries to model API messages
  6. Prepend system prompt — add the system prompt and context files

24.8 SessionManager API

The SessionManager class manages session files:

interface SessionManager {
  // Create or load a session
  createSession(cwd: string, options?: SessionOptions): Promise<Session>;
  loadSession(path: string): Promise<Session>;
  
  // List sessions
  listSessions(cwd: string): SessionSummary[];
  
  // Session operations
  appendEntry(session: Session, entry: Entry): Promise<void>;
  getEntries(session: Session): Entry[];
  getTree(session: Session): TreeNode;
  
  // Forking
  forkSession(session: Session, fromEntryId: string): Promise<Session>;
  cloneSession(session: Session): Promise<Session>;
  
  // Export
  exportSession(session: Session, format: "html" | "jsonl"): Promise<string>;
  importSession(path: string): Promise<Session>;
}

24.8.1 Using SessionManager in Extensions

pi.on("session_start", async (_event, ctx) => {
  const session = ctx.sessionManager.getCurrentSession();
  
  // Read entries
  const entries = ctx.sessionManager.getEntries(session);
  console.log(`Loaded ${entries.length} entries`);
  
  // Access the tree
  const tree = ctx.sessionManager.getTree(session);
  console.log(`Tree has ${tree.children.length} top-level branches`);
});
Tip

Session files are plain JSONL. You can inspect them with standard tools: jq, grep, head, tail. This makes session data accessible outside Pi.