Saltearse al contenido

Formato de archivo de sesión

Las sesiones se almacenan como archivos JSONL (JSON Lines). Cada línea es un objeto JSON con un campo type. Las entradas de sesión forman una estructura de árbol mediante los campos id/parentId, lo que permite ramificar in situ sin crear nuevos archivos.

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

Donde <path> es el directorio de trabajo con / reemplazado por -.

Las sesiones pueden eliminarse borrando sus archivos .jsonl bajo ~/.pi/agent/sessions/.

Pi también admite eliminar sesiones de forma interactiva desde /resume (selecciona una sesión y pulsa Ctrl+D, luego confirma). Cuando está disponible, pi usa el CLI trash para evitar la eliminación permanente.

Las sesiones tienen un campo de versión en el encabezado:

  • Version 1: Secuencia lineal de entradas (legacy, migración automática al cargar)
  • Version 2: Estructura de árbol con enlaces id/parentId
  • Version 3: Rol hookMessage renombrado a custom (unificación de extensiones)

Las sesiones existentes se migran automáticamente a la versión actual (v3) al cargarse.

Código fuente en GitHub (pi-mono):

Para definiciones TypeScript en tu proyecto, consulta node_modules/@earendil-works/pi-coding-agent/dist/ y node_modules/@earendil-works/pi-ai/dist/.

Las entradas de sesión contienen objetos AgentMessage. Comprender estos tipos es esencial para analizar sesiones y escribir extensiones.

Los mensajes contienen arrays de bloques de contenido tipados:

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

Tipos de mensaje extendidos (de pi-coding-agent)

Sección titulada «Tipos de mensaje extendidos (de 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;

Todas las entradas (excepto SessionHeader) extienden 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
}

Primera línea del archivo. Solo metadatos, no forma parte del árbol (sin id/parentId).

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

Para sesiones con padre (creadas mediante /fork, /clone o 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"}

Un mensaje en la conversación. El campo message contiene un 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}}

Emitido cuando el usuario cambia de modelo a mitad de sesión.

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

Emitido cuando el usuario cambia el nivel de pensamiento/razonamiento.

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

Creado cuando se compacta el contexto. Almacena un resumen de mensajes anteriores.

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

Campos opcionales:

  • details: Datos específicos de implementación (p. ej., { readFiles: string[], modifiedFiles: string[] } por defecto, o datos personalizados para extensiones)
  • fromHook: true si lo genera una extensión, false/undefined si lo genera pi (nombre de campo legacy)

Creado al cambiar de rama mediante /tree con un resumen generado por LLM de la rama izquierda hasta el ancestro común. Captura el contexto de la ruta abandonada.

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

Campos opcionales:

  • details: Datos de seguimiento de archivos ({ readFiles: string[], modifiedFiles: string[] } por defecto, o datos personalizados para extensiones)
  • fromHook: true si lo genera una extensión, false/undefined si lo genera pi (nombre de campo legacy)

Persistencia de estado de extensión. NO participa en el contexto del LLM.

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

Usa customType para identificar las entradas de tu extensión al recargar.

Mensajes inyectados por extensiones que SÍ participan en el contexto del LLM.

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

Campos:

  • content: Cadena o (TextContent | ImageContent)[] (igual que UserMessage)
  • display: true = mostrar en TUI con estilo distintivo, false = oculto
  • details: Metadatos opcionales específicos de extensión (no se envían al LLM)

Marcador/bookmark definido por el usuario en una entrada.

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

Establece label en undefined para borrar una etiqueta.

Metadatos de sesión (p. ej., nombre de visualización definido por el usuario). Se establece mediante /name, --name / -n, o pi.setSessionName() en extensiones.

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

El nombre de sesión se muestra en el selector de sesiones (/resume) en lugar del primer mensaje cuando está establecido.

Las entradas forman un árbol:

  • La primera entrada tiene parentId: null
  • Cada entrada posterior apunta a su padre mediante parentId
  • La ramificación crea nuevos hijos desde una entrada anterior
  • La “hoja” es la posición actual en el árbol
[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf
└─ [branch_summary] ─── [user msg] ← alternate branch

buildSessionContext() recorre desde la hoja actual hasta la raíz, produciendo la lista de mensajes para el LLM:

  1. Recopila todas las entradas en la ruta
  2. Extrae la configuración actual del modelo y nivel de pensamiento
  3. Si hay un CompactionEntry en la ruta:
    • Emite el resumen primero
    • Luego mensajes desde firstKeptEntryId hasta la compactación
    • Luego mensajes después de la compactación
  4. Convierte BranchSummaryEntry y CustomMessageEntry a formatos de mensaje apropiados
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;
}
}

Métodos clave para trabajar con sesiones de forma programática.

  • SessionManager.create(cwd, sessionDir?) - Nueva sesión
  • SessionManager.open(path, sessionDir?) - Abrir archivo de sesión existente
  • SessionManager.continueRecent(cwd, sessionDir?) - Continuar la más reciente o crear nueva
  • SessionManager.inMemory(cwd?) - Sin persistencia en archivo
  • SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?) - Bifurcar sesión desde otro proyecto
  • SessionManager.list(cwd, sessionDir?, onProgress?) - Listar sesiones de un directorio
  • SessionManager.listAll(onProgress?) - Listar todas las sesiones de todos los proyectos
  • newSession(options?) - Iniciar nueva sesión (opciones: { parentSession?: string })
  • setSessionFile(path) - Cambiar a otro archivo de sesión
  • createBranchedSession(leafId) - Extraer rama a nuevo archivo de sesión

Métodos de instancia - Añadir (todos devuelven ID de entrada)

Sección titulada «Métodos de instancia - Añadir (todos devuelven ID de entrada)»
  • appendMessage(message) - Añadir mensaje
  • appendThinkingLevelChange(level) - Registrar cambio de nivel de pensamiento
  • appendModelChange(provider, modelId) - Registrar cambio de modelo
  • appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?) - Añadir compactación
  • appendCustomEntry(customType, data?) - Estado de extensión (no en contexto)
  • appendSessionInfo(name) - Establecer nombre de visualización de sesión
  • appendCustomMessageEntry(customType, content, display, details?) - Mensaje de extensión (en contexto)
  • appendLabelChange(targetId, label) - Establecer/borrar etiqueta

Métodos de instancia - Navegación del árbol

Sección titulada «Métodos de instancia - Navegación del árbol»
  • getLeafId() - Posición actual
  • getLeafEntry() - Obtener entrada hoja actual
  • getEntry(id) - Obtener entrada por ID
  • getBranch(fromId?) - Recorrer desde entrada hasta raíz
  • getTree() - Obtener estructura completa del árbol
  • getChildren(parentId) - Obtener hijos directos
  • getLabel(id) - Obtener etiqueta de entrada
  • branch(entryId) - Mover hoja a entrada anterior
  • resetLeaf() - Restablecer hoja a null (antes de cualquier entrada)
  • branchWithSummary(entryId, summary, details?, fromHook?) - Ramificar con resumen de contexto

Métodos de instancia - Contexto e información

Sección titulada «Métodos de instancia - Contexto e información»
  • buildSessionContext() - Obtener mensajes, thinkingLevel y model para LLM
  • getEntries() - Todas las entradas (excluyendo encabezado)
  • getHeader() - Metadatos del encabezado de sesión
  • getSessionName() - Obtener nombre de visualización de la última entrada session_info
  • getCwd() - Directorio de trabajo
  • getSessionDir() - Directorio de almacenamiento de sesión
  • getSessionId() - UUID de sesión
  • getSessionFile() - Ruta del archivo de sesión (undefined para en memoria)
  • isPersisted() - Si la sesión está guardada en disco