Agent 记忆层的三个工程问题:存得下、压得住、送得到

2026-09-23T09:25:00+08:00 | 10分钟阅读 | 更新于 2026-09-23T09:25:00+08:00

@
Agent 记忆层的三个工程问题:存得下、压得住、送得到

下午四点,你在 Claude Code 里把一个模块拆到一半。第二天早上你打开 Codex,同一个仓库,同一个分支。你做的第一件事不是写代码,而是重新讲一遍架构、重新讲一遍昨天试过但走不通的两条路、重新讲一遍还悬着的那三个问题。

这段重复劳动没有任何产出,但省不掉。因为新会话里的那个模型,对你昨天的工作一无所知。

这个场景正在变得越来越普遍,原因很简单:大部分人已经不可能只用一款编码 Agent。Claude Code 擅长长任务推理,Codex 在终端里手感好,Cursor 在编辑器内改小东西最快。工具切换是常态,而每次切换都在收一笔入场费。于是「记忆」从一个锦上添花的功能,变成了基础设施级别的刚需。

今天的 GitHub 趋势榜单把这件事摆到了台面上。Top 4 全部与智能体运行时、技能、记忆相关:google/ax 单日新增 2305 星登顶,akitaonrails/ai-memory 以 520 星进入前四,C 语言榜上 DeusData/codebase-memory-mcp 也排在第一。记忆层正在被当成一个独立赛道来解决,而不是某个框架里的一个可选开关。

不过把这几天能找到的公开资料读完之后,真正值得写下来的不是「记忆很重要」这种结论,而是一组反直觉的工程事实:大部分 Agent 并不会主动使用你给它准备好的记忆,以及交付这件事比存储难得多

一、先把三件被混为一谈的事拆开

「给 Agent 加记忆」这个说法太粗了。实际落到代码里,是三个完全不同的问题,各自对应不同的机制、不同的代价,也各自有自己会踩的坑。

机制作用范围性质触发方式
压缩(Compaction)整段对话,包括用户消息、助手推理、工具调用与结果有损,不可逆服务端按 token 阈值自动触发
工具结果清理(Tool-result clearing)只动 tool_result 内容块,保留调用记录无损,可重取服务端按 token 阈值自动触发
记忆(Memory)窗口之外的外部存储持久,跨会话模型自己决定是否发起工具调用

三者的边界很容易混淆,但用一句话就能区分开:

  • 压缩回收的是「对话和推理」占掉的空间,这部分内容没法重新取回,所以只能靠摘要尽力保真。
  • 清理回收的是「可重新获取」的空间。文件内容可以再读一次,接口响应可以再请求一次,所以清掉原始载荷而保留调用记录,是最轻的回收动作。
  • 记忆是把信息搬出窗口,让它在会话结束后依然存在,下一次启动时再拉回来。

分开看这件事是有回报的。Anthropic 在自家 agentic 搜索的内部评测里给过一组数字:把记忆工具和上下文编辑组合使用,相比基线提升 39%;只用上下文编辑,提升 29%。而在一个 100 轮的网页搜索评测里,上下文编辑让原本会因为上下文耗尽而失败的工作流跑完,同时把 token 消耗降低了 84%。

这里的顺序很关键。先判断你的瓶颈是哪一类:如果上下文里塞满的是文件读取和接口响应,那清理比压缩更便宜也更安全;如果膨胀来自长对话和模型自己的推理,那只能靠压缩。选错机制,等于花力气修了不痛的器官。

二、平台侧已经交了第一批原语

好消息是,这类需求已经不需要每家自己造轮子了。Claude 开发者平台把三个原语做成了一等公民:

能力标识符触发方主要可调参数
压缩compact_20260112服务端按 token 阈值trigger(默认 150K,最小 50K)、instructionspause_after_compaction
工具结果清理clear_tool_uses_20250919服务端按 token 阈值trigger(默认 100K)、keep(默认保留 3 次工具调用)、exclude_tools
记忆memory_20250818模型主动调用客户端自己实现 viewcreatestr_replacedelete 等操作

有几个细节值得单独拎出来,因为它们直接决定实现方式。

压缩块是要回传的。 压缩触发后,API 会返回一个 compaction 块,下一次请求必须把它带回去,API 会自动丢弃它之前的所有内容块。也就是说,摘要就是模型对那段工作的全部记忆,之前的东西真的没有了。

压缩前的思考会丢。 在较新的模型上,压缩块之前的 thinking 不会被带到后续请求里。如果你的推理过程里藏着关键判断依据,而摘要 prompt 没有要求保留它,那它就永久消失了。

摘要 prompt 是完全替换而不是追加的。 instructions 参数一旦提供,默认的摘要 prompt 就整个被替换掉。Anthropic 自己给的工程建议很明确:先在复杂 trace 上把召回率调到最大,确保重要的东西一条不落,然后再去精简,把多余的降下来。这个顺序不能反过来,因为过激的压缩会丢掉那些「当时看不出重要性、后面才显形」的上下文。

三、最反直觉的发现:Agent 不会主动用记忆

如果只读到这里,结论大概是「配好这几个参数就行了」。但一篇 2026 年 7 月的论文给出的实测数据,直接推翻了这个前提。

论文标题已经把观点写在了脸上:《Delivery, Not Storage: Cue-Anchored Working Memory as a Harness Property for Coding Agents》。核心论点有两层:第一,人类专家的记忆里有一层从来没被写下来的东西,就是「这里的坑在哪」「这个功能该去哪改」这类和场景绑定的操作事实,它们不是刻意记录的结果,而是干活的副产品,并且在情境提示出现时被不自觉地唤起;第二,对长跑 Agent 来说,这一层才是真正承重的,而且它必须是harness 的属性,不能交给 Agent 自己选择。

它的实测数据相当有说服力:

  • 主动使用记忆的比例接近于零。 即使记忆库里预先放好了与当前任务直接相关的知识,Agent 在 114 轮对话里执行了 0 次记忆操作。它不缺工具,只是不会想起来用。
  • 确定性注入全部命中。 换成由 harness 在固定时机注入,三次带注入的种子运行全部送达,而且审计日志记录的触发器评估里零误报。
  • 上下文在重复付费。 会话内 39% 的重复读取,是在重新取回这个会话早就已经付过费的内容。
  • 压缩会吃掉只在对话里存在的事实。 实验放了 10 条只存在于对话中的事实,第一次摘要之后它们全部消失,在 108 次强制压缩里有 106 次保持缺席。到后期,那个被剥夺了记忆的 Agent 开始去磁盘上 grep harness 自己的会话文件来重建这些事实,等于在违反既有指令的情况下,手工搭出 harness 从来没有给它的那一层。
  • 同样的 10 条事实,换成由 harness 拥有的存储注入,结果完全不同。 在 k 到 138 的过程中完整送达,139 次有审计记录的投递里,启动那一次加上每一次压缩恢复全部命中(138/138)。最终摘要里一条都没有,但 Agent 的记忆从来不依赖摘要器选择保留什么。

论文的结论可以直接抄下来当设计原则:交付,而不是存储,才是产品。对 Agent 来说可靠的记忆通道,是它从来不需要去想的那一条。

这解释了为什么「加一个 memory 工具让模型自己调用」的方案在真实项目里经常名存实亡。工具在那里,模型也确实有能力调用它,但在长任务的高压上下文中,它不会在正确的时间点想起来。

四、跨厂商交接:把「项目」而不是「Agent」当成持久单元

理解了「交付优先」,再回头看今天榜单上的 ai-memory,它的设计取舍就很好懂了。

它是一个 Rust 单二进制,跑一个 MCP 或 HTTP 服务,同时往你的 Agent CLI 里装几个很小的生命周期 hook。这些 hook 以 fire-and-forget 的方式,把经过脱敏和长度限制的观察发给服务端,内容包括提示词、工具调用生命周期事件和会话边界。会话结束时,服务端把这些观察编译成简洁的 markdown 页面,并为下一个会话生成一个有边界的交接块。

几个工程选择值得单独说:

持久单元是项目,不是 Agent。 观察落到按项目划分的 wiki 里,下一个 Agent 在它收到第一条提示词之前,就先拿到一个「你上次停在哪里」的块。这直接解决了开头那个场景:下午四点退出,早上九点在另一个工具里接着干。

存储是可读的纯文本。 wiki 是放在 git 仓库里的普通 markdown,能 grep,能用 Obsidian 打开,能 rsync 备份。默认路径不需要向量数据库,零 LLM 模式也能给出规则摘要、全文检索、实体匹配和图邻居检索。只有你想要更高保真的连续性时,才需要选择性地打开向量重排。

检索是混合的。 全文检索和实体匹配、图邻居用倒数排名融合拼在一起,向量重排是可选项。这个组合的意义是:即使用户完全不给记忆层任何模型能力,它依然能工作。

真正的落地成本在平台差异上。 这部分最容易被低估。有些 harness 会忽略 SessionStart 钩子的标准输出,所以那些工具需要通过显式的 memory_handoff_accept 来接收交接;有些客户端根本没有真正的会话结束事件,必须手动执行 finalize-session 才能收口。跨厂商方案的最大开销不是算法,而是每个 harness 的生命周期语义都不一样,你要么写适配层,要么接受手工步骤。

五、事件溯源与按窗口读取

另一条路线来自一篇 6 月的论文,ESAA-Conversational,处理的是同一个问题的另一种抽象。

它把可见对话本身当作一个本地事件存储:hook 和 watcher 捕获可见的轮次,归一化成只追加的 activity.jsonl,然后确定性地投影出几个读模型,包括 handoff.mdstate.mddecisions.mdtasks.json

这个设计有两个漂亮的地方:

机械捕获不需要模型推理。 捕获和投影都是确定性的,Agent 只在显式策展时(记录一条持久决策、登记一个会话级任务)才需要动用判断力。这避免了「让模型决定什么值得记」带来的不稳定。

读取按窗口进行,而不是默认吃下整个日志。 一个冷启动的 Agent 先读 handoff.md 这类投影视图,需要细节时再按窗口查询,比如取最近 20 轮、按主题过滤、或者围绕某个事件号取前后若干条。这直接呼应了 Anthropic 讲的「just in time」思路:不要预先加载所有可能相关的数据,而是保留轻量级的标识符,在运行时按需把数据拉进上下文。

两篇论文、两个项目,最后收敛到同一个判断:长跑 Agent 的记忆是交付架构的问题,不是存储引擎的问题。

六、几条可以落地的判断

把上面的材料压成几条可以直接用的经验:

  1. 不要一上来就上向量库。 markdown 加全文检索加实体与图检索,已经能覆盖大部分「我上次改了什么、为什么这么改」的查询。可 grep、可 diff 的存储,调试成本远低于隐式的向量召回,出问题时你能看见里面有什么。
  2. 注入要进 harness 生命周期,而不是只提供一个工具。 那组 114 轮 0 次调用的数据已经说明,把「想起来用记忆」的责任交给模型,基本等于没有记忆。
  3. 压缩 prompt 必须自己调。 默认摘要通常保守到丢失关键判断依据,而 instructions 是全量替换语义,不写就永远用默认。先追召回,再压精度。
  4. 工具结果优先清理,不要压缩。 清理是可逆的,Agent 需要时可以重新调用工具;压缩不可逆,代价要用摘要质量来偿付。
  5. 让记忆进 git。 页面级的替代链加上普通的 git log,就足以做时间旅行。在需要审计的场景里,「随时能回滚到某个时间点的记忆状态」比任何记忆准确率指标都实用。
  6. 脱敏和权限放在写入侧。 记忆层必然接触代码、密钥和业务上下文,写入前做脱敏比事后清洗便宜得多,也更不容易漏。
  7. 提前列平台差异清单。 会话开始钩子的输出会不会被读取、有没有真正的会话结束事件、压缩前后哪些字段语义变了,这三点决定了方案要写多少适配代码。

七、总结

今天的榜单其实在讲一件事:竞争焦点已经从「模型能力」搬到了「智能体运行时」。记忆是这块新地基里最像基础设施的一环,所以它开始被当成独立赛道,而不是框架的一个附属功能。

往前看,有两个方向值得盯。一个是记忆作为动作:写入、修改、压缩、遗忘都变成一等公民的操作,由 Agent 主动编排,而不是被动地往一个仓库里堆。另一个是压缩丢弃的上下文该去哪里,目前的主流做法还是让它蒸发掉,但从交付的角度看,那里恰恰是最该被接住的蒸馏后事实。

依然没解决的是时间推理。「这个决定是什么时候改的、为什么改」这类问题,在现有系统上的准确率明显掉档,而它恰好是跨会话协作里最常被问到的一类问题。

对独立开发者来说,有一条结论比技术细节更值得记住:在这个赛道上,中立格式的交接层比绑定单一厂商更抗风险。跨厂商交接是真实存在的痛点,能同时被 Claude Code、Codex 和 OpenCode 接受的方案,天然比只服务一家的方案更容易活下来。


参考来源

Me

Cut out summary from your post content here.

The remaining content of your post.