Configuration, personnalisation, paramètres de plateforme et références API pour Pi.

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):

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

Par 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

  1. 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.json ou <project-dir>/.pi/settings.json) soit atteint
  2. 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
  3. 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
  4. Ajouter une entrée: Enregistrez CompactionEntry avec le résumé et firstKeptEntryId
  5. 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 firstKeptEntryId

Lors 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:

  1. Résumé de l'historique: contexte précédent (le cas échéant)
  2. 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

  1. Trouver l'ancêtre commun: nœud le plus profond partagé par les anciennes et les nouvelles positions
  2. Collecter les entrées: Revenir de l'ancienne feuille à l'ancêtre commun
  3. Préparer avec le budget: Incluez des messages jusqu'au budget symbolique (le plus récent en premier)
  4. Générer un résumé: Appelez LLM avec un format structuré
  5. Ajouter une entrée: Enregistrez BranchSummaryEntry au 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 tool

Cela 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.