Compactage et résumé des branches
Les LLM ont des fenêtres contextuelles limitées. Lorsque les conversations deviennent trop longues, Pi utilise le compactage pour résumer le contenu plus ancien tout en préservant le travail récent. Cette page couvre à la fois l'auto-compaction et branch summarization.
Fichiers sources (pi-mono):
packages/coding-agent/src/core/compaction/compaction.ts- Logique d'auto-compactionpackages/coding-agent/src/core/compaction/branch-summarization.ts- Résumé des branchespackages/coding-agent/src/core/compaction/utils.ts- Utilitaires partagés (suivi des fichiers, sérialisation)packages/coding-agent/src/core/session-manager.ts- Types d'entrées (CompactionEntry,BranchSummaryEntry)packages/coding-agent/src/core/extensions/types.ts- Types d'événements d'extension
Pour les définitions TypeScript de votre projet, inspectez node_modules/@earendil-works/pi-coding-agent/dist/.
Aperçu
Pi a deux mécanismes de résumé:
| Mécanisme | Déclenchement | But |
|---|---|---|
| Compactage | Le contexte dépasse le seuil, ou /compact |
Résumer les anciens messages pour libérer du contexte |
| Résumé de la branche | /treenavigation |
Préserver le contexte lors du changement de branche |
Les deux utilisent le même format de résumé structuré et suivent les opérations sur les fichiers de manière cumulative. Les demandes de compactage et de résumé de branche utilisent de nouveaux ID de session de routage et, lorsque cela est pris en charge par le fournisseur, désactivent les écritures dans le cache d'invite, car il est peu probable que ces invites ponctuelles soient réutilisées.
Compactage
Quand ça se déclenche
Le compactage automatique se déclenche lorsque:
contextTokens > contextWindow - reserveTokensPar défaut, reserveTokens correspond à 16384 jetons (configurable en ~/.pi/agent/settings.json ou <project-dir>/.pi/settings.json). Cela laisse place à la réponse du LLM.
Vous pouvez également déclencher manuellement avec /compact [instructions], où des instructions facultatives concentrent le résumé.
Comment ça marche
- Trouver le point de coupure: reculez à partir du message le plus récent, en accumulant les estimations de jetons jusqu'à ce que
keepRecentTokens(20 000 par défaut, configurable en~/.pi/agent/settings.jsonou<project-dir>/.pi/settings.json) soit atteint - Extraire les messages: collectez les messages de la limite conservée précédente (ou du début de session) jusqu'au point de coupure
- Générer un résumé: appelez LLM pour résumer avec un format structuré, en transmettant le résumé précédent comme contexte itératif lorsqu'il est présent
- Ajouter une entrée: Enregistrez
CompactionEntryavec le résumé etfirstKeptEntryId - Recharger: rechargements de session, en utilisant le résumé + les messages à partir de
firstKeptEntryId
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 firstKeptEntryIdLors de compactages répétés, la durée résumée commence à la limite conservée du compactage précédent (firstKeptEntryId), et non à l'entrée de compactage elle-même, retombant à l'entrée après le compactage précédent si cette entrée conservée est introuvable dans le chemin. Cela préserve les messages qui ont survécu au compactage précédent en les incluant également dans la prochaine passe de résumé. Pi recalcule également tokensBefore à partir du contexte de session reconstruit avant d'écrire le nouveau CompactionEntry, de sorte que le nombre de jetons reflète le contexte de pré-compactage réel remplacé.
Tours fractionnés
Un « tour » commence par un message utilisateur et inclut toutes les réponses de l'assistant et les appels d'outils jusqu'au prochain message utilisateur. Normalement, le compactage coupe aux limites des virages.
Lorsqu'un seul tour dépasse keepRecentTokens, le point de coupure atterrit à mi-tour sur un message de l'assistant. Il s'agit d'un "tour partagé":
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]Pour les tours fractionnés, Pi génère deux résumés et les fusionne:
- Résumé de l'historique: contexte précédent (le cas échéant)
- Résumé du préfixe de tour: début du tour divisé
Règles de point de coupure
Les points de coupure valides sont:
- Messages utilisateur
- Messages de l'assistant
- Messages d'exécution Bash
- Messages personnalisés (custom_message, branch_summary)
Ne coupez jamais aux résultats de l'outil (ils doivent rester avec leur appel d'outil).
Structure d'entrée de compactage
Défini en session-manager.ts:
interface CompactionEntry<T = unknown> {
type: "compaction";
id: string;
parentId: string;
timestamp: number;
summary: string;
firstKeptEntryId: string;
tokensBefore: number;
usage?: Usage; // LLM usage that generated the summary
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[];
}Extensions peut stocker toutes les données sérialisables JSON dans details. Le compactage par défaut suit les opérations sur les fichiers, mais les implémentations d'extensions personnalisées peuvent utiliser leur propre structure. Les résumés générés et fournis par l'extension stockent leur LLM usage lorsqu'il est disponible afin que les totaux des sessions incluent le travail de synthèse.
Voir prepareCompaction() et compact() pour l'implémentation. Pour un résumé programmatique direct, generateSummary() renvoie le texte du résumé et generateSummaryWithUsage() renvoie { text, usage }.
Résumé de branche
Quand ça se déclenche
Lorsque vous utilisez /tree pour accéder à une autre branche, Pi propose de résumer le travail que vous quittez. Cela injecte le contexte de la branche gauche dans la nouvelle branche.
Comment ça marche
- Trouver l'ancêtre commun: nœud le plus profond partagé par les anciennes et les nouvelles positions
- Collecter les entrées: Revenir de l'ancienne feuille à l'ancêtre commun
- Préparer avec le budget: Incluez des messages jusqu'au budget symbolique (le plus récent en premier)
- Générer un résumé: Appelez LLM avec un format structuré
- Ajouter une entrée: Enregistrez
BranchSummaryEntryau point de navigation
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
A ───┤
└─ E ─ F ─ [summary of B,C,D] (new leaf)Suivi cumulatif des fichiers
Le compactage et branch summarization suivent les fichiers de manière cumulative. Lors de la génération d'un résumé, pi extrait les opérations sur les fichiers de:
- Appels d'outils dans les messages en cours de synthèse
- Résumé du compactage ou de la branche précédente
details(le cas échéant)
Cela signifie que le suivi des fichiers s'accumule sur plusieurs compactages ou résumés de branches imbriqués, préservant ainsi l'historique complet des fichiers lus et modifiés.
Structure d'entrée BranchSummary
Défini 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
usage?: Usage; // LLM usage that generated the summary
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[];
}Tout comme pour le compactage, les extensions peuvent stocker des données personnalisées dans details.
Voir collectEntriesForBranchSummary(), prepareBranchEntries() et generateBranchSummary() pour l'implémentation.
Format du résumé
Le compactage et branch summarization utilisent le même format structuré:
## 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>Sérialisation des messages
Avant le résumé, les messages sont sérialisés en texte via 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 toolCela empêche le modèle de la traiter comme une conversation à poursuivre.
Les résultats de l'outil sont tronqués à 2 000 caractères lors de la sérialisation. Le contenu au-delà de cette limite est remplacé par un marqueur indiquant le nombre de caractères tronqués. Cela maintient les demandes de résumé dans des budgets symboliques raisonnables, puisque les résultats des outils (en particulier ceux de read et bash) sont généralement ceux qui contribuent le plus à la taille du contexte.
Résumé personnalisé via Extensions
Extensions peut intercepter et personnaliser à la fois le compactage et branch summarization. Voir extensions/types.ts pour les définitions des types d'événements.
session_avant_compact
Lancé avant le compactage automatique ou /compact. Peut annuler ou fournir un résumé personnalisé. Voir SessionBeforeCompactEvent et CompactionPreparation dans le fichier de types.
pi.on("session_before_compact", async (event, ctx) => {
const { preparation, branchEntries, customInstructions, reason, willRetry, 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)
// reason - "manual" (/compact), "threshold", or "overflow"
// willRetry - whether the aborted turn is retried after compaction (overflow recovery)
// signal - AbortSignal (pass to LLM calls)
// Cancel:
return { cancel: true };
// Custom summary:
return {
compaction: {
summary: "Your summary...",
firstKeptEntryId: preparation.firstKeptEntryId,
tokensBefore: preparation.tokensBefore,
// usage: summaryResponse.usage, // Optional; included in session totals
details: { /* custom data */ },
}
};
});Conversion de messages en texte
Pour générer un résumé avec votre propre modèle, convertissez les messages en texte en utilisant 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, usage } = await myModel.summarize(conversationText);
return {
compaction: {
summary,
firstKeptEntryId: preparation.firstKeptEntryId,
tokensBefore: preparation.tokensBefore,
usage,
}
};
});Voir custom-compaction.ts pour un exemple complet utilisant un modèle différent.
session_avant_arbre
Déclenché avant la navigation /tree. Se déclenche toujours, que l'utilisateur ait choisi ou non de résumer. Peut annuler la navigation ou fournir un résumé personnalisé.
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...",
// usage: summaryResponse.usage, // Optional; included in session totals
details: { /* custom data */ },
}
};
}
});Voir SessionBeforeTreeEvent et TreePreparation dans le fichier de types.
Paramètres
Configurez le compactage en ~/.pi/agent/settings.json ou <project-dir>/.pi/settings.json:
{
"compaction": {
"enabled": true,
"reserveTokens": 16384,
"keepRecentTokens": 20000
}
}| Paramètre | Défaut | Description |
|---|---|---|
enabled |
true |
Activer le compactage automatique |
reserveTokens |
16384 |
Jetons à réserver pour la réponse LLM |
keepRecentTokens |
20000 |
Jetons récents à conserver (non résumés) |
Désactivez le compactage automatique avec "enabled": false. Vous pouvez toujours compacter manuellement avec /compact.