コンパクションとブランチ要約
LLM には限られたコンテキストウィンドウがあります。会話が長くなりすぎると、pi は古いコンテンツを要約しつつ最近の作業を保持するコンパクションを使用します。このページでは、自動コンパクションとブランチ要約の両方を説明します。
ソースファイル(pi-mono):
packages/coding-agent/src/core/compaction/compaction.ts- 自動コンパクションのロジックpackages/coding-agent/src/core/compaction/branch-summarization.ts- ブランチ要約packages/coding-agent/src/core/compaction/utils.ts- 共有ユーティリティ(ファイル追跡、シリアライズ)packages/coding-agent/src/core/session-manager.ts- エントリ型(CompactionEntry、BranchSummaryEntry)packages/coding-agent/src/core/extensions/types.ts- 拡張イベント型
プロジェクトで TypeScript 定義を確認するには、node_modules/@earendil-works/pi-coding-agent/dist/ を参照してください。
Pi には 2 つの要約メカニズムがあります:
| メカニズム | トリガー | 目的 |
|---|---|---|
| コンパクション | コンテキストが閾値を超える、または /compact | 古いメッセージを要約してコンテキストを解放 |
| ブランチ要約 | /tree ナビゲーション | ブランチ切り替え時にコンテキストを保持 |
どちらも同じ構造化要約形式を使用し、ファイル操作を累積的に追跡します。
コンパクション
Section titled “コンパクション”トリガー条件
Section titled “トリガー条件”自動コンパクションは次の場合にトリガーされます:
contextTokens > contextWindow - reserveTokensデフォルトでは、reserveTokens は 16384 トークンです(~/.pi/agent/settings.json または <project-dir>/.pi/settings.json で設定可能)。これにより LLM の応答用の余地が確保されます。
/compact [instructions] で手動トリガーも可能です。オプションの instructions で要約の焦点を指定できます。
動作の仕組み
Section titled “動作の仕組み”- 切り取り点を見つける:最新メッセージから逆方向に走査し、
keepRecentTokens(デフォルト 20k、~/.pi/agent/settings.jsonまたは<project-dir>/.pi/settings.jsonで設定可能)に達するまでトークン見積もりを累積 - メッセージを抽出:前回の保持境界(またはセッション開始)から切り取り点までのメッセージを収集
- 要約を生成:構造化形式で LLM を呼び出して要約を生成。存在する場合は前回の要約を反復コンテキストとして渡す
- エントリを追加:要約と
firstKeptEntryIdを含むCompactionEntryを保存 - 再読み込み:セッションを再読み込み。要約と
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繰り返しコンパクションでは、要約対象の範囲はコンパクションエントリ自体ではなく、前回のコンパクションの保持境界(firstKeptEntryId)から始まります。パス内でその保持エントリが見つからない場合は、前回のコンパクションの直後のエントリにフォールバックします。これにより、前回のコンパクションで残ったメッセージも次の要約パスに含まれます。Pi は新しい CompactionEntry を書き込む前に、再構築されたセッションコンテキストから tokensBefore を再計算するため、トークン数は実際に置き換えられるコンパクション前のコンテキストを反映します。
「ターン」はユーザーメッセージで始まり、次のユーザーメッセージまでのすべての assistant 応答と tool 呼び出しを含みます。通常、コンパクションはターン境界で切り取られます。
単一ターンが keepRecentTokens を超えると、切り取り点は assistant メッセージでターン途中に来ます。これを「分割ターン」と呼びます:
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]分割ターンでは、pi は 2 つの要約を生成してマージします:
- 履歴要約:以前のコンテキスト(存在する場合)
- ターン接頭要約:分割ターンの前半部分
切り取り点のルール
Section titled “切り取り点のルール”有効な切り取り点:
- ユーザーメッセージ
- Assistant メッセージ
- BashExecution メッセージ
- カスタムメッセージ(custom_message、branch_summary)
tool 結果では切り取らないでください(tool 呼び出しと一緒に保持する必要があります)。
CompactionEntry 構造
Section titled “CompactionEntry 構造”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[];}拡張は details に任意の JSON シリアライズ可能なデータを保存できます。デフォルトのコンパクションはファイル操作を追跡しますが、カスタム拡張実装は独自の構造を使用できます。
実装は prepareCompaction() と compact() を参照してください。
ブランチ要約
Section titled “ブランチ要約”トリガー条件
Section titled “トリガー条件”/tree で別のブランチに移動すると、pi は離れる作業の要約を提案します。これにより、左側ブランチのコンテキストが新しいブランチに注入されます。
動作の仕組み
Section titled “動作の仕組み”- 共通祖先を見つける:旧位置と新位置で共有される最深ノード
- エントリを収集:旧リーフから共通祖先まで遡る
- 予算内で準備:トークン予算内でメッセージを含める(新しいものを優先)
- 要約を生成:構造化形式で LLM を呼び出す
- エントリを追加:ナビゲーション地点に
BranchSummaryEntryを保存
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)累積ファイル追跡
Section titled “累積ファイル追跡”コンパクションとブランチ要約の両方がファイルを累積的に追跡します。要約生成時、pi は以下からファイル操作を抽出します:
- 要約対象メッセージ内の tool 呼び出し
- 以前のコンパクションまたはブランチ要約の
details(存在する場合)
これにより、ファイル追跡は複数のコンパクションやネストしたブランチ要約にわたって累積し、読み取り・変更ファイルの完全な履歴を保持します。
BranchSummaryEntry 構造
Section titled “BranchSummaryEntry 構造”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[];}コンパクションと同様、拡張は details にカスタムデータを保存できます。
実装は collectEntriesForBranchSummary()、prepareBranchEntries()、generateBranchSummary() を参照してください。
コンパクションとブランチ要約は同じ構造化形式を使用します:
## 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>メッセージのシリアライズ
Section titled “メッセージのシリアライズ”要約の前に、メッセージは 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これにより、モデルが続きの会話として扱うことを防ぎます。
シリアライズ時、tool 結果は 2000 文字に切り詰められます。制限を超えたコンテンツは、切り詰められた文字数を示すマーカーに置き換えられます。tool 結果(特に read と bash からのもの)は通常コンテキストサイズの最大の要因であるため、要約リクエストを合理的なトークン予算内に保ちます。
拡張によるカスタム要約
Section titled “拡張によるカスタム要約”拡張はコンパクションとブランチ要約の両方をインターセプトしてカスタマイズできます。イベント型定義は extensions/types.ts を参照してください。
session_before_compact
Section titled “session_before_compact”自動コンパクションまたは /compact の前に発火。キャンセルまたはカスタム要約を提供可能。型ファイルの SessionBeforeCompactEvent と CompactionPreparation を参照。
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 */ }, } };});メッセージをテキストに変換
Section titled “メッセージをテキストに変換”独自モデルで要約を生成するには、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, } };});別モデルを使用する完全な例は custom-compaction.ts を参照してください。
session_before_tree
Section titled “session_before_tree”/tree ナビゲーションの前に発火。ユーザーが要約を選択したかどうかに関わらず常に発火。ナビゲーションをキャンセルまたはカスタム要約を提供可能。
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 */ }, } }; }});型ファイルの SessionBeforeTreeEvent と TreePreparation を参照してください。
~/.pi/agent/settings.json または <project-dir>/.pi/settings.json でコンパクションを設定:
{ "compaction": { "enabled": true, "reserveTokens": 16384, "keepRecentTokens": 20000 }}| 設定 | デフォルト | 説明 |
|---|---|---|
enabled | true | 自動コンパクションを有効化 |
reserveTokens | 16384 | LLM 応答用に予約するトークン |
keepRecentTokens | 20000 | 保持する最近のトークン(要約しない) |
"enabled": false で自動コンパクションを無効化できます。/compact による手動コンパクションは引き続き可能です。