mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 22:03:45 +00:00
- Add SessionInfoEntry type for session metadata - Add /name <name> command to set session display name - Add pi.setSessionName() and pi.getSessionName() extension API - Session selector shows name (in warning color) instead of first message when set - Session name included in fuzzy search - /session command displays name when set closes #650
255 lines
9.5 KiB
Markdown
255 lines
9.5 KiB
Markdown
# 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
|
|
|
|
```
|
|
~/.pi/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`/`parentId` linking
|
|
- **Version 3**: Renamed `hookMessage` role to `custom` (extensions unification)
|
|
|
|
Existing sessions are automatically migrated to the current version (v3) when loaded.
|
|
|
|
## Type Definitions
|
|
|
|
- [`src/core/session-manager.ts`](../src/core/session-manager.ts) - Session entry types
|
|
- [`packages/agent-core/src/types.ts`](../../agent-core/src/types.ts) - `AgentMessage`
|
|
- [`packages/ai/src/types.ts`](../../ai/src/types.ts) - `UserMessage`, `AssistantMessage`, `ToolResultMessage`, `Usage`, `ToolCall`, `ImageContent`, `TextContent`
|
|
|
|
## 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":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project"}
|
|
```
|
|
|
|
For sessions with a parent (created via `/fork` or `newSession({ parentSession })`):
|
|
|
|
```json
|
|
{"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`.
|
|
|
|
```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 extension implementations)
|
|
- `fromHook`: `true` if generated by an extension, `false`/`undefined` if pi-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 an extension
|
|
|
|
### CustomEntry
|
|
|
|
Extension state persistence. Does NOT participate in LLM context.
|
|
|
|
```json
|
|
{"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.
|
|
|
|
### CustomMessageEntry
|
|
|
|
Extension-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-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.
|
|
|
|
```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.
|
|
|
|
### SessionInfoEntry
|
|
|
|
Session metadata (e.g., user-defined display name). Set via `/name` command or `pi.setSessionName()` in extensions.
|
|
|
|
```json
|
|
{"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.
|
|
|
|
## 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 { 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:
|
|
|
|
### 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?)` - Extension state (not in context)
|
|
- `appendSessionInfo(name)` - Set session display name
|
|
- `appendCustomMessageEntry(customType, content, display, details?)` - Extension message (in context)
|
|
- `appendLabelChange(targetId, label)` - Set/clear label
|
|
|
|
### 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
|
|
|
|
### Context
|
|
- `buildSessionContext()` - Get messages for LLM
|
|
- `getEntries()` - All entries (excluding header)
|
|
- `getHeader()` - Session metadata
|
|
- `getSessionName()` - Get display name from latest session_info entry
|