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):
packages/coding-agent/src/core/compaction/compaction.ts- Lógica de compactación automáticapackages/coding-agent/src/core/compaction/branch-summarization.ts- Resumen de ramaspackages/coding-agent/src/core/compaction/utils.ts- Utilidades compartidas (seguimiento de archivos, serialización)packages/coding-agent/src/core/session-manager.ts- Tipos de entrada (CompactionEntry,BranchSummaryEntry)packages/coding-agent/src/core/extensions/types.ts- Tipos de eventos de extensión
Para definiciones TypeScript en tu proyecto, consulta node_modules/@earendil-works/pi-coding-agent/dist/.
Resumen
Sección titulada «Resumen»Pi tiene dos mecanismos de resumen:
| Mecanismo | Disparador | Propósito |
|---|---|---|
| Compactación | El contexto supera el umbral, o /compact | Resumir mensajes antiguos para liberar contexto |
| Resumen de ramas | Navegación con /tree | Preservar contexto al cambiar de rama |
Ambos usan el mismo formato de resumen estructurado y rastrean operaciones de archivos de forma acumulativa.
Compactación
Sección titulada «Compactación»Cuándo se activa
Sección titulada «Cuándo se activa»La compactación automática se activa cuando:
contextTokens > contextWindow - reserveTokensPor 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.
Cómo funciona
Sección titulada «Cómo funciona»- 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.jsono<project-dir>/.pi/settings.json) - Extraer mensajes: Recopilar mensajes desde el límite de retención anterior (o inicio de sesión) hasta el punto de corte
- Generar resumen: Llamar al LLM para resumir con formato estructurado, pasando el resumen anterior como contexto iterativo cuando exista
- Añadir entrada: Guardar
CompactionEntrycon resumen yfirstKeptEntryId - Recargar: La sesión se recarga, usando el resumen y los mensajes desde
firstKeptEntryIden 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 firstKeptEntryIdEn 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.
Turnos divididos
Sección titulada «Turnos divididos»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:
- Resumen de historial: Contexto anterior (si existe)
- Resumen de prefijo de turno: La parte inicial del turno dividido
Reglas del punto de corte
Sección titulada «Reglas del punto de corte»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).
Estructura CompactionEntry
Sección titulada «Estructura CompactionEntry»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.
Resumen de ramas
Sección titulada «Resumen de ramas»Cuándo se activa
Sección titulada «Cuándo se activa»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.
Cómo funciona
Sección titulada «Cómo funciona»- Encontrar ancestro común: Nodo más profundo compartido por posiciones antigua y nueva
- Recopilar entradas: Recorrer desde la hoja antigua hasta el ancestro común
- Preparar con presupuesto: Incluir mensajes hasta el presupuesto de tokens (más recientes primero)
- Generar resumen: Llamar al LLM con formato estructurado
- Añadir entrada: Guardar
BranchSummaryEntryen el punto de navegación
Tree before navigation:
┌─ B ─ C ─ D (old leaf, being abandoned) A ───┤ └─ E ─ F (target)
Common ancestor: AEntries to summarize: B, C, D
After navigation with summary:
┌─ B ─ C ─ D ─ [summary of B,C,D] A ───┤ └─ E ─ F (new leaf)Seguimiento acumulativo de archivos
Sección titulada «Seguimiento acumulativo de archivos»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
detailsde 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.
Estructura BranchSummaryEntry
Sección titulada «Estructura BranchSummaryEntry»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.
Formato de resumen
Sección titulada «Formato de resumen»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 Steps1. [What should happen next]
## Critical Context- [Data needed to continue]
<read-files>path/to/file1.tspath/to/file2.ts</read-files>
<modified-files>path/to/changed.ts</modified-files>Serialización de mensajes
Sección titulada «Serialización de mensajes»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 toolEsto 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.
Resumen personalizado mediante extensiones
Sección titulada «Resumen personalizado mediante extensiones»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.
session_before_compact
Sección titulada «session_before_compact»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 */ }, } };});Convertir mensajes a texto
Sección titulada «Convertir mensajes a texto»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.
session_before_tree
Sección titulada «session_before_tree»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.
Configuración
Sección titulada «Configuración»Configura la compactación en ~/.pi/agent/settings.json o <project-dir>/.pi/settings.json:
{ "compaction": { "enabled": true, "reserveTokens": 16384, "keepRecentTokens": 20000 }}| Configuración | Predeterminado | Descripción |
|---|---|---|
enabled | true | Habilitar compactación automática |
reserveTokens | 16384 | Tokens reservados para respuesta del LLM |
keepRecentTokens | 20000 | Tokens recientes a conservar (no resumir) |
Desactiva la compactación automática con "enabled": false. Aún puedes compactar manualmente con /compact.