Configuración, personalización, ajustes de plataforma y referencias de API para Pi.

Formato de archivo de sesión

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

Ubicación del archivo

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

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

Eliminar sesiones

Las sesiones se pueden eliminar eliminando sus archivos .jsonl en ~/.pi/agent/sessions/.

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

Versión de sesión

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

  • Versión 1: Secuencia de entrada lineal (heredada, migrada automáticamente al cargar)
  • Versión 2: Estructura de árbol con enlaces id/parentId
  • Versión 3: Se cambió el nombre del rol hookMessage a custom (unificación de extensiones)

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

Archivos fuente

Fuente en GitHub (pi-mono):

Para las definiciones de TypeScript en su proyecto, inspeccione node_modules/@earendil-works/pi-coding-agent/dist/ y node_modules/@earendil-works/pi-ai/dist/.

Tipos de mensajes

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

Content Blocks

Los mensajes contienen matrices de bloques de contenido escritos:

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

Base Message Types (from 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;
  };
}

El tipo pi-ai exportado StopReason también incluye "pending", pero ese valor está reservado para mensajes parciales en eventos de transmisión. Los mensajes del terminal done/error lo reemplazan con un motivo de finalización antes de que pi persista en el mensaje del asistente, por lo que "pending" nunca debería aparecer en la sesión JSONL.

Extended Message Types (from 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;

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

SessionHeader

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

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

Para sesiones con uno de los padres (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

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

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

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

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}

Las compactaciones más recientes generadas por Pi incorporan el contexto posterior a la compactación directamente en la entrada, en lugar de 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"}]}

Campos opcionales:

  • usage: uso de LLM desde la generación del resumen; incluido en el token de sesión y los costos totales
  • retainedTail: AgentMessage[] materializado que se conserva después de la compactación. Es opcional solo por compatibilidad con sesiones anteriores. Las compactaciones más recientes generadas por Pi lo incluyen para reconstruir el contexto desde ese punto de control sin recorrer las entradas antiguas anteriores a la entrada de compactación.
  • details: datos específicos de la implementación (por ejemplo, { readFiles: string[], modifiedFiles: string[] } para datos predeterminados o personalizados para extensiones)
  • fromHook: true si es generado por una extensión, false/undefined si es generado por pi (nombre de campo heredado)
  • firstKeptEntryId: por compatibilidad con el formato de entrada anterior.

BranchSummaryEntry

Creado al cambiar de rama a través de /tree con un resumen generado por LLM de la rama izquierda hasta el ancestro común. Captura el contexto del camino abandonado.

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

Campos opcionales:

  • usage: uso de LLM desde la generación del resumen; incluido en el token de sesión y los costos totales
  • details: datos de seguimiento de archivos ({ readFiles: string[], modifiedFiles: string[] }) para datos predeterminados o personalizados para extensiones
  • fromHook: true si es generado por una extensión, false/undefined si es generado por pi (nombre de campo heredado)

CustomEntry

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

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

Utilice customType para identificar las entradas de su extensión al recargar. El modo interactivo puede representar entradas personalizadas a través de pi.registerEntryRenderer(customType, renderer), pero aún no participan en el contexto LLM.

CustomMessageEntry

Mensajes inyectados con extensión que SÍ participan en el contexto 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 un estilo distinto, false = oculto
  • details: metadatos específicos de la extensión opcionales (no enviados a LLM)

LabelEntry

Marcador/marcador 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"}

Establezca label en undefined para borrar una etiqueta.

SessionInfoEntry

Metadatos de la sesión (por ejemplo, nombre para mostrar definido por el usuario). Establecer 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 la sesión se muestra en el selector de sesión (/resume) en lugar del primer mensaje cuando se configura.

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 a partir de 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

Construcción de contexto

buildContextEntries() camina desde la hoja actual hasta la raíz, generando la lista de entradas activas respetando la compactación:

  1. Recoge todas las entradas en el camino.
  2. Si hay un CompactionEntry en el camino:
    • Incluye la entrada de compactación primero.
    • Si retainedTail está presente, actúa como un punto de control autónomo y se incluyen las entradas después de la compactación.
    • De lo contrario se incluyen las entradas desde firstKeptEntryId a la compactación.
    • Luego se incluyen las entradas después de la compactación.
  3. Conserva las entradas que no son mensajes en el rango seleccionado para que el modo interactivo pueda representarlas

buildSessionContext() se basa en esa lista de entradas para producir la lista de mensajes para el LLM:

  1. Extrae el modelo actual y la configuración del nivel de pensamiento de la ruta completa.
  2. Convierte entradas seleccionadas en mensajes:
    • message -> almacenado AgentMessage
    • compaction -> compactionSummary más retainedTail cuando esté presente
    • branch_summary -> branchSummary
    • custom_message -> CustomMessage
    • custom -> sin mensaje de contexto

Esto hace que las compactaciones más nuevas actúen como puntos de control autónomos. retainedTail es opcional solo para que las sesiones más antiguas que solo almacenan firstKeptEntryId continúen cargándose correctamente.

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

Administrador de sesión API

Métodos clave para trabajar con sesiones mediante programación.

Static Creation Methods

  • SessionManager.create(cwd, sessionDir?) - Nueva sesión
  • SessionManager.open(path, sessionDir?) - Abrir archivo de sesión existente
  • SessionManager.continueRecent(cwd, sessionDir?) - Continuar con el más reciente o crear uno nuevo
  • SessionManager.inMemory(cwd?) - Sin persistencia de archivos
  • SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?) - Sesión bifurcada de otro proyecto

Static Listing Methods

  • SessionManager.list(cwd, sessionDir?, onProgress?) - Listar sesiones para un directorio
  • SessionManager.listAll(onProgress?): enumera todas las sesiones de todos los proyectos

Instance Methods - Session Management

  • newSession(options?) - Iniciar una nueva sesión (opciones: { parentSession?: string })
  • setSessionFile(path) - Cambiar a un archivo de sesión diferente
  • createBranchedSession(leafId) - Extraer rama a un nuevo archivo de sesión

Instance Methods - Appending (all return entry ID)

  • appendMessage(message) - Agregar mensaje
  • appendThinkingLevelChange(level) - Registrar cambio de pensamiento
  • appendModelChange(provider, modelId) - Cambio de modelo de registro
  • appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?) - Agregar compactación
  • appendCustomEntry(customType, data?) - Estado de extensión (no en contexto)
  • appendSessionInfo(name) - Establecer nombre para mostrar de la sesión
  • appendCustomMessageEntry(customType, content, display, details?) - Mensaje de extensión (en contexto)
  • appendLabelChange(targetId, label) - Establecer/borrar etiqueta

Instance Methods - Tree Navigation

  • getLeafId() - Posición actual
  • getLeafEntry() - Obtener la entrada de la hoja actual
  • getEntry(id) - Obtener entrada por ID
  • getBranch(fromId?) - Camina desde la entrada hasta la raíz
  • getTree() - Obtener estructura de árbol completa
  • getChildren(parentId) - Obtener hijos directos
  • getLabel(id) - Obtener etiqueta para ingresar
  • branch(entryId) - Mover hoja a la entrada anterior
  • resetLeaf() - Restablecer la hoja a nula (antes de cualquier entrada)
  • branchWithSummary(entryId, summary, details?, fromHook?) - Rama con resumen de contexto

Instance Methods - Context & Info

  • buildContextEntries() - Obtener entradas de la rama activa con compactación aplicada
  • buildSessionContext() - Recibe mensajes, nivel de pensamiento y modelo para LLM
  • getEntries() - Todas las entradas (excluyendo el encabezado)
  • getHeader() - Metadatos del encabezado de sesión
  • getSessionName(): obtiene el nombre para mostrar de la última entrada de session_info
  • getCwd() - Directorio de trabajo
  • getSessionDir() - Directorio de almacenamiento de sesiones
  • getSessionId() - UUID de sesión
  • getSessionFile() - Ruta del archivo de sesión (no definida para en memoria)
  • isPersisted(): si la sesión se guarda en el disco