源码级对比 PiCodexClaude Code OpenCodeHermesOpenClaw

上下文不是记忆:
六大 Agent 压缩发动机

从一次模型请求到底装了什么开始,逐层拆开触发器、Tool Output 裁剪、滚动摘要、Recent Tail、Transcript、Memory Flush 与恢复路径。重点不是背 API,而是看懂 Runtime 在什么时候改写了哪些状态。

每一次模型调用真正看到的输入

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 的官方文档和源码快照。

先给结论

六套系统看起来都叫“自动压缩上下文”,底层却至少分成四种路线:

  1. 滚动摘要 + 保留近期原文:Pi、OpenCode、Hermes 的默认实现最典型。
  2. 客户端摘要或服务端 Compaction Checkpoint:Codex 会根据 Provider 能力选择本地总结或远程 /responses/compact。
  3. 先清理旧 Tool Output,再做摘要:Claude Code、OpenCode、Hermes 都采用这种分层减负思路。
  4. 压缩前先写回长期记忆: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 进程内,只多出一次总结模型请求;远程模型服务当然是进程外的网络服务。

真正决定质量的不是“有没有摘要”,而是以下六个选择:


三、Pi:最清楚的“Compaction Entry + Recent Tail”模型

Pi 的默认触发公式非常直接:

contextTokens > contextWindow - reserveTokens

当前默认值为:

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 + 新增 CompactionEntry

Pi 如何处理超长单轮 Tool Loop

正常切割发生在用户回合边界。如果一个用户回合本身就超过 keepRecentTokens,Pi 会在回合内部切割,但不会从 Tool Result 中间硬切。它分别生成:

  1. 以前历史的 Summary;
  2. 当前超长回合前半段的 Turn Prefix Summary;

然后把两者合并,近期后半段仍保留原文。

多次压缩为什么仍会腐烂

第二次压缩时,Pi 会把 previousSummary 和新产生的旧消息一起交给模型,要求生成更新后的滚动摘要。这比“只总结最新片段”更连贯,但依然存在信息级联损失:

原始事实 A、B、C
   ↓ 第一次摘要遗漏 C 的理由
摘要 A、B + 新历史 D
   ↓ 第二次摘要无法凭空恢复 C
摘要 A、B、D

这就是观察记忆、可检索 Transcript 和结构化工作状态存在的意义。

Pi 的扩展点

session_before_compact Hook 可以:

因此 Pi 的原生机制简单,但它给第三方压缩引擎留下了清晰的接管点。

来源:Pi Compaction 文档、compaction.ts。


四、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。客户端安装前还会:

所以远程压缩不是“服务器替 Codex 写一段 Markdown”这么简单,而是服务器返回一种可以在后续 Responses 请求中继续携带的压缩历史/checkpoint。

3. 触发不只发生在两个用户回合之间

Codex 会跟踪:

它可以在下一轮采样前压缩,也可以在 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 History

Claude 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 设计成可重新装载的外部来源,正是为了不让它们完全依赖一次有损摘要。

触发、控制与防抖

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:

[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 继续未完成的工作。

插件扩展点

OpenCode 当前仓库还包含新的 Event-sourced Compaction Core:Started 是尝试记录,只有 Ended 才持久化最终 Summary 与 Recent Context。这体现了相同理念——完整 Transcript 和模型可见 Projection 是两回事。

来源:OpenCode session/compaction.ts、新 Core compaction.ts。


七、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 的四阶段

  1. 无模型清理:保护区外、超过 200 字符的旧 Tool Results 替换为清理标记。
  2. 切分边界:保护 System + 头部消息;中段待总结;尾部按 Token Budget 与最少消息数共同保护;Tool Call/Result 不拆开。
  3. 辅助模型总结:中段一次性送给独立配置的 Compression Model,生成 Goal、Progress、Key Decisions、Relevant Files、Next Steps 等结构化摘要。
  4. 重新组装: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 保留和索引。

四类触发边界

  1. Threshold Maintenance:一次成功 Turn 后,Context 超过模型窗口减 Headroom。
  2. Overflow Recovery:Provider 返回明确的 Context Overflow,压缩后重试。
  3. Active Transcript Byte Guard:可选;下一轮开始前,Transcript 活跃部分字节数过大则先本地压缩。
  4. 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 Tail

Memory Flush 不是摘要本身,而像数据库系统在页回收前做 Write-back:先把必须长期存在的状态从易失工作集写到持久层,再允许有损压缩发生。

它每个压缩周期只运行一次,状态记录在 Session Row;只适用于 Embedded OpenClaw Session,CLI Backend 跳过;只读 Workspace 也会跳过。

Safeguard 与质量审计

OpenClaw 插件可通过 registerCompactionProvider() 注册压缩 Provider。Safeguard 模式不仅生成摘要,还会检查最终持久化文本是否:

检查失败会在限定次数内纠正;用尽重试后,压缩在 Append 之前取消,原 Transcript 继续作为权威来源。第三方 Provider 失败或返回空结果时回退到内置总结。

这说明 OpenClaw 的设计重点已经从“能不能总结”升级为“摘要能不能作为可验证的状态迁移提交”。

来源:Session Management & Compaction、Compaction 概念。


九、六套系统放进同一张表

系统 触发策略 压缩前减负 摘要/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 + 质量审计

不能简单排名“谁最好”,因为它们优化的故障模式不同:


十、上下文压缩真正会失败在哪里

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. 压缩时机错误

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、一次自主实现产生大量临时上下文。

项目:nicobailon/pi-boomerang。

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,因为三者不能同时拥有同一个压缩生命周期。

项目:k0valik/pi-blackhole。

pi-memory:长期记忆,不是压缩器

它把 Curated Memory、Daily Logs、Scratchpad 保存为 Markdown,并可通过 qmd 做 BM25、向量或混合搜索。它在 Session 检查点注入有上限的稳定 Memory Snapshot,还能在 Compaction 前写 Handoff。

适合:新 Session 仍需记住包管理偏好、架构决策、历史教训。

不要期待:它替你重写当前巨大 Conversation;它解决的是外部持久状态与 Recall。

项目:jayzeng/pi-memory。

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 开源许可证项目。

项目:mksglu/context-mode。


十二、怎样组合才不会互相打架

普通 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 是在信息进入摘要之前清掉低价值体积。

所以真正成熟的上下文管理不是“压缩得越小越好”,而是:

让高价值状态留在模型眼前,让低频事实可检索,让完整证据可恢复,让临时噪声尽早退出。

这也是六套实现最终趋同的方向。


研究边界与源码快照

仓库持续变化,本文中的默认阈值和实验性接口应以对应版本源码为准。

活动上下文本次远程模型请求里真实存在的 Token。
压缩摘要旧历史的有损投影,用来续接当前任务。
Transcript / Memory外部持久状态,需要 Runtime 检索或注入才能生效。