Sitzungsdateiformat
Sitzungen werden als JSONL (JSON Zeilen) Dateien gespeichert. Jede Zeile ist ein JSON-Objekt mit einem type-Feld. Sitzungseinträge bilden über id/parentId-Felder eine Baumstruktur und ermöglichen eine direkte Verzweigung, ohne dass neue Dateien erstellt werden müssen.
Dateispeicherort
~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonlDabei ist <path> das Arbeitsverzeichnis, wobei / durch - ersetzt wird.
Sitzungen löschen
Sitzungen können entfernt werden, indem ihre .jsonl-Dateien unter ~/.pi/agent/sessions/ gelöscht werden.
Pi unterstützt auch das interaktive Löschen von Sitzungen aus /resume (wählen Sie eine Sitzung aus und drücken Sie Ctrl+D, dann bestätigen). Wenn verfügbar, verwendet Pi trash CLI, um ein dauerhaftes Löschen zu vermeiden.
Sitzungsversion
Sitzungen haben ein Versionsfeld in der Kopfzeile:
- Version 1: Lineare Eingabesequenz (alt, beim Laden automatisch migriert)
- Version 2: Baumstruktur mit
id/parentId-Verknüpfung - Version 3: Rolle
hookMessageincustomumbenannt (Vereinheitlichung der Erweiterungen)
Bestehende Sitzungen werden beim Laden automatisch auf die aktuelle Version (v3) migriert.
Quelldateien
Quelle am GitHub (pi-mono):
packages/coding-agent/src/core/session-manager.ts– Sitzungseintragstypen und SessionManagerpackages/coding-agent/src/core/messages.ts– Erweiterte Nachrichtentypen (BashExecutionMessage, CustomMessage usw.)packages/ai/src/types.ts– Basisnachrichtentypen (UserMessage, AssistantMessage, ToolResultMessage)packages/agent/src/types.ts– AgentMessage-Union-Typ
Überprüfen Sie für TypeScript-Definitionen in Ihrem Projekt node_modules/@earendil-works/pi-coding-agent/dist/ und node_modules/@earendil-works/pi-ai/dist/.
Nachrichtentypen
Sitzungseinträge enthalten AgentMessage Objekte. Das Verständnis dieser Typen ist für das Parsen von Sitzungen und das Schreiben von Erweiterungen unerlässlich.
Inhaltsblöcke
Nachrichten enthalten Arrays typisierter Inhaltsblöcke:
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>;
}Basisnachrichtentypen (von 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;
};
}Der exportierte pi-ai-Typ StopReason enthält auch "pending", dieser Wert ist jedoch für Teilnachrichten in Streaming-Ereignissen reserviert. Terminal-Nachrichten done/error ersetzen es durch einen Abschlussgrund, bevor pi die Assistentennachricht beibehält, sodass "pending" niemals in Sitzung JSONL erscheinen sollte.
Erweiterte Nachrichtentypen (von 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;
}AgentMessage Union
type AgentMessage =
| UserMessage
| AssistantMessage
| ToolResultMessage
| BashExecutionMessage
| CustomMessage
| BranchSummaryMessage
| CompactionSummaryMessage;Einstiegsbasis
Alle Einträge (außer SessionHeader) erweitern 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
}Eintragstypen
SessionHeader
Erste Zeile der Datei. Nur Metadaten, kein Teil des Baums (kein id/parentId).
{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project"}Für Sitzungen mit einem Elternteil (erstellt über /fork, /clone oder 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
Eine Nachricht im Gespräch. Das Feld message enthält eine 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
Wird ausgegeben, wenn der Benutzer mitten in der Sitzung das Modell wechselt.
{"type":"model_change","id":"d4e5f6g7","parentId":"c3d4e5f6","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"gpt-4o"}ThinkingLevelChangeEntry
Wird ausgegeben, wenn der Benutzer die Denk-/Argumentationsebene ändert.
{"type":"thinking_level_change","id":"e5f6g7h8","parentId":"d4e5f6g7","timestamp":"2024-12-03T14:06:00.000Z","thinkingLevel":"high"}Verdichtungseintrag
Wird erstellt, wenn der Kontext komprimiert wird. Speichert eine Zusammenfassung früherer Nachrichten.
{"type":"compaction","id":"f6g7h8i9","parentId":"e5f6g7h8","timestamp":"2024-12-03T14:10:00.000Z","summary":"User discussed X, Y, Z...","firstKeptEntryId":"c3d4e5f6","tokensBefore":50000}Neuere, durch Kabelbäume generierte Verdichtungen betten den beibehaltenen Post-Verdichtungskontext direkt in den Eintrag ein, statt 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"}]}Optionale Felder:
usage: LLM-Nutzung durch Generierung der Zusammenfassung; im Sitzungs-Token und in den Gesamtkosten enthaltenretainedTail: MaterialisiertAgentMessage[]bleibt nach der Verdichtung erhalten. Dies ist nur aus Gründen der Abwärtskompatibilität mit älteren Sitzungen optional. Neuere, durch Kabelbäume generierte Komprimierungen enthalten es, sodass wir den Kontext von diesem Prüfpunkt aus neu erstellen können, ohne ältere Einträge vor dem Komprimierungseintrag zu durchlaufen.details: Implementierungsspezifische Daten (z. B.{ readFiles: string[], modifiedFiles: string[] }für Standard oder benutzerdefinierte Daten für Erweiterungen)fromHook:true, wenn durch eine Erweiterung generiert,false/undefined, wenn Pi-generiert (Legacy-Feldname)firstKeptEntryId: für Kompatibilität mit dem alten Eingabeformat.
BranchSummaryEntry
Wird beim Zweigwechsel über /tree mit einer LLM-generierten Zusammenfassung des linken Zweigs bis zum gemeinsamen Vorfahren erstellt. Erfasst den Kontext des verlassenen Pfads.
{"type":"branch_summary","id":"g7h8i9j0","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:15:00.000Z","fromId":"f6g7h8i9","summary":"Branch explored approach A..."}Optionale Felder:
usage: LLM-Nutzung durch Generierung der Zusammenfassung; im Sitzungs-Token und in den Gesamtkosten enthaltendetails: Dateiverfolgungsdaten ({ readFiles: string[], modifiedFiles: string[] }) für Standard oder benutzerdefinierte Daten für ErweiterungenfromHook:true, wenn durch eine Erweiterung generiert,false/undefined, wenn Pi-generiert (Legacy-Feldname)
Benutzerdefinierter Eintrag
Persistenz des Erweiterungsstatus. Nimmt NICHT am LLM-Kontext teil.
{"type":"custom","id":"h8i9j0k1","parentId":"g7h8i9j0","timestamp":"2024-12-03T14:20:00.000Z","customType":"my-extension","data":{"count":42}}Verwenden Sie customType, um die Einträge Ihrer Erweiterung beim Neuladen zu identifizieren. Der interaktive Modus kann benutzerdefinierte Einträge über pi.registerEntryRenderer(customType, renderer) rendern, sie nehmen jedoch immer noch nicht am LLM-Kontext teil.
CustomMessageEntry
Durch Erweiterungen eingefügte Nachrichten, die am LLM-Kontext beteiligt sind.
{"type":"custom_message","id":"i9j0k1l2","parentId":"h8i9j0k1","timestamp":"2024-12-03T14:25:00.000Z","customType":"my-extension","content":"Injected context...","display":true}Felder:
content: String oder(TextContent | ImageContent)[](wie UserMessage)display:true= in TUI mit eindeutigem Stil anzeigen,false= ausgeblendetdetails: Optionale erweiterungsspezifische Metadaten (nicht an LLM gesendet)
Etiketteneintrag
Benutzerdefiniertes Lesezeichen/Markierung für einen Eintrag.
{"type":"label","id":"j0k1l2m3","parentId":"i9j0k1l2","timestamp":"2024-12-03T14:30:00.000Z","targetId":"a1b2c3d4","label":"checkpoint-1"}Setzen Sie label auf undefined, um ein Etikett zu löschen.
SessionInfoEntry
Sitzungsmetadaten (z. B. benutzerdefinierter Anzeigename). Wird über /name, --name / -n oder pi.setSessionName() in Erweiterungen eingestellt.
{"type":"session_info","id":"k1l2m3n4","parentId":"j0k1l2m3","timestamp":"2024-12-03T14:35:00.000Z","name":"Refactor auth module"}Der Sitzungsname wird in der Sitzungsauswahl (/resume) anstelle der ersten Nachricht angezeigt, wenn diese festgelegt ist.
Baumstruktur
Einträge bilden einen Baum:
- Erster Eintrag hat
parentId: null - Jeder nachfolgende Eintrag verweist über
parentIdauf seinen übergeordneten Eintrag. - Durch die Verzweigung werden neue untergeordnete Elemente aus einem früheren Eintrag erstellt
- Das „Blatt“ ist die aktuelle Position im Baum
[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf
│
└─ [branch_summary] ─── [user msg] ← alternate branchKontextbildung
buildContextEntries() geht vom aktuellen Blatt zur Wurzel und erstellt die aktive Eintragsliste unter Berücksichtigung der Komprimierung:
- Sammelt alle Einträge auf dem Pfad
- Wenn sich eine
CompactionEntryauf dem Pfad befindet:- Beinhaltet zuerst den Komprimierungseintrag
- Wenn
retainedTailvorhanden ist, fungiert es als eigenständiger Prüfpunkt und Einträge nach der Komprimierung werden einbezogen - Ansonsten sind Einträge von
firstKeptEntryIdbis zur Verdichtung enthalten - Dann werden Einträge nach der Komprimierung einbezogen
- Behält Nicht-Nachrichteneinträge im ausgewählten Bereich bei, sodass sie im interaktiven Modus gerendert werden können
buildSessionContext() baut auf dieser Eintragsliste auf, um die Nachrichtenliste für das LLM zu erstellen:
- Extrahiert aktuelle Modell- und Denkebeneneinstellungen aus dem vollständigen Pfad
- Konvertiert ausgewählte Einträge in Nachrichten:
message-> gespeichertAgentMessagecompaction->compactionSummaryplusretainedTail, falls vorhandenbranch_summary->branchSummarycustom_message->CustomMessagecustom-> keine Kontextmeldung
Dadurch wirken neuere Verdichtungen wie eigenständige Kontrollpunkte. retainedTail ist nur optional, damit ältere Sitzungen, die nur firstKeptEntryId speichern, weiterhin korrekt geladen werden.
Parsing-Beispiel
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
Schlüsselmethoden für die programmgesteuerte Arbeit mit Sitzungen.
Statische Erstellungsmethoden
SessionManager.create(cwd, sessionDir?)– Neue SitzungSessionManager.open(path, sessionDir?)– Vorhandene Sitzungsdatei öffnenSessionManager.continueRecent(cwd, sessionDir?)– Mit der neuesten Version fortfahren oder eine neue erstellenSessionManager.inMemory(cwd?)– Keine DateipersistenzSessionManager.forkFrom(sourcePath, targetCwd, sessionDir?)– Fork-Sitzung von einem anderen Projekt
Statische Auflistungsmethoden
SessionManager.list(cwd, sessionDir?, onProgress?)– Sitzungen für ein Verzeichnis auflistenSessionManager.listAll(onProgress?)– Alle Sitzungen in allen Projekten auflisten
Instanzmethoden – Sitzungsverwaltung
newSession(options?)– Eine neue Sitzung starten (Optionen:{ parentSession?: string })setSessionFile(path)– Wechseln Sie zu einer anderen SitzungsdateicreateBranchedSession(leafId)– Zweig in neue Sitzungsdatei extrahieren
Instanzmethoden – Anhängen (alle Rückgabeeintrags-ID)
appendMessage(message)– Nachricht hinzufügenappendThinkingLevelChange(level)– Denkänderungen aufzeichnenappendModelChange(provider, modelId)– Modellwechsel aufzeichnenappendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?)– Komprimierung hinzufügenappendCustomEntry(customType, data?)– Erweiterungsstatus (nicht im Kontext)appendSessionInfo(name)– Sitzungsanzeigenamen festlegenappendCustomMessageEntry(customType, content, display, details?)– Erweiterungsnachricht (im Kontext)appendLabelChange(targetId, label)– Beschriftung festlegen/löschen
Instanzmethoden – Baumnavigation
getLeafId()– Aktuelle PositiongetLeafEntry()– Aktuellen Blatteintrag abrufengetEntry(id)– Erhalten Sie Zutritt per IDgetBranch(fromId?)– Gehen Sie vom Eingang zur WurzelgetTree()– Vollständige Baumstruktur erhaltengetChildren(parentId)– Holen Sie sich direkte KindergetLabel(id)– Label für den Eintrag abrufenbranch(entryId)– Blatt zum früheren Eintrag verschiebenresetLeaf()– Blatt auf Null zurücksetzen (vor irgendwelchen Einträgen)branchWithSummary(entryId, summary, details?, fromHook?)– Zweig mit Kontextzusammenfassung
Instanzmethoden – Kontext und Informationen
buildContextEntries()– Aktive Zweigeinträge mit angewendeter Komprimierung abrufenbuildSessionContext()– Erhalten Sie Nachrichten, Denkebene und Modell für LLMgetEntries()– Alle Einträge (außer Header)getHeader()– Sitzungsheader-MetadatengetSessionName()– Anzeigenamen aus dem letzten session_info-Eintrag abrufengetCwd()– ArbeitsverzeichnisgetSessionDir()– SitzungsspeicherverzeichnisgetSessionId()– Sitzungs-UUIDgetSessionFile()– Sitzungsdateipfad (undefiniert für In-Memory)isPersisted()– Ob die Sitzung auf der Festplatte gespeichert wird