コンテンツにスキップ

セッションファイル形式

セッションは JSONL(JSON Lines)ファイルとして保存されます。各行は type フィールドを持つ JSON オブジェクトです。セッションエントリは id/parentId フィールドでツリー構造を形成し、新しいファイルを作成せずにその場でブランチできます。

~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl

<path> は作業ディレクトリで、/- に置き換えられます。

~/.pi/agent/sessions/ 配下の .jsonl ファイルを削除することで、セッションを削除できます。

Pi は /resume からの対話的な削除もサポートしています(セッションを選択して Ctrl+D を押し、確認します)。利用可能な場合、pi は trash CLI を使用して完全削除を避けます。

セッションのヘッダーにはバージョンフィールドがあります:

  • Version 1:線形エントリシーケンス(レガシー、読み込み時に自動移行)
  • Version 2id/parentId リンクによるツリー構造
  • Version 3hookMessage ロールを custom に改名(拡張の統一)

既存のセッションは読み込み時に現在のバージョン(v3)へ自動移行されます。

GitHub 上のソース(pi-mono):

プロジェクトで TypeScript 定義を確認するには、node_modules/@earendil-works/pi-coding-agent/dist/node_modules/@earendil-works/pi-ai/dist/ を参照してください。

セッションエントリには AgentMessage オブジェクトが含まれます。これらの型を理解することは、セッションの解析と拡張の作成に不可欠です。

メッセージには型付きコンテンツブロックの配列が含まれます:

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>;
}

基本メッセージ型(pi-ai より)

Section titled “基本メッセージ型(pi-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;
};
}

拡張メッセージ型(pi-coding-agent より)

Section titled “拡張メッセージ型(pi-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;
}
type AgentMessage =
| UserMessage
| AssistantMessage
| ToolResultMessage
| BashExecutionMessage
| CustomMessage
| BranchSummaryMessage
| CompactionSummaryMessage;

すべてのエントリ(SessionHeader を除く)は 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
}

ファイルの最初の行。メタデータのみで、ツリーの一部ではありません(id/parentId なし)。

{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project"}

親セッションを持つセッション(/fork/clone、または 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"}

会話内のメッセージ。message フィールドには 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}}

ユーザーがセッション中にモデルを切り替えたときに発行されます。

{"type":"model_change","id":"d4e5f6g7","parentId":"c3d4e5f6","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"gpt-4o"}

ユーザーが思考/推論レベルを変更したときに発行されます。

{"type":"thinking_level_change","id":"e5f6g7h8","parentId":"d4e5f6g7","timestamp":"2024-12-03T14:06:00.000Z","thinkingLevel":"high"}

コンテキストがコンパクションされたときに作成されます。以前のメッセージの要約を保存します。

{"type":"compaction","id":"f6g7h8i9","parentId":"e5f6g7h8","timestamp":"2024-12-03T14:10:00.000Z","summary":"User discussed X, Y, Z...","firstKeptEntryId":"c3d4e5f6","tokensBefore":50000}

オプションフィールド:

  • details:実装固有のデータ(例:デフォルトの { readFiles: string[], modifiedFiles: string[] }、または拡張のカスタムデータ)
  • fromHook:拡張で生成された場合は true、pi で生成された場合は false/undefined(レガシーフィールド名)

/tree でブランチを切り替えるときに作成されます。共通祖先までの左ブランチの LLM 生成要約を含みます。放棄されたパスのコンテキストをキャプチャします。

{"type":"branch_summary","id":"g7h8i9j0","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:15:00.000Z","fromId":"f6g7h8i9","summary":"Branch explored approach A..."}

オプションフィールド:

  • details:ファイル追跡データ(デフォルトの { readFiles: string[], modifiedFiles: string[] }、または拡張のカスタムデータ)
  • fromHook:拡張で生成された場合は true、pi で生成された場合は false/undefined(レガシーフィールド名)

拡張状態の永続化。LLM コンテキストには参加しません。

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

再読み込み時に拡張のエントリを識別するには customType を使用します。

LLM コンテキストに参加する拡張注入メッセージ。

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

フィールド:

  • content:文字列または (TextContent | ImageContent)[](UserMessage と同じ)
  • displaytrue = TUI に独自スタイルで表示、false = 非表示
  • details:オプションの拡張固有メタデータ(LLM には送信されない)

エントリ上のユーザー定義ブックマーク/マーカー。

{"type":"label","id":"j0k1l2m3","parentId":"i9j0k1l2","timestamp":"2024-12-03T14:30:00.000Z","targetId":"a1b2c3d4","label":"checkpoint-1"}

ラベルをクリアするには labelundefined に設定します。

セッションメタデータ(例:ユーザー定義の表示名)。/name--name / -n、または拡張内の pi.setSessionName() で設定します。

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

設定すると、セッション名は最初のメッセージの代わりにセッションセレクター(/resume)に表示されます。

エントリはツリーを形成します:

  • 最初のエントリは parentId: null
  • 各後続エントリは parentId で親を指す
  • ブランチは以前のエントリから新しい子を作成する
  • 「リーフ」はツリー内の現在位置
[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf
└─ [branch_summary] ─── [user msg] ← alternate branch

buildSessionContext() は現在のリーフからルートまで走査し、LLM 用のメッセージリストを生成します:

  1. パス上のすべてのエントリを収集
  2. 現在のモデルと思考レベル設定を抽出
  3. パス上に CompactionEntry がある場合:
    • まず要約を出力
    • 次に firstKeptEntryId からコンパクションまでのメッセージ
    • その後コンパクション以降のメッセージ
  4. BranchSummaryEntryCustomMessageEntry を適切なメッセージ形式に変換
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.create(cwd, sessionDir?) - 新規セッション
  • SessionManager.open(path, sessionDir?) - 既存セッションファイルを開く
  • SessionManager.continueRecent(cwd, sessionDir?) - 最新セッションを続行または新規作成
  • SessionManager.inMemory(cwd?) - ファイル永続化なし
  • SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?) - 別プロジェクトからセッションをフォーク
  • SessionManager.list(cwd, sessionDir?, onProgress?) - ディレクトリのセッションを一覧
  • SessionManager.listAll(onProgress?) - すべてのプロジェクトのセッションを一覧

インスタンスメソッド - セッション管理

Section titled “インスタンスメソッド - セッション管理”
  • newSession(options?) - 新規セッションを開始(オプション:{ parentSession?: string }
  • setSessionFile(path) - 別のセッションファイルに切り替え
  • createBranchedSession(leafId) - ブランチを新しいセッションファイルに抽出

インスタンスメソッド - 追加(すべてエントリ ID を返す)

Section titled “インスタンスメソッド - 追加(すべてエントリ ID を返す)”
  • appendMessage(message) - メッセージを追加
  • appendThinkingLevelChange(level) - 思考レベル変更を記録
  • appendModelChange(provider, modelId) - モデル変更を記録
  • appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?) - コンパクションを追加
  • appendCustomEntry(customType, data?) - 拡張状態(コンテキスト外)
  • appendSessionInfo(name) - セッション表示名を設定
  • appendCustomMessageEntry(customType, content, display, details?) - 拡張メッセージ(コンテキスト内)
  • appendLabelChange(targetId, label) - ラベルを設定/クリア

インスタンスメソッド - ツリーナビゲーション

Section titled “インスタンスメソッド - ツリーナビゲーション”
  • getLeafId() - 現在位置
  • getLeafEntry() - 現在のリーフエントリを取得
  • getEntry(id) - ID でエントリを取得
  • getBranch(fromId?) - エントリからルートまで走査
  • getTree() - 完全なツリー構造を取得
  • getChildren(parentId) - 直接の子を取得
  • getLabel(id) - エントリのラベルを取得
  • branch(entryId) - リーフを以前のエントリに移動
  • resetLeaf() - リーフを null にリセット(すべてのエントリより前)
  • branchWithSummary(entryId, summary, details?, fromHook?) - コンテキスト要約付きブランチ

インスタンスメソッド - コンテキストと情報

Section titled “インスタンスメソッド - コンテキストと情報”
  • buildSessionContext() - LLM 用のメッセージ、thinkingLevel、model を取得
  • getEntries() - すべてのエントリ(ヘッダーを除く)
  • getHeader() - セッションヘッダーメタデータ
  • getSessionName() - 最新の session_info エントリから表示名を取得
  • getCwd() - 作業ディレクトリ
  • getSessionDir() - セッション保存ディレクトリ
  • getSessionId() - セッション UUID
  • getSessionFile() - セッションファイルパス(インメモリの場合は undefined)
  • isPersisted() - セッションがディスクに保存されているか