Saltearse al contenido

Compactación y resumen de ramas

Los LLM tienen ventanas de contexto limitadas. Cuando las conversaciones se alargan demasiado, pi usa la compactación para resumir contenido antiguo preservando el trabajo reciente. Esta página cubre tanto la compactación automática como el resumen de ramas.

Archivos fuente (pi-mono):

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

Pi tiene dos mecanismos de resumen:

MecanismoDisparadorPropósito
CompactaciónEl contexto supera el umbral, o /compactResumir mensajes antiguos para liberar contexto
Resumen de ramasNavegación con /treePreservar contexto al cambiar de rama

Ambos usan el mismo formato de resumen estructurado y rastrean operaciones de archivos de forma acumulativa.

La compactación automática se activa cuando:

contextTokens > contextWindow - reserveTokens

Por defecto, reserveTokens es 16384 tokens (configurable en ~/.pi/agent/settings.json o <project-dir>/.pi/settings.json). Esto deja espacio para la respuesta del LLM.

También puedes activarla manualmente con /compact [instructions], donde las instrucciones opcionales enfocan el resumen.

  1. Encontrar punto de corte: Recorrer hacia atrás desde el mensaje más reciente, acumulando estimaciones de tokens hasta alcanzar keepRecentTokens (20k por defecto, configurable en ~/.pi/agent/settings.json o <project-dir>/.pi/settings.json)
  2. Extraer mensajes: Recopilar mensajes desde el límite de retención anterior (o inicio de sesión) hasta el punto de corte
  3. Generar resumen: Llamar al LLM para resumir con formato estructurado, pasando el resumen anterior como contexto iterativo cuando exista
  4. Añadir entrada: Guardar CompactionEntry con resumen y firstKeptEntryId
  5. Recargar: La sesión se recarga, usando el resumen y los mensajes desde firstKeptEntryId en adelante
Before compaction:
entry: 0 1 2 3 4 5 6 7 8 9
┌─────┬─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┐
│ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│
└─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┘
└────────┬───────┘ └──────────────┬──────────────┘
messagesToSummarize kept messages
firstKeptEntryId (entry 4)
After compaction (new entry appended):
entry: 0 1 2 3 4 5 6 7 8 9 10
┌─────┬─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬─────┐
│ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│ cmp │
└─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┴─────┘
└──────────┬──────┘ └──────────────────────┬───────────────────┘
not sent to LLM sent to LLM
starts from firstKeptEntryId
What the LLM sees:
┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐
│ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │
└────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘
↑ ↑ └─────────────────┬────────────────┘
prompt from cmp messages from firstKeptEntryId

En compactaciones repetidas, el tramo resumido comienza en el límite de retención de la compactación anterior (firstKeptEntryId), no en la entrada de compactación en sí, volviendo a la entrada posterior a la compactación anterior si esa entrada retenida no se encuentra en la ruta. Esto preserva mensajes que sobrevivieron la compactación anterior incluyéndolos también en el siguiente pase de resumen. Pi también recalcula tokensBefore desde el contexto de sesión reconstruido antes de escribir la nueva CompactionEntry, de modo que el conteo de tokens refleja el contexto pre-compactación real que se reemplaza.

Un “turno” comienza con un mensaje de usuario e incluye todas las respuestas del assistant y llamadas a herramientas hasta el siguiente mensaje de usuario. Normalmente, la compactación corta en los límites de turno.

Cuando un solo turno supera keepRecentTokens, el punto de corte cae a mitad de turno en un mensaje del assistant. Esto es un “turno dividido”:

Split turn (one huge turn exceeds budget):
entry: 0 1 2 3 4 5 6 7 8
┌─────┬─────┬─────┬──────┬─────┬──────┬──────┬─────┬──────┐
│ hdr │ usr │ ass │ tool │ ass │ tool │ tool │ ass │ tool │
└─────┴─────┴─────┴──────┴─────┴──────┴──────┴─────┴──────┘
↑ ↑
turnStartIndex = 1 firstKeptEntryId = 7
│ │
└──── turnPrefixMessages (1-6) ───────┘
└── kept (7-8)
isSplitTurn = true
messagesToSummarize = [] (no complete turns before)
turnPrefixMessages = [usr, ass, tool, ass, tool, tool]

Para turnos divididos, pi genera dos resúmenes y los fusiona:

  1. Resumen de historial: Contexto anterior (si existe)
  2. Resumen de prefijo de turno: La parte inicial del turno dividido

Los puntos de corte válidos son:

  • Mensajes de usuario
  • Mensajes del assistant
  • Mensajes BashExecution
  • Mensajes personalizados (custom_message, branch_summary)

Nunca cortar en resultados de herramientas (deben permanecer con su llamada a herramienta).

Definida en session-manager.ts:

interface CompactionEntry<T = unknown> {
type: "compaction";
id: string;
parentId: string;
timestamp: number;
summary: string;
firstKeptEntryId: string;
tokensBefore: number;
fromHook?: boolean; // true if provided by extension (legacy field name)
details?: T; // implementation-specific data
}
// Default compaction uses this for details (from compaction.ts):
interface CompactionDetails {
readFiles: string[];
modifiedFiles: string[];
}

Las extensiones pueden almacenar cualquier dato serializable en JSON en details. La compactación predeterminada rastrea operaciones de archivos, pero las implementaciones de extensión personalizadas pueden usar su propia estructura.

Consulta prepareCompaction() y compact() para la implementación.

Cuando usas /tree para navegar a una rama diferente, pi ofrece resumir el trabajo que estás dejando. Esto inyecta contexto de la rama izquierda en la nueva rama.

  1. Encontrar ancestro común: Nodo más profundo compartido por posiciones antigua y nueva
  2. Recopilar entradas: Recorrer desde la hoja antigua hasta el ancestro común
  3. Preparar con presupuesto: Incluir mensajes hasta el presupuesto de tokens (más recientes primero)
  4. Generar resumen: Llamar al LLM con formato estructurado
  5. Añadir entrada: Guardar BranchSummaryEntry en el punto de navegación
Tree before navigation:
┌─ B ─ C ─ D (old leaf, being abandoned)
A ───┤
└─ E ─ F (target)
Common ancestor: A
Entries to summarize: B, C, D
After navigation with summary:
┌─ B ─ C ─ D ─ [summary of B,C,D]
A ───┤
└─ E ─ F (new leaf)

Tanto la compactación como el resumen de ramas rastrean archivos de forma acumulativa. Al generar un resumen, pi extrae operaciones de archivos de:

  • Llamadas a herramientas en los mensajes que se resumen
  • details de compactación o resumen de rama anterior (si existe)

Esto significa que el seguimiento de archivos se acumula a través de múltiples compactaciones o resúmenes de rama anidados, preservando el historial completo de archivos leídos y modificados.

Definida en session-manager.ts:

interface BranchSummaryEntry<T = unknown> {
type: "branch_summary";
id: string;
parentId: string;
timestamp: number;
summary: string;
fromId: string; // Entry we navigated from
fromHook?: boolean; // true if provided by extension (legacy field name)
details?: T; // implementation-specific data
}
// Default branch summarization uses this for details (from branch-summarization.ts):
interface BranchSummaryDetails {
readFiles: string[];
modifiedFiles: string[];
}

Al igual que la compactación, las extensiones pueden almacenar datos personalizados en details.

Consulta collectEntriesForBranchSummary(), prepareBranchEntries() y generateBranchSummary() para la implementación.

Tanto la compactación como el resumen de ramas usan el mismo formato estructurado:

## Goal
[What the user is trying to accomplish]
## Constraints & Preferences
- [Requirements mentioned by user]
## Progress
### Done
- [x] [Completed tasks]
### In Progress
- [ ] [Current work]
### Blocked
- [Issues, if any]
## Key Decisions
- **[Decision]**: [Rationale]
## Next Steps
1. [What should happen next]
## Critical Context
- [Data needed to continue]
<read-files>
path/to/file1.ts
path/to/file2.ts
</read-files>
<modified-files>
path/to/changed.ts
</modified-files>

Antes del resumen, los mensajes se serializan a texto mediante serializeConversation():

[User]: What they said
[Assistant thinking]: Internal reasoning
[Assistant]: Response text
[Assistant tool calls]: read(path="foo.ts"); edit(path="bar.ts", ...)
[Tool result]: Output from tool

Esto evita que el modelo lo trate como una conversación a continuar.

Los resultados de herramientas se truncan a 2000 caracteres durante la serialización. El contenido más allá de ese límite se reemplaza con un marcador que indica cuántos caracteres se truncaron. Esto mantiene las solicitudes de resumen dentro de presupuestos de tokens razonables, ya que los resultados de herramientas (especialmente de read y bash) suelen ser los mayores contribuyentes al tamaño del contexto.

Las extensiones pueden interceptar y personalizar tanto la compactación como el resumen de ramas. Consulta extensions/types.ts para definiciones de tipos de eventos.

Se dispara antes de la compactación automática o /compact. Puede cancelar o proporcionar un resumen personalizado. Consulta SessionBeforeCompactEvent y CompactionPreparation en el archivo de tipos.

pi.on("session_before_compact", async (event, ctx) => {
const { preparation, branchEntries, customInstructions, signal } = event;
// preparation.messagesToSummarize - messages to summarize
// preparation.turnPrefixMessages - split turn prefix (if isSplitTurn)
// preparation.previousSummary - previous compaction summary
// preparation.fileOps - extracted file operations
// preparation.tokensBefore - context tokens before compaction
// preparation.firstKeptEntryId - where kept messages start
// preparation.settings - compaction settings
// branchEntries - all entries on current branch (for custom state)
// signal - AbortSignal (pass to LLM calls)
// Cancel:
return { cancel: true };
// Custom summary:
return {
compaction: {
summary: "Your summary...",
firstKeptEntryId: preparation.firstKeptEntryId,
tokensBefore: preparation.tokensBefore,
details: { /* custom data */ },
}
};
});

Para generar un resumen con tu propio modelo, convierte mensajes a texto usando serializeConversation:

import { convertToLlm, serializeConversation } from "@earendil-works/pi-coding-agent";
pi.on("session_before_compact", async (event, ctx) => {
const { preparation } = event;
// Convert AgentMessage[] to Message[], then serialize to text
const conversationText = serializeConversation(
convertToLlm(preparation.messagesToSummarize)
);
// Returns:
// [User]: message text
// [Assistant thinking]: thinking content
// [Assistant]: response text
// [Assistant tool calls]: read(path="..."); bash(command="...")
// [Tool result]: output text
// Now send to your model for summarization
const summary = await myModel.summarize(conversationText);
return {
compaction: {
summary,
firstKeptEntryId: preparation.firstKeptEntryId,
tokensBefore: preparation.tokensBefore,
}
};
});

Consulta custom-compaction.ts para un ejemplo completo usando un modelo diferente.

Se dispara antes de la navegación con /tree. Siempre se dispara independientemente de si el usuario eligió resumir. Puede cancelar la navegación o proporcionar un resumen personalizado.

pi.on("session_before_tree", async (event, ctx) => {
const { preparation, signal } = event;
// preparation.targetId - where we're navigating to
// preparation.oldLeafId - current position (being abandoned)
// preparation.commonAncestorId - shared ancestor
// preparation.entriesToSummarize - entries that would be summarized
// preparation.userWantsSummary - whether user chose to summarize
// Cancel navigation entirely:
return { cancel: true };
// Provide custom summary (only used if userWantsSummary is true):
if (preparation.userWantsSummary) {
return {
summary: {
summary: "Your summary...",
details: { /* custom data */ },
}
};
}
});

Consulta SessionBeforeTreeEvent y TreePreparation en el archivo de tipos.

Configura la compactación en ~/.pi/agent/settings.json o <project-dir>/.pi/settings.json:

{
"compaction": {
"enabled": true,
"reserveTokens": 16384,
"keepRecentTokens": 20000
}
}
ConfiguraciónPredeterminadoDescripción
enabledtrueHabilitar compactación automática
reserveTokens16384Tokens reservados para respuesta del LLM
keepRecentTokens20000Tokens recientes a conservar (no resumir)

Desactiva la compactación automática con "enabled": false. Aún puedes compactar manualmente con /compact.