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.
Ubicación del archivo
Sección titulada «Ubicación del archivo»~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonlDonde <path> es el directorio de trabajo con / reemplazado por -.
Eliminar sesiones
Sección titulada «Eliminar sesiones»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.
Versión de sesión
Sección titulada «Versión de sesión»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
hookMessagerenombrado acustom(unificación de extensiones)
Las sesiones existentes se migran automáticamente a la versión actual (v3) al cargarse.
Archivos fuente
Sección titulada «Archivos fuente»Código fuente en GitHub (pi-mono):
packages/coding-agent/src/core/session-manager.ts- Tipos de entrada de sesión y SessionManagerpackages/coding-agent/src/core/messages.ts- Tipos de mensaje extendidos (BashExecutionMessage, CustomMessage, etc.)packages/ai/src/types.ts- Tipos de mensaje base (UserMessage, AssistantMessage, ToolResultMessage)packages/agent/src/types.ts- Tipo unión AgentMessage
Para definiciones TypeScript en tu proyecto, consulta node_modules/@earendil-works/pi-coding-agent/dist/ y node_modules/@earendil-works/pi-ai/dist/.
Tipos de mensaje
Sección titulada «Tipos de mensaje»Las entradas de sesión contienen objetos AgentMessage. Comprender estos tipos es esencial para analizar sesiones y escribir extensiones.
Bloques de contenido
Sección titulada «Bloques de contenido»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>;}Tipos de mensaje base (de pi-ai)
Sección titulada «Tipos de mensaje base (de 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; };}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;}Unión AgentMessage
Sección titulada «Unión AgentMessage»type AgentMessage = | UserMessage | AssistantMessage | ToolResultMessage | BashExecutionMessage | CustomMessage | BranchSummaryMessage | CompactionSummaryMessage;Base de entrada
Sección titulada «Base de entrada»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}Tipos de entrada
Sección titulada «Tipos de entrada»SessionHeader
Sección titulada «SessionHeader»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"}SessionMessageEntry
Sección titulada «SessionMessageEntry»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}}ModelChangeEntry
Sección titulada «ModelChangeEntry»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"}ThinkingLevelChangeEntry
Sección titulada «ThinkingLevelChangeEntry»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"}CompactionEntry
Sección titulada «CompactionEntry»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:truesi lo genera una extensión,false/undefinedsi lo genera pi (nombre de campo legacy)
BranchSummaryEntry
Sección titulada «BranchSummaryEntry»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:truesi lo genera una extensión,false/undefinedsi lo genera pi (nombre de campo legacy)
CustomEntry
Sección titulada «CustomEntry»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.
CustomMessageEntry
Sección titulada «CustomMessageEntry»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= ocultodetails: Metadatos opcionales específicos de extensión (no se envían al LLM)
LabelEntry
Sección titulada «LabelEntry»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.
SessionInfoEntry
Sección titulada «SessionInfoEntry»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.
Estructura de árbol
Sección titulada «Estructura de árbol»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 branchConstrucción de contexto
Sección titulada «Construcción de contexto»buildSessionContext() recorre desde la hoja actual hasta la raíz, produciendo la lista de mensajes para el LLM:
- Recopila todas las entradas en la ruta
- Extrae la configuración actual del modelo y nivel de pensamiento
- Si hay un
CompactionEntryen la ruta:- Emite el resumen primero
- Luego mensajes desde
firstKeptEntryIdhasta la compactación - Luego mensajes después de la compactación
- Convierte
BranchSummaryEntryyCustomMessageEntrya formatos de mensaje apropiados
Ejemplo de análisis
Sección titulada «Ejemplo de análisis»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
Sección titulada «SessionManager API»Métodos clave para trabajar con sesiones de forma programática.
Métodos estáticos de creación
Sección titulada «Métodos estáticos de creación»SessionManager.create(cwd, sessionDir?)- Nueva sesiónSessionManager.open(path, sessionDir?)- Abrir archivo de sesión existenteSessionManager.continueRecent(cwd, sessionDir?)- Continuar la más reciente o crear nuevaSessionManager.inMemory(cwd?)- Sin persistencia en archivoSessionManager.forkFrom(sourcePath, targetCwd, sessionDir?)- Bifurcar sesión desde otro proyecto
Métodos estáticos de listado
Sección titulada «Métodos estáticos de listado»SessionManager.list(cwd, sessionDir?, onProgress?)- Listar sesiones de un directorioSessionManager.listAll(onProgress?)- Listar todas las sesiones de todos los proyectos
Métodos de instancia - Gestión de sesión
Sección titulada «Métodos de instancia - Gestión de sesión»newSession(options?)- Iniciar nueva sesión (opciones:{ parentSession?: string })setSessionFile(path)- Cambiar a otro archivo de sesióncreateBranchedSession(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 mensajeappendThinkingLevelChange(level)- Registrar cambio de nivel de pensamientoappendModelChange(provider, modelId)- Registrar cambio de modeloappendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?)- Añadir compactaciónappendCustomEntry(customType, data?)- Estado de extensión (no en contexto)appendSessionInfo(name)- Establecer nombre de visualización de sesiónappendCustomMessageEntry(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 actualgetLeafEntry()- Obtener entrada hoja actualgetEntry(id)- Obtener entrada por IDgetBranch(fromId?)- Recorrer desde entrada hasta raízgetTree()- Obtener estructura completa del árbolgetChildren(parentId)- Obtener hijos directosgetLabel(id)- Obtener etiqueta de entradabranch(entryId)- Mover hoja a entrada anteriorresetLeaf()- 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 LLMgetEntries()- Todas las entradas (excluyendo encabezado)getHeader()- Metadatos del encabezado de sesióngetSessionName()- Obtener nombre de visualización de la última entrada session_infogetCwd()- Directorio de trabajogetSessionDir()- Directorio de almacenamiento de sesióngetSessionId()- UUID de sesióngetSessionFile()- Ruta del archivo de sesión (undefined para en memoria)isPersisted()- Si la sesión está guardada en disco