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.

Bloques de contenido

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

Tipos de mensajes básicos (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
  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.

Tipos de mensajes 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 de mensajes de agente

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

Encabezado de sesión

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

Entrada de mensaje de sesión

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

Entrada de cambio de modelo

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

PensamientoNivelCambioEntrada

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

Entrada de compactación

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 nuevas generadas por arnés incorporan el contexto posterior a la compactación retenido 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: Materializado AgentMessage[] mantenido después de la compactación. Esto es opcional sólo por compatibilidad con sesiones anteriores. Las compactaciones más nuevas generadas por arnés lo incluyen para que podamos reconstruir el contexto desde este punto de control sin tener que recorrer las entradas más antiguas antes de 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.

RamaResumenEntrada

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)

Entrada personalizada

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.

Entrada de mensaje personalizado

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)

Entrada de etiqueta

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.

Entrada de información de sesión

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.

Métodos de creación estática

  • 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

Métodos de listado estático

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

Métodos de instancia: gestión de sesiones

  • 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

Métodos de instancia: anexar (todos los ID de entrada devueltos)

  • 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

Métodos de instancia: navegación en árbol

  • 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

Métodos de instancia: contexto e información

  • buildContextEntries() - Obtener entradas de sucursales activas 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