壓縮和分支匯總
法學碩士的背景窗口有限。當對話變得太長時,Pi 使用壓縮來總結舊內容,同時保留最近的工作。本頁涵蓋了自動壓縮和branch summarization。
來源檔案 (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有兩種總結機制:
| 機制 | 扳機 | 目的 |
|---|---|---|
| 壓實 | 上下文超過閾值,或/compact |
總結舊消息以釋放上下文 |
| 分支總結 | /tree導航 |
切換分支時保留上下文 |
兩者都使用相同的結構化摘要格式並累積追蹤文件操作。壓縮和分支摘要請求使用新的路由會話 ID,並且在提供者支援的情況下停用提示快取寫入,因為這些一次性提示不太可能被重複使用。
壓實
當它觸發時
自動壓縮在以下情況觸發:
contextTokens > contextWindow - reserveTokens預設情況下,reserveTokens為16384個令牌(可在~/.pi/agent/settings.json或<project-dir>/.pi/settings.json中配置)。這為LLM的回應留下了空間。
您也可以使用 /compact [instructions] 手動觸發,其中可選指令重點關注摘要。
它是如何運作的
- 尋找切點:從最新訊息向後走,累積令牌估計,直到達到
keepRecentTokens(預設20k,可在~/.pi/agent/settings.json或<project-dir>/.pi/settings.json中配置) - 提取訊息:收集從先前保留的邊界(或會話開始)到切入點的訊息
- 產生摘要:呼叫LLM以結構化格式進行摘要,將先前的摘要作為迭代上下文(如果存在)傳遞
- 追加條目:儲存
CompactionEntry和摘要以及firstKeptEntryId - 重新加載:會話重新加載,使用從
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)開始,而不是從壓縮條目本身開始,如果在路徑中找不到該保留條目,則返回到先前壓縮後的條目。這透過將早期壓縮中倖存下來的訊息也包含在下一個摘要過程中來保留它們。在寫入新的 CompactionEntry 之前,Pi 也會根據重建的會話上下文重新計算 tokensBefore,因此令牌計數反映了被取代的實際預壓縮上下文。
分叉轉彎
「輪次」以用戶訊息開始,包括所有助手響應和工具調用,直到下一條用戶訊息。通常,壓實會在轉彎邊界處進行切割。
當單圈超過 keepRecentTokens 時,切入點會在轉彎中途出現輔助訊息。這是一個「分裂回合」:
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 產生兩個摘要並將它們合併:
- 歷史摘要:以前的背景(如果有)
- 回合前綴總結:分割回合的早期部分
切點規則
有效的切點是:
- 用戶留言
- 助理訊息
- Bash執行訊息
- 自訂訊息(custom_message、branch_summary)
切勿削減工具結果(它們必須保留工具呼叫)。
壓實入口結構
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可以在details中儲存任何JSON可序列化的資料。預設壓縮追蹤檔案操作,但自訂擴充實作可以使用自己的結構。產生的和擴充提供的摘要會儲存其 LLM usage(如果可用),因此會話總數包括摘要工作。
具體實現參見prepareCompaction()和compact()。對於直接編程摘要,generateSummary() 返回摘要文本,generateSummaryWithUsage() 返回{ text, usage }。
分支總結
當它觸發時
當您使用 /tree 導覽到不同的分支時,Pi 會總結您要離開的工作。這會將左分支的上下文注入到新分支中。
它是如何運作的
- 找到共同祖先:新舊位置共享的最深節點
- 收集條目:從老葉子回到共同的祖先
- 準備預算:包括不超過代幣預算的消息(最新的優先)
- 產生摘要:以結構化格式呼叫LLM
- 追加條目:在導航點保存
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
A ───┤
└─ E ─ F ─ [summary of B,C,D] (new leaf)累積文件追蹤
壓縮和branch summarization都累積追蹤檔。產生摘要時,pi 從以下位置提取文件操作:
- 正在匯總的消息中的工具調用
- 先前的壓縮或分支摘要
details(如果有)
這意味著檔案追蹤會在多個壓縮或嵌套分支摘要中累積,從而保留讀取和修改檔案的完整歷史記錄。
BranchSummaryEntry 結構
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[];
}與壓縮相同,擴充功能可以將自訂資料儲存在details中。
具體實現請參見collectEntriesForBranchSummary()、prepareBranchEntries()和generateBranchSummary()。
摘要格式
壓縮和branch summarization都使用相同的結構化格式:
## 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這會阻止模型將其視為繼續對話。
工具結果在序列化期間被截斷為 2000 個字元。超出該限制的內容將替換為指示被截斷字元數的標記。這使匯總請求保持在合理的令牌預算內,因為工具結果(尤其是來自read和bash)通常是上下文大小的最大貢獻者。
透過 Extensions 自訂摘要
Extensions可以攔截並自訂compaction和branch summarization。有關事件類型定義,請參閱extensions/types.ts。
壓縮前的會話
在自動壓縮或 /compact 之前觸發。可以取消或提供自訂摘要。請參閱類型文件中的 SessionBeforeCompactEvent 和 CompactionPreparation。
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 */ },
}
};
});將訊息轉換為文字
若要使用您自己的模型產生摘要,請使用 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,
}
};
});有關使用不同模型的完整範例,請參閱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...",
// usage: summaryResponse.usage, // Optional; included in session totals
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 手動壓縮。