Format File Sesi
Sesi disimpan sebagai file JSONL (JSON Baris). Setiap baris adalah objek JSON dengan bidang type. Entri sesi membentuk struktur pohon melalui kolom id/parentId, memungkinkan percabangan di tempat tanpa membuat file baru.
Lokasi Berkas
~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonlDimana <path> adalah direktori kerja dengan / digantikan oleh -.
Menghapus Sesi
Sesi dapat dihapus dengan menghapus file .jsonl di bawah ~/.pi/agent/sessions/.
Pi juga mendukung penghapusan sesi secara interaktif dari /resume (pilih sesi dan tekan Ctrl+D, lalu konfirmasi). Jika tersedia, pi menggunakan trash CLI untuk menghindari penghapusan permanen.
Versi Sesi
Sesi memiliki kolom versi di header:
- Versi 1: Urutan entri linier (lama, dimigrasi otomatis saat dimuat)
- Versi 2: Struktur pohon dengan tautan
id/parentId - Versi 3: Mengganti nama peran
hookMessagemenjadicustom(penyatuan ekstensi)
Sesi yang ada secara otomatis dimigrasikan ke versi saat ini (v3) saat dimuat.
File Sumber
Sumber di GitHub (pi-mono):
packages/coding-agent/src/core/session-manager.ts- Jenis entri sesi dan SessionManagerpackages/coding-agent/src/core/messages.ts- Jenis pesan yang diperluas (BashExecutionMessage, CustomMessage, dll.)packages/ai/src/types.ts- Jenis pesan dasar (UserMessage, AssistantMessage, ToolResultMessage)packages/agent/src/types.ts- Jenis gabungan AgentMessage
Untuk definisi TypeScript dalam proyek Anda, periksa node_modules/@earendil-works/pi-coding-agent/dist/ dan node_modules/@earendil-works/pi-ai/dist/.
Jenis Pesan
Entri sesi berisi objek AgentMessage. Memahami jenis ini penting untuk sesi penguraian dan penulisan ekstensi.
Blok Konten
Pesan berisi array blok konten yang diketik:
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>;
}Jenis Pesan Dasar (dari 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
usage?: Usage; // Nested LLM work performed by the tool
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;
};
}Jenis pi-ai StopReason yang diekspor juga mencakup "pending", tetapi nilai tersebut dicadangkan untuk sebagian pesan dalam acara streaming. Pesan terminal done/error menggantinya dengan alasan penyelesaian sebelum pi mempertahankan pesan asisten, jadi "pending" tidak akan pernah muncul di sesi JSONL.
Jenis Pesan yang Diperluas (dari 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;
}Persatuan AgenPesan
type AgentMessage =
| UserMessage
| AssistantMessage
| ToolResultMessage
| BashExecutionMessage
| CustomMessage
| BranchSummaryMessage
| CompactionSummaryMessage;Basis Masuk
Semua entri (kecuali SessionHeader) diperpanjang 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
}Jenis Entri
SessionHeader
Baris pertama file. Hanya metadata, bukan bagian dari pohon (tidak ada id/parentId).
{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project"}Untuk sesi dengan orang tua (dibuat melalui /fork, /clone, atau 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"}Entri Pesan Sesi
Sebuah pesan dalam percakapan. Bidang message berisi 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}}Entri Perubahan Model
Dipancarkan saat pengguna mengganti model di tengah sesi.
{"type":"model_change","id":"d4e5f6g7","parentId":"c3d4e5f6","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"gpt-4o"}Entri Perubahan Tingkat Berpikir
Dipancarkan ketika pengguna mengubah tingkat berpikir/penalaran.
{"type":"thinking_level_change","id":"e5f6g7h8","parentId":"d4e5f6g7","timestamp":"2024-12-03T14:06:00.000Z","thinkingLevel":"high"}Entri Pemadatan
Dibuat ketika konteks dipadatkan. Menyimpan ringkasan pesan sebelumnya.
{"type":"compaction","id":"f6g7h8i9","parentId":"e5f6g7h8","timestamp":"2024-12-03T14:10:00.000Z","summary":"User discussed X, Y, Z...","firstKeptEntryId":"c3d4e5f6","tokensBefore":50000}Pemadatan yang dihasilkan oleh harness yang lebih baru menyematkan konteks pasca-pemadatan yang dipertahankan langsung pada entri, bukan firstKeptEntryId:
{"type":"compaction","id":"f6g7h8i9","parentId":"e5f6g7h8","timestamp":"2024-12-03T14:10:00.000Z","summary":"User discussed X, Y, Z...","tokensBefore":50000,"retainedTail":[{"role":"user","content":"latest request"},{"role":"assistant","content":[{"type":"text","text":"latest reply"}],"provider":"anthropic","model":"claude-sonnet-4-5","usage":{...},"stopReason":"stop"}]}Bidang opsional:
usage: Penggunaan LLM dari pembuatan ringkasan; termasuk dalam token sesi dan total biayaretainedTail: TerwujudAgentMessage[]disimpan setelah pemadatan. Ini opsional hanya untuk kompatibilitas dengan sesi yang lebih lama. Pemadatan yang dihasilkan oleh harness yang lebih baru menyertakannya sehingga kami dapat membangun kembali konteks dari pos pemeriksaan ini tanpa harus menjalankan entri lama sebelum entri pemadatan.details: Data spesifik implementasi (misalnya,{ readFiles: string[], modifiedFiles: string[] }untuk default, atau data khusus untuk ekstensi)fromHook:truejika dihasilkan oleh ekstensi,false/undefinedjika dihasilkan oleh pi (nama kolom lama)firstKeptEntryId: untuk kompatibilitas dengan format entri lama.
Entri Ringkasan Cabang
Dibuat saat berpindah cabang melalui /tree dengan ringkasan yang dihasilkan LLM dari cabang kiri hingga nenek moyang yang sama. Menangkap konteks dari jalur yang ditinggalkan.
{"type":"branch_summary","id":"g7h8i9j0","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:15:00.000Z","fromId":"f6g7h8i9","summary":"Branch explored approach A..."}Bidang opsional:
usage: Penggunaan LLM dari pembuatan ringkasan; termasuk dalam token sesi dan total biayadetails: Data pelacakan file ({ readFiles: string[], modifiedFiles: string[] }) untuk default, atau data khusus untuk ekstensifromHook:truejika dihasilkan oleh ekstensi,false/undefinedjika dihasilkan oleh pi (nama kolom lama)
Entri Kustom
Persistensi status ekstensi. TIDAK berpartisipasi dalam konteks LLM.
{"type":"custom","id":"h8i9j0k1","parentId":"g7h8i9j0","timestamp":"2024-12-03T14:20:00.000Z","customType":"my-extension","data":{"count":42}}Gunakan customType untuk mengidentifikasi entri ekstensi Anda saat memuat ulang. Mode interaktif dapat merender entri khusus melalui pi.registerEntryRenderer(customType, renderer), tetapi entri tersebut tetap tidak berpartisipasi dalam konteks LLM.
Entri Pesan Khusus
Pesan yang dimasukkan ekstensi yang DO berpartisipasi dalam konteks LLM.
{"type":"custom_message","id":"i9j0k1l2","parentId":"h8i9j0k1","timestamp":"2024-12-03T14:25:00.000Z","customType":"my-extension","content":"Injected context...","display":true}Bidang:
content: String atau(TextContent | ImageContent)[](sama seperti UserMessage)display:true= tampilkan di TUI dengan gaya berbeda,false= tersembunyidetails: Metadata khusus ekstensi opsional (tidak dikirim ke LLM)
LabelEntri
Bookmark/penanda yang ditentukan pengguna pada sebuah entri.
{"type":"label","id":"j0k1l2m3","parentId":"i9j0k1l2","timestamp":"2024-12-03T14:30:00.000Z","targetId":"a1b2c3d4","label":"checkpoint-1"}Setel label ke undefined untuk menghapus label.
Entri Info Sesi
Metadata sesi (misalnya, nama tampilan yang ditentukan pengguna). Diatur melalui /name, --name / -n, atau pi.setSessionName() dalam ekstensi.
{"type":"session_info","id":"k1l2m3n4","parentId":"j0k1l2m3","timestamp":"2024-12-03T14:35:00.000Z","name":"Refactor auth module"}Nama sesi ditampilkan di pemilih sesi (/resume) dan bukan di pesan pertama saat disetel.
Struktur Pohon
Entri membentuk pohon:
- Entri pertama memiliki
parentId: null - Setiap entri berikutnya menunjuk ke induknya melalui
parentId - Percabangan menciptakan anak baru dari entri sebelumnya
- "Daun" adalah posisi saat ini di pohon
[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf
│
└─ [branch_summary] ─── [user msg] ← alternate branchMembangun Konteks
buildContextEntries() berjalan dari daun saat ini ke akar, menghasilkan daftar entri aktif sambil melakukan pemadatan:
- Mengumpulkan semua entri di jalur
- Jika
CompactionEntryada di jalur:- Termasuk entri pemadatan terlebih dahulu
- Jika ada
retainedTail, maka pos tersebut berfungsi sebagai pos pemeriksaan mandiri dan entri setelah pemadatan disertakan - Jika tidak, entri dari
firstKeptEntryIdhingga pemadatan akan disertakan - Kemudian entri setelah pemadatan dimasukkan
- Mempertahankan entri non-pesan dalam rentang yang dipilih sehingga mode interaktif dapat merendernya
buildSessionContext() dibangun berdasarkan daftar entri tersebut untuk menghasilkan daftar pesan untuk LLM:
- Mengekstrak model saat ini dan pengaturan tingkat pemikiran dari jalur lengkap
- Mengonversi entri yang dipilih menjadi pesan:
message-> disimpanAgentMessagecompaction->compactionSummaryditambahretainedTailsaat adabranch_summary->branchSummarycustom_message->CustomMessagecustom-> tidak ada pesan konteks
Hal ini membuat pemadatan yang lebih baru berfungsi seperti pos pemeriksaan mandiri. retainedTail bersifat opsional sehingga sesi lama yang hanya menyimpan firstKeptEntryId terus dimuat dengan benar.
Contoh Penguraian
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;
}
}Manajer Sesi API
Metode utama untuk bekerja dengan sesi secara terprogram.
Metode Penciptaan Statis
SessionManager.create(cwd, sessionDir?)- Sesi baruSessionManager.open(path, sessionDir?)- Buka file sesi yang adaSessionManager.continueRecent(cwd, sessionDir?)- Lanjutkan yang terbaru atau buat yang baruSessionManager.inMemory(cwd?)- Tidak ada persistensi fileSessionManager.forkFrom(sourcePath, targetCwd, sessionDir?)- Sesi fork dari proyek lain
Metode Daftar Statis
SessionManager.list(cwd, sessionDir?, onProgress?)- Daftar sesi untuk direktoriSessionManager.listAll(onProgress?)- Daftar semua sesi di semua proyek
Metode Instance - Manajemen Sesi
newSession(options?)- Memulai sesi baru (opsi:{ parentSession?: string })setSessionFile(path)- Beralih ke file sesi laincreateBranchedSession(leafId)- Ekstrak cabang ke file sesi baru
Metode Instance - Menambahkan (semua ID entri kembali)
appendMessage(message)- Tambahkan pesanappendThinkingLevelChange(level)- Rekam perubahan pemikiranappendModelChange(provider, modelId)- Rekam perubahan modelappendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?)- Tambahkan pemadatanappendCustomEntry(customType, data?)- Status ekstensi (tidak dalam konteks)appendSessionInfo(name)- Tetapkan nama tampilan sesiappendCustomMessageEntry(customType, content, display, details?)- Pesan ekstensi (dalam konteks)appendLabelChange(targetId, label)- Setel/hapus label
Metode Instance - Navigasi Pohon
getLeafId()- Posisi saat inigetLeafEntry()- Dapatkan entri daun saat inigetEntry(id)- Dapatkan entri berdasarkan IDgetBranch(fromId?)- Berjalan dari entri ke rootgetTree()- Dapatkan struktur pohon lengkapgetChildren(parentId)- Dapatkan anak langsunggetLabel(id)- Dapatkan label untuk masukbranch(entryId)- Pindahkan daun ke entri sebelumnyaresetLeaf()- Setel ulang daun ke nol (sebelum entri apa pun)branchWithSummary(entryId, summary, details?, fromHook?)- Cabang dengan ringkasan konteks
Metode Instance - Konteks & Info
buildContextEntries()- Dapatkan entri cabang aktif dengan pemadatan diterapkanbuildSessionContext()- Dapatkan pesan, Tingkat berpikir, dan model untuk LLMgetEntries()- Semua entri (tidak termasuk header)getHeader()- Metadata header sesigetSessionName()- Dapatkan nama tampilan dari entri session_info terbarugetCwd()- Direktori kerjagetSessionDir()- Direktori penyimpanan sesigetSessionId()- Sesi UUIDgetSessionFile()- Jalur file sesi (tidak ditentukan untuk dalam memori)isPersisted()- Apakah sesi disimpan ke disk