コンテンツにスキップ

コンパクションとブランチ要約

LLM には限られたコンテキストウィンドウがあります。会話が長くなりすぎると、pi は古いコンテンツを要約しつつ最近の作業を保持するコンパクションを使用します。このページでは、自動コンパクションとブランチ要約の両方を説明します。

ソースファイルpi-mono):

プロジェクトで TypeScript 定義を確認するには、node_modules/@earendil-works/pi-coding-agent/dist/ を参照してください。

Pi には 2 つの要約メカニズムがあります:

メカニズムトリガー目的
コンパクションコンテキストが閾値を超える、または /compact古いメッセージを要約してコンテキストを解放
ブランチ要約/tree ナビゲーションブランチ切り替え時にコンテキストを保持

どちらも同じ構造化要約形式を使用し、ファイル操作を累積的に追跡します。

自動コンパクションは次の場合にトリガーされます:

contextTokens > contextWindow - reserveTokens

デフォルトでは、reserveTokens は 16384 トークンです(~/.pi/agent/settings.json または <project-dir>/.pi/settings.json で設定可能)。これにより LLM の応答用の余地が確保されます。

/compact [instructions] で手動トリガーも可能です。オプションの instructions で要約の焦点を指定できます。

  1. 切り取り点を見つける:最新メッセージから逆方向に走査し、keepRecentTokens(デフォルト 20k、~/.pi/agent/settings.json または <project-dir>/.pi/settings.json で設定可能)に達するまでトークン見積もりを累積
  2. メッセージを抽出:前回の保持境界(またはセッション開始)から切り取り点までのメッセージを収集
  3. 要約を生成:構造化形式で LLM を呼び出して要約を生成。存在する場合は前回の要約を反復コンテキストとして渡す
  4. エントリを追加:要約と firstKeptEntryId を含む CompactionEntry を保存
  5. 再読み込み:セッションを再読み込み。要約と 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 つの要約を生成してマージします:

  1. 履歴要約:以前のコンテキスト(存在する場合)
  2. ターン接頭要約:分割ターンの前半部分

有効な切り取り点:

  • ユーザーメッセージ
  • Assistant メッセージ
  • BashExecution メッセージ
  • カスタムメッセージ(custom_message、branch_summary)

tool 結果では切り取らないでください(tool 呼び出しと一緒に保持する必要があります)。

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() を参照してください。

/tree で別のブランチに移動すると、pi は離れる作業の要約を提案します。これにより、左側ブランチのコンテキストが新しいブランチに注入されます。

  1. 共通祖先を見つける:旧位置と新位置で共有される最深ノード
  2. エントリを収集:旧リーフから共通祖先まで遡る
  3. 予算内で準備:トークン予算内でメッセージを含める(新しいものを優先)
  4. 要約を生成:構造化形式で LLM を呼び出す
  5. エントリを追加:ナビゲーション地点に BranchSummaryEntry を保存
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)

コンパクションとブランチ要約の両方がファイルを累積的に追跡します。要約生成時、pi は以下からファイル操作を抽出します:

  • 要約対象メッセージ内の tool 呼び出し
  • 以前のコンパクションまたはブランチ要約の details(存在する場合)

これにより、ファイル追跡は複数のコンパクションやネストしたブランチ要約にわたって累積し、読み取り・変更ファイルの完全な履歴を保持します。

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

要約の前に、メッセージは 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 結果(特に readbash からのもの)は通常コンテキストサイズの最大の要因であるため、要約リクエストを合理的なトークン予算内に保ちます。

拡張はコンパクションとブランチ要約の両方をインターセプトしてカスタマイズできます。イベント型定義は extensions/types.ts を参照してください。

自動コンパクションまたは /compact の前に発火。キャンセルまたはカスタム要約を提供可能。型ファイルの SessionBeforeCompactEventCompactionPreparation を参照。

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 */ },
}
};
});

独自モデルで要約を生成するには、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 を参照してください。

/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 */ },
}
};
}
});

型ファイルの SessionBeforeTreeEventTreePreparation を参照してください。

~/.pi/agent/settings.json または <project-dir>/.pi/settings.json でコンパクションを設定:

{
"compaction": {
"enabled": true,
"reserveTokens": 16384,
"keepRecentTokens": 20000
}
}
設定デフォルト説明
enabledtrue自動コンパクションを有効化
reserveTokens16384LLM 応答用に予約するトークン
keepRecentTokens20000保持する最近のトークン(要約しない)

"enabled": false で自動コンパクションを無効化できます。/compact による手動コンパクションは引き続き可能です。