正文
源码级对比 PiCodexClaude Code OpenCodeHermesOpenClaw
上下文不是记忆:
六大 Agent 压缩发动机
从一次模型请求到底装了什么开始,逐层拆开触发器、Tool Output 裁剪、滚动摘要、Recent Tail、Transcript、Memory Flush 与恢复路径。重点不是背 API,而是看懂 Runtime 在什么时候改写了哪些状态。
每一次模型调用真正看到的输入
:::card System / Rules / Tools + 压缩摘要(旧历史的投影) + Recent Tail(近期原文) + 当前用户输入 = Active Context :::
三个必须分开的运行边界
本地 Runtime组装、裁剪、替换通常仍是同一进程
远程模型服务生成 Summary / Checkpoint网络请求 ≠ OS 进程
磁盘 / SQLiteTranscript 与 Memory保存 ≠ 模型已看见
Agent 为什么会“忘记”:Pi、Codex、Claude Code、OpenCode、Hermes 与 OpenClaw 的上下文压缩原理
面向熟悉 Java、但不熟悉 Agent Runtime 内部实现的读者。本文基于 2026-08-13 的官方文档和源码快照。
先给结论
六套系统看起来都叫“自动压缩上下文”,底层却至少分成四种路线:
- 滚动摘要 + 保留近期原文:Pi、OpenCode、Hermes 的默认实现最典型。
- 客户端摘要或服务端 Compaction Checkpoint:Codex 会根据 Provider 能力选择本地总结或远程
/responses/compact。 - 先清理旧 Tool Output,再做摘要:Claude Code、OpenCode、Hermes 都采用这种分层减负思路。
- 压缩前先写回长期记忆:OpenClaw 的 memory flush 是最鲜明的例子。
最重要的事实是:
上下文压缩不是让模型“记忆变强”,而是 Runtime 重写下一次模型请求中的消息数组。
模型没有一个会在两次 API 请求之间自动增长的 Java 对象。每次调用模型时,Agent Runtime 都要重新组装:
System Prompt
+ 项目规则 / Skills / Tool Schemas
+ 历史消息 / Tool Results
+ 压缩摘要
+ 最近几轮原文
+ 当前用户输入
────────────────────────────
= 本次真正发送给模型的 Context当这份请求快装不下时,Runtime 用更短的信息替换旧信息。被替换的是“模型当前可见视图”,不一定是磁盘上的完整会话记录。
一、先把五个经常混用的词拆开
1. Active Context:模型这一次真的看到什么
这是本次 API 请求携带的内容。它受 Context Window 硬限制。模型只能依据这里面的信息推理。
2. Transcript:完整会话账本
它通常保存在 JSONL、SQLite 或事件流中。里面可以保留旧消息、工具调用和压缩边界。Transcript 还在,不代表模型当前能看到它。
3. Compaction:重写 Active Context
典型做法是:
旧历史原文 ──LLM 总结──► 滚动摘要
最近若干轮 ────────────► 保留原文
下一次请求 = 系统上下文 + 滚动摘要 + 最近原文这是有损压缩。摘要没有带走的细节,在后续推理中就相当于丢失,除非 Runtime 能从 Transcript 或外部记忆再次检索。
4. Memory:跨压缩或跨 Session 的外部状态
Memory 可以是 MEMORY.md、Daily Log、SQLite、向量库或结构化观察记录。它位于 Context Window 外,需要注入或检索后模型才能使用。
5. Prompt Cache:少算一次,不是少发送一次
Prompt Cache 复用相同的请求前缀,降低延迟或费用;它不扩大 Context Window,也不替代 Compaction。压缩会改变 Conversation Prefix,因此通常会让会话层缓存失效。
可以用 Java 心智模型表示:
// Transcript:数据库中的完整事件
List<Event> transcript = sessionRepository.load(sessionId);
// Active Context:本轮临时 DTO
ModelRequest request = contextAssembler.build(transcript, memory, rules);
// Compaction:改变 build() 以后选入 request 的内容
CompactionEntry summary = compactor.compact(oldEvents);
// 模型从来不会直接“拥有” transcript
ModelResponse response = modelClient.call(request);二、所有压缩系统共有的一条时序
用户/Tool Agent Runtime Session Store 模型 API
│ │ │ │
│ 新消息或 Tool 结果 │ │ │
├───────────────────►│ append │ │
│ ├────────────────────────────►│ │
│ │ 估算下一次请求 Token │ │
│ │ │ │
│ │ 超过安全阈值? │ │
│ │ ├─否:正常组装请求 │ │
│ │ └─是:进入 Compaction │ │
│ │ │ │
│ │ 选旧历史 + 近期保留区 │ │
│ ├────────总结请求────────────────────────────────────►│
│ │◄──────────────────────────────结构化摘要────────────┤
│ │ 保存 Compaction Entry │ │
│ ├────────────────────────────►│ │
│ │ │ │
│ │ 新 Active Context: │ │
│ │ 系统 + 摘要 + 近期原文 │ │
│ ├────────────────────────────────────────────────────►│
│ │◄──────────────────────────────正常 Agent 响应────────┤原生压缩通常不创建新的本地 OS 进程。它发生在 Agent Runtime 进程内,只多出一次总结模型请求;远程模型服务当然是进程外的网络服务。
真正决定质量的不是“有没有摘要”,而是以下六个选择:
- 何时触发,预留多少 Headroom;
- 是否先清理旧 Tool Output;
- 摘要覆盖哪一段;
- 最近多少原文不压缩;
- 上一次摘要如何进入下一次压缩;
- 原始证据是否还能按需找回。
三、Pi:最清楚的“Compaction Entry + Recent Tail”模型
Pi 的默认触发公式非常直接:
contextTokens > contextWindow - reserveTokens当前默认值为:
reserveTokens = 16384:给下一次模型输出和工具循环留空间;keepRecentTokens = 20000:压缩以后尽量保留的近期原文预算。
Pi 的完整流程
【同一个 Pi 进程】
AgentSession
│ 根据最后一次真实 usage + 尾部估算得到 contextTokens
│
├─ 未到阈值 → 正常请求模型
│
└─ 到阈值
│ 从后往前累计,找到 keepRecentTokens 切点
│ 避免把 tool call 与 tool result 拆开
│
├─ old span ──► 序列化为普通文本
│ Tool Result 最多保留 2000 字符
│ └─► 独立 LLM 总结请求
│
├─ recent tail ─► 原文保留
│
└─ append CompactionEntry
summary
firstKeptEntryId
tokensBefore
readFiles / modifiedFiles磁盘 Session 并没有简单地把旧消息物理删除。Pi 追加一个 CompactionEntry,其中的 firstKeptEntryId 告诉 Context Builder:
模型下一轮看到:System Prompt + Summary + firstKeptEntryId 之后的消息
磁盘会话树仍保留:此前的原始 Entry + 新增 CompactionEntryPi 如何处理超长单轮 Tool Loop
正常切割发生在用户回合边界。如果一个用户回合本身就超过 keepRecentTokens,Pi 会在回合内部切割,但不会从 Tool Result 中间硬切。它分别生成:
- 以前历史的 Summary;
- 当前超长回合前半段的 Turn Prefix Summary;
然后把两者合并,近期后半段仍保留原文。
多次压缩为什么仍会腐烂
第二次压缩时,Pi 会把 previousSummary 和新产生的旧消息一起交给模型,要求生成更新后的滚动摘要。这比“只总结最新片段”更连贯,但依然存在信息级联损失:
原始事实 A、B、C
↓ 第一次摘要遗漏 C 的理由
摘要 A、B + 新历史 D
↓ 第二次摘要无法凭空恢复 C
摘要 A、B、D这就是观察记忆、可检索 Transcript 和结构化工作状态存在的意义。
Pi 的扩展点
session_before_compact Hook 可以:
- 取消压缩;
- 替换摘要;
- 使用另一个模型;
- 保存自定义
details; - 改变保留边界。
因此 Pi 的原生机制简单,但它给第三方压缩引擎留下了清晰的接管点。
四、Codex:客户端摘要与服务端 Compaction 双路径
Codex 当前源码中的核心差异是:它不一定总在 CLI 本地生成普通 Markdown 摘要。Runtime 会根据 Provider Capability 选择压缩实现。
Codex Runtime
│
├─ Provider 支持 Remote Compaction V2 → 远程 V2 checkpoint
├─ Provider 支持 Remote Compaction V1 → /responses/compact
└─ Provider 不支持 → 本地 LLM 摘要1. 本地摘要路径
Codex 把压缩 Prompt 附加到当前 History,通过正常模型流生成 Summary。成功后,它构造一份新的 Active History:
Canonical Initial Context
+ 最近的真实 User Messages(总预算最多约 20K tokens)
+ Summary Prefix + 模型生成的 Summary这里有一个很有 Codex 特征的选择:本地压缩会尽量保留近期用户原话,而旧 Assistant、Reasoning 和 Tool 细节主要进入摘要。
如果压缩请求本身仍然超窗,Codex 会从压缩输入开头逐项移除最旧 History Item,再重试;它不会因为一次总结请求过大就立刻放弃整个 Session。
2. 远程压缩路径
Provider 支持 /responses/compact 时,Codex 把 History 交给远程压缩端点,得到 Provider 归一化的 Replacement History。客户端安装前还会:
- 丢弃远端返回的旧 Developer Message,避免重复或过期系统指令;
- 丢弃不能安全回放的 Tool、Reasoning 等项目;
- 保留真实用户消息、摘要和允许的 Assistant 内容;
- 从当前 Session 重新注入 Canonical Initial Context。
所以远程压缩不是“服务器替 Codex 写一段 Markdown”这么简单,而是服务器返回一种可以在后续 Responses 请求中继续携带的压缩历史/checkpoint。
3. 触发不只发生在两个用户回合之间
Codex 会跟踪:
- 当前 Active Context Tokens;
- Model 给出的
auto_compact_token_limit; -完整 Context Window 硬上限; - Token Limit 的统计范围是全部 Context,还是去掉固定 Prefix 后的 Body。
它可以在下一轮采样前压缩,也可以在 Tool Loop 中模型还需要 Follow-up、但 Token 已到阈值时做 Mid-turn Compaction。模型切换到更小窗口或 Compaction Hash 改变,也可能触发预处理。
4. Pre-turn 与 Mid-turn 为什么要区别对待
Pre-turn 压缩后,下一次普通 Turn 可以重新完整注入初始上下文。Mid-turn 压缩仍位于同一个工具循环里,Codex 必须把 Canonical Initial Context 插入到最后一条真实用户消息之前,同时保持 Compaction Summary 处在模型训练所期望的位置。
这是 Codex 比很多“摘要后继续”实现更精细的地方:它把压缩当作 Agent Loop 内部的状态转换,而不是一个只能发生在回合结束后的命令。
5. 持久化和 Hook
Codex 调用 replace_compacted_history() 安装新的活跃历史,同时记录 Context Compaction Item、Window 编号和 Checkpoint 元数据。当前源码也有 PreCompact/PostCompact Hook 生命周期。
原生压缩仍在同一个 Codex 进程内执行;本地路径多一次模型请求,远程路径多一次 Compact Endpoint 请求,不会为每次压缩 spawn 一个新 Codex CLI。
来源:compact.rs、compact_remote.rs、context_window.rs。Codex 的压缩源码是公开的,但部分远程 checkpoint 语义仍由 OpenAI 服务端实现,因此客户端只能看到协议边界。
五、Claude Code:先清旧 Tool Output,再用缓存友好的独立请求总结
Claude Code 并未公开完整 Runtime 源码,所以这一节只陈述 Anthropic 官方文档明确保证的行为,不伪造函数名或内部数据结构。
官方公开的压缩顺序
Context 接近上限
│
├─ 先清理较旧 Tool Outputs
│
└─ 仍然太大
│
├─ 发起独立 Summarization Request
└─ 用 Structured Summary 替换 Conversation HistoryClaude Code 的总结请求包含:同一 System Prompt、Tools、完整 History,以及最后追加的一条 Summarization Instruction。如果原会话 Prompt Cache 仍然温热,这次请求可以命中旧前缀,因此主要成本来自生成摘要,而不是重新处理整段输入。
压缩完成以后,Conversation Layer 变成了新的短摘要,旧 Conversation Prefix 不再匹配,因此会话层 Cache 会失效;但 System Prompt 和未变化的 Project Context 层仍有机会命中缓存。
什么会在压缩后重新出现
| 内容 | 压缩后行为 |
|---|---|
| System Prompt、Output Style | 不属于普通消息历史,保持不变 |
| 项目根 CLAUDE.md、无 paths 限制的 Rules | 从磁盘重新注入 |
| Auto Memory | 从磁盘重新注入 |
带 paths: 的 Rules、子目录 CLAUDE.md | 暂时丢失,下一次读取匹配文件时再加载 |
| 已调用 Skill Body | 重新注入;单 Skill 与总量均有上限,旧 Skill 可能被丢弃 |
| Hook | 是代码执行机制,本身不占会话 Context |
这揭示了一个重要架构原则:持久规则不应该只存在于早期聊天消息里。Claude Code 把 CLAUDE.md、Auto Memory 和 Skill 设计成可重新装载的外部来源,正是为了不让它们完全依赖一次有损摘要。
触发、控制与防抖
- 自动压缩在接近模型限制时发生;阈值取决于模型和配置;
/compact <focus>可以告诉 Summary 保留什么;/autocompact可以设置自动压缩窗口;- Session 的 Extended Thinking 配置会影响摘要生成;
- 如果单个文件或 Tool Output 导致压缩后立即再次爆满,Claude Code 在几次尝试后停止自动压缩并报告 Thrashing,而不是无限循环。
Claude Code 的优势不是我们知道了它的全部算法,而是它把“可重新注入的项目规则、Skill、Auto Memory”与“可丢弃的 Conversation”分层得很清楚。
来源:Claude Code Context Window、How Claude Code Works、Prompt Caching。
六、OpenCode:Tool Output Pruning 与 Rolling Summary 是两个阶段
OpenCode 的实现特别适合解释“删除垃圾”和“压缩语义”为什么不是一回事。
阶段一:Prune 旧 Tool Output,不调用 LLM
当前实现从后向前统计 Tool Results:
- 最近约 40K Token 的 Tool Output 受保护;
skillTool 受保护;- 至少跨过最近两个用户回合;
- 预计可回收超过约 20K Token 才执行;
- 被清理的 Tool Part 标记
time.compacted,后续模型投影看到占位符。
[Tool result: 30K 日志原文]
↓ prune
[Old tool result content cleared]它没有试图“理解”日志,只是把高体积、低复用价值的数据移出 Active Context。
阶段二:生成滚动 Summary
如果整体请求仍超出可用预算,OpenCode 选择近期 Tail,将更早的 Head 序列化为:
[User]: ...
[Assistant reasoning]: ...
[Assistant tool call]: ...
[Tool result]: ...(单项最多 2000 字符)然后用 compaction Agent 生成结构化摘要。已有上一版 Summary 时,新 Prompt 明确要求合并旧摘要与本轮新增历史;旧摘要随后退出 Active Projection,新的滚动摘要成为唯一版本。
Overflow Replay 与自动继续
如果是 Provider 已经返回 Context Overflow,OpenCode 会向前找到最近一个真实用户请求,把它从总结输入中暂时拿出,压缩更早历史,然后重新创建一条用户消息 Replay 原请求。普通自动压缩成功后,它可以注入一条 Synthetic Continue Message,让 Agent 继续未完成的工作。
插件扩展点
experimental.session.compacting:向压缩 Prompt 注入 Context,或完全替换 Prompt;experimental.compaction.autocontinue:控制压缩后是否自动继续;experimental.chat.messages.transform:在压缩前变换待总结消息。
OpenCode 当前仓库还包含新的 Event-sourced Compaction Core:Started 是尝试记录,只有 Ended 才持久化最终 Summary 与 Recent Context。这体现了相同理念——完整 Transcript 和模型可见 Projection 是两回事。
七、Hermes:双重保险 + 可替换 ContextEngine
Hermes 的特点不是摘要模板,而是把“谁拥有上下文策略”抽象成 ContextEngine。
class ContextEngine:
should_compress(...)
compress(messages, ...)
select_context(...) # 可选:本次请求如何选上下文
on_turn_complete(...) # 可选:回合结束后观察默认是有损 ContextCompressor;用户也可以显式选择其他插件引擎,例如 Lossless Context Management。Memory Provider 只观察和存储,不拥有压缩决策;Context Engine 才拥有 Active Context 的选择权。
两层独立触发器
消息进入 Gateway
│
├─ Gateway Session Hygiene
│ 约 85% 窗口,优先真实 usage,否则粗略估算
│ 用于捕获夜间累积、跨平台消息等漏网大 Session
│
▼
Agent Tool Loop
│
└─ ContextCompressor
配置默认 50%
当前小于 512K 的窗口会把有效阈值向上抬到至少 75%
使用真实 API usage 作为主要依据Gateway 层是安全网,Agent 层才是日常管理器。两层阈值不同,避免每条网关消息都重复压缩。
默认 Compressor 的四阶段
- 无模型清理:保护区外、超过 200 字符的旧 Tool Results 替换为清理标记。
- 切分边界:保护 System + 头部消息;中段待总结;尾部按 Token Budget 与最少消息数共同保护;Tool Call/Result 不拆开。
- 辅助模型总结:中段一次性送给独立配置的 Compression Model,生成 Goal、Progress、Key Decisions、Relevant Files、Next Steps 等结构化摘要。
- 重新组装:Head + Summary + Tail,并清理孤儿 Tool Call/Result。
摘要预算随被压缩内容增长:目标约为内容 Token 的 20%,下限 2K,上限为 min(contextWindow × 5%, 12K)。
In-place Compaction
默认 in_place: true 时,Hermes 不为每次压缩创建新 Session ID:
同一个 sessionId
├─ 旧中段:active=0, compacted=1,仍可搜索
├─ Rolling Summary:进入 Active Context
└─ Recent Tail:保留原文旧旋转模式仍可选,但默认同一 Session ID 避免 Goal 状态、搜索和父链在多次轮换中断裂。
Hermes 的工程风险
Compression Model 必须装得下待总结中段。辅助模型便宜并不代表合适:窗口太小会让 Summary 请求失败。Hermes 为此校验辅助模型窗口、调整阈值并加入 Anti-thrashing 保护。可插拔 ContextEngine 很强,但意味着错误的第三方引擎也可以彻底控制请求投影。
来源:Context Compression and Caching、context_engine.py、context_compressor.py。
八、OpenClaw:压缩前先做一次“内存写回”
OpenClaw 当前不是简单调用 Pi /compact 的薄壳。它在单一 Gateway 进程中拥有 Session 状态,当前版本把 Session Rows 和 Transcript Events 存入每 Agent SQLite,并维护自己的 Agent Core、Compaction Safeguard、Memory Flush 与 Provider 插槽。
两层持久化
Gateway Process
│
├─ Session Row
│ sessionKey → current sessionId
│ token counters / compactionCount / memoryFlush state
│
└─ Append-only Transcript Events
message / toolResult / compaction / branch_summary
id + parentId 构成树Compaction Entry 是持久事件。未来模型请求使用 Summary + firstKeptEntryId 后的 Recent Messages;完整 Transcript 由 SQLite 保留和索引。
四类触发边界
- Threshold Maintenance:一次成功 Turn 后,Context 超过模型窗口减 Headroom。
- Overflow Recovery:Provider 返回明确的 Context Overflow,压缩后重试。
- Active Transcript Byte Guard:可选;下一轮开始前,Transcript 活跃部分字节数过大则先本地压缩。
- Mid-turn Precheck:可选;Tool Result 加入后、下次模型调用前检查压力。它不会在内部直接硬改状态,而是抛出结构化信号,让外层 Recovery Path 负责截断或压缩并重试。
Memory Flush 时序
这是 OpenClaw 与普通滚动摘要最大的区别:
Context 接近 Compaction Threshold
│ 但还留有 softThresholdTokens 空间
▼
Gateway 启动一次静默 Agent Turn
│ Prompt:把关键状态写入 memory/YYYY-MM-DD.md 等
│ 回复必须为 NO_REPLY
▼
Agent 使用文件工具写 Durable Memory
│
▼
Delivery Layer 吞掉 NO_REPLY,用户看不到中间回复
│
▼
正式 Compaction:旧对话 → Summary + Recent TailMemory Flush 不是摘要本身,而像数据库系统在页回收前做 Write-back:先把必须长期存在的状态从易失工作集写到持久层,再允许有损压缩发生。
它每个压缩周期只运行一次,状态记录在 Session Row;只适用于 Embedded OpenClaw Session,CLI Backend 跳过;只读 Workspace 也会跳过。
Safeguard 与质量审计
OpenClaw 插件可通过 registerCompactionProvider() 注册压缩 Provider。Safeguard 模式不仅生成摘要,还会检查最终持久化文本是否:
- 保留必须的结构标题;
- 保留 Pending Asks;
- 保留精确标识符;
- 符合最终 Token Budget。
检查失败会在限定次数内纠正;用尽重试后,压缩在 Append 之前取消,原 Transcript 继续作为权威来源。第三方 Provider 失败或返回空结果时回退到内置总结。
这说明 OpenClaw 的设计重点已经从“能不能总结”升级为“摘要能不能作为可验证的状态迁移提交”。
九、六套系统放进同一张表
| 系统 | 触发策略 | 压缩前减负 | 摘要/Checkpoint | Recent Tail | 原始记录 | 长期记忆协作 | 可扩展性 |
|---|---|---|---|---|---|---|---|
| Pi | Window - Reserve | 总结输入截断 Tool Output | 当前模型结构化摘要 | 默认约 20K | Session Tree 保留 Entry | 本身较轻 | session_before_compact 可接管 |
| Codex | 模型限制 + 硬窗口;支持 Mid-turn | 压缩失败时移除最旧输入;远端过滤不可回放项 | 本地摘要或远程 Compaction | 本地保留近期 User Messages;远端由 checkpoint 决定 | 记录 Compaction Checkpoint | 依赖外部规则/Hook | 本地/远端双路径 + Hooks |
| Claude Code | 模型/配置相关的接近上限 | 先清旧 Tool Outputs | 独立请求生成 Structured Summary | 官方确认保留近期与关键内容,未公开算法 | Session 可 Resume;实现细节闭源 | CLAUDE.md、Auto Memory 重新注入 | PreCompact Hook、Compact Instructions |
| OpenCode | 完整模型请求估算超过可用窗口 | 独立 Prune,保护近期 Tool Output | Compaction Agent 更新滚动摘要 | Token Budget;支持回合内切分 | Session 数据与模型投影分离 | 依赖插件/外部文件 | 可替换 Prompt、Context、AutoContinue |
| Hermes | Agent 阈值 + Gateway 85% 安全网 | 旧 Tool Result 清理 | Auxiliary Model 滚动摘要 | Token Budget + 最少消息/用户锚点 | 旧中段 Soft Archive、可搜索 | Memory Provider 与 ContextEngine 分工 | 整个 ContextEngine 可替换 |
| OpenClaw | Turn 后阈值 + Overflow + 可选 Preflight/Mid-turn | Transcript Guard、Tool 截断、分阶段总结 | Built-in/Provider Safeguard | 默认近期 Token Budget | SQLite Append-only Transcript | 压缩前 Silent Memory Flush | Provider 插槽 + Hooks + 质量审计 |
不能简单排名“谁最好”,因为它们优化的故障模式不同:
- Pi 最容易理解和扩展;
- Codex 最强调 Agent Loop 内的本地/远端状态转换;
- Claude Code 最强调可重新加载的规则层与 Prompt Cache;
- OpenCode 把无语义 Prune 与语义 Summary 分得最直观;
- Hermes 给 ContextEngine 最大控制权;
- OpenClaw 把压缩、持久记忆和提交前质量检查结合得最深。
十、上下文压缩真正会失败在哪里
1. Summary of Summary 漂移
每次滚动摘要都是不可逆投影。旧摘要遗漏的信息不会在下次自动回来。结构化模板只能降低风险,不能消除信息熵。
2. Tool Output 比业务历史更快填满窗口
日志、网页、测试输出和文件全文常常占大头。先 Prune、延迟加载、把原始数据放外部存储,再检索小片段,通常比提高摘要模型更划算。
3. 把 Prompt Cache 当成 Memory
Cache 只复用相同前缀的计算结果。它不能让模型看到已从请求中删除的消息。Compaction 改写前缀后,Conversation Cache 本就应该失效。
4. 把 Transcript 存在当成“模型还能回忆”
磁盘历史需要 Recall Tool、搜索索引或 Runtime 重注入,才会重新进入 Active Context。没有检索路径的 Archive,只能帮助人类审计,不能帮助本轮模型推理。
5. 压缩时机错误
- 太晚:总结请求本身也超窗;
- 太早:频繁打断工作、浪费 Cache;
- Tail 太大:压缩后几乎没省空间,产生 Thrashing;
- Tail 太小:最新用户约束和未完成 Tool 状态被摘要化。
6. 多个插件同时拥有同一 Hook
两个 Extension 都想从 session_before_compact 返回自己的 Replacement Summary,通常不是“效果叠加”,而是所有权冲突。压缩链只能有一个最终写入者;Memory Observer 可以并存,但两个 Compaction Owner 必须明确协调。
十一、值得研究的扩展:它们分别改动哪一层
pi-custom-compaction:替换摘要模型与模板
它拦截 Pi 原生 session_before_compact:切点和 Session Entry 仍使用 Pi 机制,但 Summary Model、Fallback Chain、Trigger Token、Recent Retention 和模板可以自定义。
适合:你认可 Pi 的状态模型,只想提升摘要质量、降低总结费用或让大窗口模型更早压缩。
不解决:跨 Session 长期记忆、原始证据检索。
项目:nicobailon/pi-custom-compaction。
pi-boomerang:在任务边界主动 Collapse
它不是等整个 Session 快满才压缩,而是围绕一次明确任务设置 Anchor:
Anchor
└─ 大量读文件 / 修改 / 测试 / Tool Calls
↓ 任务完成
Branch Summary
└─ Future Context 只看 Expanded Handoff完整工作历史仍在 Pi Session Tree,可以通过 /tree 回看;主 Agent 收到包含 Changed Files、Reads、Commands、Failures、Outcome 的自包含 Handoff。
适合:一次重构、一次 Review、一次自主实现产生大量临时上下文。
pi-observational-memory:边工作边蒸馏,而不是压缩时临时回忆
它在同一个 Pi 进程内运行 Observer、Reflector、Dropper 三个 Agent Loop。它们是独立模型请求,不是三个新 OS 进程:
对话增量
└─ Observer:提取具体观察
└─ Reflector:合并成稳定认识
└─ Dropper:删除过时、重复、已被 Reflection 覆盖的活跃观察压缩时 Memory 已经准备好,Extension 将观察和反思渲染进 Replacement Context,并提供 Recall 路径返回来源证据。
适合:多日同一 Session、决策理由很多、经历多次 Compaction 的工作。
代价:多次后台模型调用、更多状态与模型配置;V3 与 V2 不兼容,应使用稳定 npm 版本并从新 Session 开始。
项目:elpapi42/pi-observational-memory。
pi-blackhole:算法式摘要 + 观察记忆合并为一个 Owner
它把确定性的结构提取放在 Compaction Slot,把 Observer/Reflector/Dropper 放在 Memory Layer,解决 pi-vcc 与 Observational Memory 同时争抢 Hook 的问题。
优势:Compaction 本身不需要 LLM,总结输出可复现,并保留 Recall ID。
风险:项目较新、运行面更大;README 明确要求卸载独立的 pi-vcc 和 pi-observational-memory,因为三者不能同时拥有同一个压缩生命周期。
pi-memory:长期记忆,不是压缩器
它把 Curated Memory、Daily Logs、Scratchpad 保存为 Markdown,并可通过 qmd 做 BM25、向量或混合搜索。它在 Session 检查点注入有上限的稳定 Memory Snapshot,还能在 Compaction 前写 Handoff。
适合:新 Session 仍需记住包管理偏好、架构决策、历史教训。
不要期待:它替你重写当前巨大 Conversation;它解决的是外部持久状态与 Recall。
context-mode:从源头阻止大输出进入 Context
这是跨 Claude Code、Codex、OpenCode、Pi、OpenClaw 等平台的 Context Gateway/MCP 方案。它把原始 Tool Output 留在本地 Sandbox/SQLite/FTS5 中,只把程序化处理后的短结果送进 Agent,并利用 Hook 在压缩前生成恢复快照。
它解决的是“输入预防 + 外部检索”,不是替代所有宿主的原生 Summary。需要注意:该项目采用 Elastic License 2.0,源码可读、可修改,但不应把它误称为标准 OSI 开源许可证项目。
十二、怎样组合才不会互相打架
普通 Pi 编程会话
Pi 原生 Compaction
+ Tool 输出保持克制
+ 关键规则写 AGENTS.md先用默认机制,观察实际遗忘点,再增加复杂度。
明确边界的大任务
Pi 原生 Compaction
+ pi-boomerang(任务结束主动折叠)Boomerang 解决局部任务爆炸,原生 Compaction 处理整个 Session 的长期增长。
多天持续开发
二选一作为 Compaction Owner:
方案 A:Pi + pi-observational-memory
方案 B:Pi + pi-blackhole再按需增加 pi-memory 作为跨 Session 长期记忆。不要同时安装两个都接管 session_before_compact 的引擎。
大量网页、日志、MCP 数据
宿主原生 Compaction
+ context-mode / 专用数据 Sandbox
+ 按需检索最好的 Token 是从未进入 Active Context 的 Token。
十三、最终的系统设计答案
一个更可靠的长期 Agent 不应该只有一个“更聪明的摘要 Prompt”,而应该有四层:
┌──────────────────────────────────────────────┐
│ L1 Active Context │
│ 当前目标、近期原文、必要 Tool 结果 │
├──────────────────────────────────────────────┤
│ L2 Rolling Compaction │
│ 旧历史的结构化 Handoff │
├──────────────────────────────────────────────┤
│ L3 Durable Memory + Retrieval │
│ 决策、偏好、教训、观察、可按需召回的证据 │
├──────────────────────────────────────────────┤
│ L4 Full Transcript / Event Log │
│ 完整审计与恢复来源,不默认全部送给模型 │
└──────────────────────────────────────────────┘Prompt Cache 是这四层之外的性能优化;Subagent 和 Boomerang 是减少主上下文污染的工作分解策略;Tool Pruning 是在信息进入摘要之前清掉低价值体积。
所以真正成熟的上下文管理不是“压缩得越小越好”,而是:
让高价值状态留在模型眼前,让低频事实可检索,让完整证据可恢复,让临时噪声尽早退出。
这也是六套实现最终趋同的方向。
研究边界与源码快照
- Pi:commit
46bb9a2c3bdb296b0d2179f7309ec6b79a7f3106 - Codex:commit
c30a3e49c9231361abeaa88d4a57bb7c3e248a50 - OpenCode:commit
cc4b45612974f735ddec46009ede07729511fba4 - Hermes Agent:commit
fa83af3f9a42790730b8966ff67e7d9fb627899f - OpenClaw:commit
57fabb2c9c35db79956c9aa1e9a1956b09d9a39e - Claude Code:闭源实现,依据 2026-08-13 抓取的官方 Context Window、How It Works 和 Prompt Caching 文档;未将观察行为反推为未公开源码。
仓库持续变化,本文中的默认阈值和实验性接口应以对应版本源码为准。
活动上下文本次远程模型请求里真实存在的 Token。
压缩摘要旧历史的有损投影,用来续接当前任务。
Transcript / Memory外部持久状态,需要 Runtime 检索或注入才能生效。
来源与依据
- 旧 Wiki 原文 · 上下文压缩不是记忆增强,而是 Runtime 对模型输入的重写
收录日期:2026-10-11
- 原文引用 · github.com
收录日期:2026-10-11
- 原文引用 · github.com
收录日期:2026-10-11
- 原文引用 · github.com
收录日期:2026-10-11
- 原文引用 · github.com
收录日期:2026-10-11
- 原文引用 · github.com
收录日期:2026-10-11
- 原文引用 · code.claude.com
收录日期:2026-10-11
- 原文引用 · code.claude.com
收录日期:2026-10-11
- 原文引用 · code.claude.com
收录日期:2026-10-11
- 原文引用 · github.com
收录日期:2026-10-11