Configuração, personalização, ajustes de plataforma e referências de API para Pi.

Formato de arquivo de sessão

As sessões são armazenadas como arquivos JSONL (JSON Linhas). Cada linha é um objeto JSON com um campo type. As entradas de sessão formam uma estrutura em árvore por meio dos campos id/parentId, permitindo ramificações no local sem criar novos arquivos.

Localização do arquivo

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

Onde <path> é o diretório de trabalho com / substituído por -.

Excluindo Sessões

As sessões podem ser removidas excluindo seus arquivos .jsonl em ~/.pi/agent/sessions/.

Pi também suporta a exclusão interativa de sessões de /resume (selecione uma sessão e pressione Ctrl+D e confirme). Quando disponível, pi usa trash CLI para evitar exclusão permanente.

Versão da sessão

As sessões têm um campo de versão no cabeçalho:

  • Versão 1: sequência de entrada linear (herdada, migrada automaticamente durante o carregamento)
  • Versão 2: Estrutura em árvore com ligação id/parentId
  • Versão 3: Função hookMessage renomeada para custom (unificação de extensões)

As sessões existentes são migradas automaticamente para a versão atual (v3) quando carregadas.

Arquivos de origem

Fonte em GitHub (pi-mono):

Para definições de TypeScript em seu projeto, inspecione node_modules/@earendil-works/pi-coding-agent/dist/ e node_modules/@earendil-works/pi-ai/dist/.

Tipos de mensagens

As entradas de sessão contêm objetos AgentMessage. Compreender esses tipos é essencial para analisar sessões e escrever extensões.

Blocos de conteúdo

As mensagens contêm matrizes de blocos de conteúdo digitados:

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 básicos de mensagens (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;
  };
}

O tipo pi-ai StopReason exportado também inclui "pending", mas esse valor é reservado para mensagens parciais em eventos de streaming. As mensagens done/error do terminal substituem-no por um motivo de conclusão antes que pi persista a mensagem do assistente, então "pending" nunca deve aparecer na sessão JSONL.

Tipos de mensagens estendidas (do 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ão de mensagem do agente

type AgentMessage =
  | UserMessage
  | AssistantMessage
  | ToolResultMessage
  | BashExecutionMessage
  | CustomMessage
  | BranchSummaryMessage
  | CompactionSummaryMessage;

Base de Entrada

Todas as entradas (exceto SessionHeader) estendem 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

Cabeçalho da Sessão

Primeira linha do arquivo. Apenas metadados, não fazem parte da árvore (não id/parentId).

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

Para sessões com um pai (criadas via /fork, /clone ou 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"}

SessãoMessageEntry

Uma mensagem na conversa. O campo message contém um 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 quando o usuário troca de modelo no meio da sessão.

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

ThinkingLevelChangeEntry

Emitido quando o usuário altera o nível de pensamento/raciocínio.

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

Entrada de compactação

Criado quando o contexto é compactado. Armazena um resumo de mensagens anteriores.

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

As compactações geradas por chicotes mais recentes incorporam o contexto pós-compactação retido diretamente na entrada, em vez 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 opcionais:

  • usage: uso do LLM a partir da geração do resumo; incluído no token de sessão e nos totais de custo
  • retainedTail: Materializado AgentMessage[] mantido após compactação. Isto é opcional apenas para compatibilidade retroativa com sessões mais antigas. As compactações geradas por chicotes mais recentes incluem-no para que possamos reconstruir o contexto a partir deste ponto de verificação sem percorrer entradas mais antigas antes da entrada de compactação.
  • details: Dados específicos da implementação (por exemplo, { readFiles: string[], modifiedFiles: string[] } para padrão ou dados personalizados para extensões)
  • fromHook: true se gerado por uma extensão, false/undefined se gerado por pi (nome do campo legado)
  • firstKeptEntryId: para compatibilidade com formato de entrada antigo.

FilialSummaryEntry

Criado ao alternar ramificações via /tree com um resumo gerado por LLM da ramificação esquerda até o ancestral comum. Captura o contexto do caminho abandonado.

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

Campos opcionais:

  • usage: uso do LLM a partir da geração do resumo; incluído no token de sessão e nos totais de custo
  • details: Dados de rastreamento de arquivo ({ readFiles: string[], modifiedFiles: string[] }) para padrão ou dados personalizados para extensões
  • fromHook: true se gerado por uma extensão, false/undefined se gerado por pi (nome do campo legado)

Entrada personalizada

Persistência do estado de extensão. NÃO participa do contexto LLM.

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

Use customType para identificar as entradas da sua extensão ao recarregar. O modo interativo pode renderizar entradas personalizadas via pi.registerEntryRenderer(customType, renderer), mas elas ainda não participam do contexto LLM.

Entrada de mensagem personalizada

Mensagens injetadas por extensão que participam do 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: String ou (TextContent | ImageContent)[] (igual a UserMessage)
  • display: true = mostrar em TUI com estilo distinto, false = oculto
  • details: Metadados opcionais específicos da extensão (não enviados para LLM)

LabelEntry

Marcador/marcador definido pelo usuário em uma entrada.

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

Defina label como undefined para limpar um rótulo.

SessãoInfoEntry

Metadados da sessão (por exemplo, nome de exibição definido pelo usuário). Defina via /name, --name / -n ou pi.setSessionName() nas extensões.

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

O nome da sessão é exibido no seletor de sessão (/resume) em vez da primeira mensagem quando definido.

Estrutura da árvore

As entradas formam uma árvore:

  • A primeira entrada tem parentId: null
  • Cada entrada subsequente aponta para seu pai via parentId
  • A ramificação cria novos filhos a partir de uma entrada anterior
  • A "folha" é a posição atual na árvore
[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf
                                                            │
                                                            └─ [branch_summary] ─── [user msg] ← alternate branch

Construção de Contexto

buildContextEntries() caminha da folha atual até a raiz, produzindo a lista de entradas ativas enquanto respeita a compactação:

  1. Coleta todas as entradas no caminho
  2. Se um CompactionEntry estiver no caminho:
    • Inclui a entrada de compactação primeiro
    • Se retainedTail estiver presente, ele atua como um ponto de verificação independente e as entradas após a compactação são incluídas
    • Caso contrário, as entradas de firstKeptEntryId para a compactação serão incluídas
    • Então as entradas após a compactação são incluídas
  3. Preserva entradas que não são de mensagem no intervalo selecionado para que o modo interativo possa renderizá-las

buildSessionContext() baseia-se nessa lista de entradas para produzir a lista de mensagens para o LLM:

  1. Extrai o modelo atual e as configurações de nível de pensamento do caminho completo
  2. Converte entradas selecionadas em mensagens:
    • message -> armazenado AgentMessage
    • compaction -> compactionSummary mais retainedTail quando presente
    • branch_summary -> branchSummary
    • custom_message -> CustomMessage
    • custom -> nenhuma mensagem de contexto

Isso faz com que as compactações mais recentes atuem como pontos de verificação independentes. retainedTail é opcional apenas para que sessões mais antigas que armazenam apenas firstKeptEntryId continuem a carregar corretamente.

Exemplo de análise

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

Gerenciador de Sessão API

Principais métodos para trabalhar com sessões programaticamente.

Métodos de criação estática

  • SessionManager.create(cwd, sessionDir?) - Nova sessão
  • SessionManager.open(path, sessionDir?) - Abra o arquivo de sessão existente
  • SessionManager.continueRecent(cwd, sessionDir?) - Continue o mais recente ou crie um novo
  • SessionManager.inMemory(cwd?) - Sem persistência de arquivo
  • SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?) - Sessão bifurcada de outro projeto

Métodos de listagem estática

  • SessionManager.list(cwd, sessionDir?, onProgress?) - Lista sessões para um diretório
  • SessionManager.listAll(onProgress?) - Lista todas as sessões em todos os projetos

Métodos de Instância - Gerenciamento de Sessão

  • newSession(options?) - Iniciar uma nova sessão (opções: { parentSession?: string })
  • setSessionFile(path) - Mudar para um arquivo de sessão diferente
  • createBranchedSession(leafId) - Extraia branch para novo arquivo de sessão

Métodos de instância - Anexando (todos os IDs de entrada de retorno)

  • appendMessage(message) - Adicionar mensagem
  • appendThinkingLevelChange(level) - Registrar mudança de pensamento
  • appendModelChange(provider, modelId) - Registrar mudança de modelo
  • appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?) - Adicionar compactação
  • appendCustomEntry(customType, data?) - Estado da extensão (fora do contexto)
  • appendSessionInfo(name) - Definir nome de exibição da sessão
  • appendCustomMessageEntry(customType, content, display, details?) - Mensagem de extensão (no contexto)
  • appendLabelChange(targetId, label) - Definir/limpar rótulo

Métodos de Instância - Navegação em Árvore

  • getLeafId() - Posição atual
  • getLeafEntry() - Obtenha a entrada atual da folha
  • getEntry(id) - Obtenha entrada por ID
  • getBranch(fromId?) - Caminhe da entrada até a raiz
  • getTree() - Obtenha estrutura de árvore completa
  • getChildren(parentId) - Obtenha filhos diretos
  • getLabel(id) - Obtenha etiqueta para entrada
  • branch(entryId) - Mover folha para entrada anterior
  • resetLeaf() - Redefinir folha para nulo (antes de qualquer entrada)
  • branchWithSummary(entryId, summary, details?, fromHook?) - Ramificação com resumo de contexto

Métodos de Instância - Contexto e Informações

  • buildContextEntries() - Obtenha entradas de ramificação ativas com compactação aplicada
  • buildSessionContext() - Obtenha mensagens, nível de pensamento e modelo para LLM
  • getEntries() - Todas as entradas (excluindo cabeçalho)
  • getHeader() - Metadados do cabeçalho da sessão
  • getSessionName() - Obtenha o nome de exibição da última entrada session_info
  • getCwd() - Diretório de trabalho
  • getSessionDir() - Diretório de armazenamento de sessão
  • getSessionId() - UUID da sessão
  • getSessionFile() - Caminho do arquivo da sessão (indefinido para memória)
  • isPersisted() - Se a sessão é salva no disco