大模型变聪明的速度,比它变「懂我」的速度快得多。它可以一次读完十万行代码、把一个陌生模块讲得头头是道;但换台机器,或者隔两周再来,它对我一无所知:我喜欢什么写法、我踩过哪个坑、我强调过什么不能做——全部归零。

我们习惯把这件事叫「没有记忆」。但真去给自己的助手补记忆时会发现,难的既不是存储,也不是检索,而是回答一个问题:到底该记什么。

因为「记忆」这个词把太多不同的东西装进了同一个口袋:我的偏好像是记忆,项目里的命名约定也是记忆,上周排查某个报错的过程还是记忆。可这三样东西多久变一次、记错了会有多麻烦、什么时候该出现在对话里,完全不一样。

更麻烦的是,连「记下来的东西该怎么用」这件事,现成的答案都是彼此矛盾的:有人把记忆压到几千字符,每次开始新对话时整块塞给模型;有人一条都不塞,等模型自己想起来去查。研究界也没给出统一答案——现在最主流的几个「记忆考试」,考的其实是「能不能回答一段长对话里问过的事」,而不是「能不能用记住的东西把活干对」。

问题因此不在「存了多少」,而在「存的是什么、以及它什么时候该出现」。既然现成的答案互相矛盾,值得先把这个坐标系立起来。这篇文章有两个判断:

  1. 「记忆」是个统称,至少包含五个层级——而且它们该不该常驻,答案分成三种。所以「该常驻还是该去查」这个争论,本身问错了问题。
  2. 记忆系统的质量上限由写入路径决定,不是由检索决定。 检索只能把已经写对的东西找回来。

一、把「记忆」拆成五个层级

绝大多数关于记忆的设计混乱,源头是把五件不同的事都叫「记忆」。它们多久变一次、有多确定、搞砸了会怎样,完全不是一回事:

层 装什么 多久变一次 有多确定 主要会怎么坏 该常驻还是该去查
L1 身份与偏好 我是谁、说话习惯、技术栈偏好 几个月–几年 人明说的就高,系统猜的就低 过时了、或者被猜错了还固化下来 常在场(但要小)
L2 约束与纪律 禁令、「必须遵守」的规矩 几乎不变 必须是人说的,不能靠猜 漏用 无条件在场
L3 事实与教训 环境怪癖、工具坑、项目约定 几周–几个月 中,而且会随环境变 重复、过时、搜不到 去查
L4 事件流 会话历史、当时为什么这么决定 只增不减 高(是记录),但会过时 被淹没、搜不到 去查(优先按时间点找)
L5 流程 可以复用的步骤、技能 几个月 中,用过几次才知道准不准 越堆越多、互相抢位置 先列目录,用到再加载

先记住一个例子,后面几节还会回到它。

我跟助手说过不止一次:我电脑上只装了 pnpm,别用 npm。
(pnpm 和 npm 是两个功能差不多的工具,用来装代码依赖——混着用会把项目搞乱。)

就这么一句话,其实躺在五个层上:

  • 「我习惯用 pnpm」——L1 身份与偏好
  • 「别用 npm」——L2 约束与纪律
  • 「这台机器的 Node 版本旧,装新版会报错」——L3 事实与教训
  • 「上次试 npm,锁文件被改了,回滚了半天」——L4 事件流
  • 「这个项目的发布流程」——L5 流程

五条都和 npm 有关,但它们该被对待的方式完全不同。这就是这篇文章要讲的事。

区分这五层的,不是「存了什么」,而是三件事:它多久变一次、你有多确定它是对的、它坏起来有多麻烦——表里中间那三列说的就是这三件事,而它们决定了最右边那一列。

这也是为什么「记忆能力」这个说法帮不上忙:「能力」是对结果的期待,而上面这三件事是能直接观察、能直接调的。

从这张表能读出两条方向相反的规律:

  • 变得越快、越不确定的东西,越不能常驻。(L3、L4)这是成本律:摆在常驻位置上的东西,每聊一轮都要重新付一次钱。
  • 越接近「禁令」的东西,越必须常驻。(L2)这是可靠性律:禁令漏用一次就是事故。

这两条在同一层上会打架,所以:

不存在「最优的常驻方案」,只存在分层。

这也是为什么业界在「该塞进去还是该去查」上永远吵不出结论——Hermes Agent 选了「固定约 3,600 字符 + 冻结快照」,而另一派的做法彻底相反:一条都不塞,全靠去查。作为整体形态,两者都不是错的,只是同一根成本曲线上的两个不同位置。

顺带说一下范围。L5 那一层(技能与流程)我之前已经写过:《从对话到 Skills》讲的是它的抽象演进,《知识复利:让 AI 维护一个会自己生长的知识库》讲的是知识库怎么自己生长。这篇只处理剩下的四层——尤其是最容易被忽略的那个问题:存进去的东西怎么不烂掉。

换个角度看,这五层还能归成三类:

  • 配置(L1、L2):人的状态——我是谁、什么不能做
  • 记录(L3、L4):对世界的观察——发生过什么
  • 流程(L5):做法本身——一件事该怎么做

分成这三类不是为了整齐,而是因为谁有权写它们,完全不一样:约束不该由被约束的那个人自己写,而记录应该尽量自动抓。这一条会在第四节直接推出一条结论。

二、五问:拿到任何实现都能问的问题

不管看的是别人的产品还是自己的系统,对每一层都该问这五个问题。空的格子就是设计缺失:

# 问题 为什么要问
Q1 谁在什么时候写? 决定这东西长得多快。「每轮后台自己偷偷存一次」和「用户在界面上点了保存才写」,是两个完全不同的系统
Q2 同一件事被写第二遍时,凭什么认出「这是同一条」? 决定重复率。没有这个判据,库里必然越积越多近义条目
Q3 靠什么把它找回来? 按字面 / 按意思 / 按关系 / 按时间 决定「运气最差时能找回多少」,也反过来决定 Q2 那边的压力有多大
Q4 它怎么失效? 删掉 / 标记作废 / 慢慢降权 / 永不处理 决定这东西半年后还能不能用,也是最常被整节漏掉的一格
Q5 每次要花多少成本带着它? 常驻 vs 用到再取,谁付这笔钱 决定「记得越多模型越笨」会不会发生

Q2 和 Q3 之间那条箭头,是后面要展开的关键因果链。

三、成本花在哪儿:五种给记忆安排位置的方式

第一节那个「常驻还是去查」的二分,拆开就是下面这五种:

形态 怎么做的 成本花在哪儿
冻结快照 随系统提示在对话开始时整块塞进去一次,之后不再变,会话结束就重来 每轮固定一笔
独立预算块 系统单独划出来的一小块预算,独立于会话存在,可以随时增删 每轮一小笔固定开销
目录 + 按需 只把名字和一句话说明放进去 每轮一笔中等开销(条目越多越贵)
纯检索 完全不塞,全靠模型自己想起来去查 查一次要等一会儿,而且可能压根想不起来查
不做记忆(对照基线) 没有记忆 0(但每次都得重新交代背景)

前两种都是「永远在场」,区别在那笔预算由谁控制:冻结快照是系统提示的一部分,会话一开始就定型,中途改不动;独立预算块是另一笔独立的小额度,能随时增删。

Hermes Agent 的官方文档给了一组很诚实的一手数字:MEMORY.md 上限 2,200 字符(约 800 tokens),USER.md 上限 1,375 字符(约 500 tokens),加起来约 1,300 tokens 的固定开销;技能库分三级逐步展开,第一级(只列名字和一句话说明)大约 3k tokens。

换句话说,常驻是有价格的,价格就是上下文额度——这个视角我在《把上下文当成一等资源》里展开过。区别在于:上下文是一次性的,而记忆是每次开新对话都要重新付的那一笔。

其中有一个细节我一开始读反了,值得单说。Hermes 的做法是冻结的——对话中途新写进去的记忆,这一轮对话的系统提示里不会出现。文档把原因写得很直白:这个快照在对话开始时抓一次,中途绝不改动,这是故意的,为的是保住模型的「前缀缓存」,省掉重复计算。(原文见文末附录。)

这不是实现偷懒,是一个成本上的取舍。但它带来两个可预期的后果:写进去的东西不会当场出现在系统提示里;而且系统提示里只要有一条错事实,这一轮对话里就没有机会自我纠正。

不过这里要补一个限定,否则容易读过头。Hermes 的文档紧接着还有一句:磁盘上的记忆是立刻写入的,agent 调工具时读到的也始终是最新值——看不见的只是「系统提示里那一份」。所以准确的说法是:冻结快照没有自动纠错,纠错要靠 agent 自己想起来去查。

推论是:冻结快照要求上游的写入质量足够高,因为它少了自动纠错那一环。而纯检索恰好相反——每次都能读到最新值,但每次都指望模型自己想起来去查。两种形态的坏法正好错开,不是互相竞争。

还有一个必须对照的基线:不做记忆。记忆不是免费的,它要一次性建设,之后还要一直交维护税(合并、作废、评测、隐私审计),而且它会主动造成伤害——过时的事实被当成当前事实、错误推断被固化下来反复使用、上下文被无关条目污染。如果一个场景里「重新交代背景」的成本低于「维护记忆」的成本,就不该做记忆。

四、写入路径才是主战场

如果一个记忆系统的质量差,先怀疑写入路径;只有当写入质量可验证时,才有资格谈检索能力不足。

回到 pnpm 那个例子:助手第一次知道「我不用 npm」,可能是从我说的一句话里学到的。可如果第二次、第三次它还要再问一遍,问题就不在检索——是那句话压根没被写对。

消解重复有四条路,每条都有价格:

方式 怎么做的 代价 会怎么坏
写之前查重(但不改) 写之前先看看有没有;被推翻的旧条目留着 每次写入多查一次 + 多抽一次 新旧两条并存 → 查的时候可能返回已经过时的那条,冲突只能甩给上层应用去处理
写的时候判定(自动改写) 先抽出候选,和已有的比,自动把旧的作废 每次写入多跑一次模型,贵到需要专门优化 判错了会悄悄累积;错误合并会丢信息,而且不可逆
写满了才合并 满了才触发,让模型自己合并腾地方 偶发、突然、很贵 平时完全不约束增长——没写满之前一直只增不减
靠提示词里的纪律 在 prompt 里规定「写之前先搜」 ≈0 会自己失效(下面细说)

这四条路最后都会落到一个数字上:重复率。而任何「重复率」数字,先问它的口径。 用单一相似度阈值判定重复时有个陷阱:A 和 B 像、B 和 C 像,程序就会把 A 和 C 并进同一簇,哪怕它俩毫无关系。于是同一份数据,阈值松一点可以报出「一半都是重复」,紧一点又会说「几乎没有重复」。这不是数据的性质,是方法的性质——任何报告重复率的系统,都得说明它用了什么聚类方法、这个方法会在哪里失效。

两条代价是有原始证据的:Zep 的官方博客说每次写入都做图构建会消耗推理成本,他们「在过去六个月把图构建的 LLM 推理成本降低了 40%」;Mem0 的官方文档则把「去掉重复事实」列成抽取流水线里单独的一步(Deduplication and embedding: redundant facts are removed),并说明一旦关掉推断(infer=False),存进去的就是原样内容,同一件事会存好几份。

把「抽事实 + 做判定」放在写入这一步,付出的不是 token,而是写入延迟——而写入延迟用户是直接能感觉到的。

为什么「写之前先搜一下」不能当主机制

这是整篇文章里我最想强调的一条,它有完整的因果链:

1
2
3
4
记忆库里的重复条目
← 由「搜不到就以为不存在,于是再写一条」产生
← 由检索能力不足导致(按字面搜搜不到近义说法、也搜不到换个语言的表达)
← 而「先搜后写」这条纪律恰恰依赖这个检索

你想用「写之前先搜一下」来防止重复——可搜不搜得到,正是你不放心的那个环节。这是个死循环。 检索能力不够的时候,这条纪律不是报错,而是「搜了一下没找到,那就新增一条」,看起来完全正常。它是悄悄失效的。

所以:提示纪律只能当兜底。凡是能用机制挡住的,永远比靠自觉可靠。

在这一条上要诚实一点:并不是所有系统都没做检查。Mem0 的官方文档里,「写之前先看看有没有」是流程的第一步(Context lookup),代价也摆在明处——每次写入多一次查重和抽取,也就是前面说的那笔写入延迟。所以真正的分野不是「有没有查」,而是「查重这件事由谁付账」: 靠提示词纪律,账只会在重复率上慢慢还;靠结构性查重,账当场付清。前者便宜但不可靠,后者可靠但不免费。

这两件事不矛盾:Mem0 查的是「别再存一份一模一样的」,不是「把旧的改掉」。 它的自动路径是叠加式的——官方原话是「可以存下新事实而不悄悄改写旧的那条」,更正和删除要靠显式 API。二手综述经常把它说成「写入时判定 ADD/UPDATE/DELETE/NOOP」那一路,方向正好说反。

有意思的是,三家互不相干的实现在「谁不能写」上收敛了

翻文档时我发现一个信号:几个独立实现都有某种「结构上就写不进去」的存储。

实现 机制
Hermes 技能写入先暂存,人审过了才生效(skills.write_approval)
Letta 只读 memory block(system_config 由外部 API 更新,agent 只能读)
Mem0 标了 immutable 的条目不能就地更新,只能删掉重建

不同设计在同一个点上收敛,是这个模式确实必要的强证据。由此得到一条分层原则:

越是「我是谁」「不许做什么」这类东西(L1、L2),越该用机制去挡——不可写、人来审、永远在场;
越是「发生过什么」这类东西(L3、L4),越该自动化——写入时判定、合并、检索。

反过来就是两类典型浪费:把 L3 事实的收集做成人来审(浪费人的注意力)、把 L2 约束交给「记得去查」(这是设计缺陷,不是实现缺陷)。

五、检索的上限,会反向决定写入质量

这一节不是要翻前面的案。检索弱不会让写好的东西变差,它只是逼你把本来该在检索端做的事挪到写入端——两条路总得走一条。

检索能力是分级的,没有哪一级是普遍最优的:

级别 能找回来 找不回来
按字面(子串 / BM25) 用词一样或者大体重合的 换了语言、换了近义词、说法更抽象了
按意思(向量) 近义的说法、跨语言的说法 精确的标识符、冷门专名、否定和例外
按关系 / 按时间(图、时序) 跳几层的关系、时间上的推理、「当时是不是这样」 得先有抽取质量,schema 也得设计对
人直接指定 100% 前提是人得知道库里有什么

用 pnpm 那个例子看一眼就明白:你搜的是「依赖管理工具」,库里那条写的是「pnpm」——按字面找,这两个词一个字都不重合。 于是系统认为你没记过这件事,转身又问了你一遍。

如果索引是按 3 个字符的滑动窗口切的(trigram),它有个容易被忽略的硬边界:1–2 个字的查询根本匹配不上——而中文里最常用的搜索词,恰恰就是两个字。

按字面找还有两个结构性缺口:

  1. 跨语言断裂:同一件事用中英各记一遍,两种说法各只能命中一半。这不是模型的问题,是索引的问题。
  2. 搜什么词是模型猜的:一条都搜不到时它不会报错,模型只会认为「没有这条记忆」,然后可能再写一条。

第二条,正是前面那个死循环的另一面——检索弱 → 搜不到已有的近义条目 → 判定「不存在」→ 新增一条 → 重复越积越多,而写入纪律就在这个环节悄悄失效。

**设计推论:**如果只能用弱检索,就把语义能力挪到写入那一边——在条目里显式写上同义词、别名、容易拼错的写法(提高字面命中率);强制统一语言,禁止同一件事中英各记一条;写入时就把「这条属于哪个对象/主题」结构化,而不是留给检索去猜。

原则:把能力放在更强的那一端。 检索弱,就让写入端多干活。

六、生命周期:最容易整节被忘掉的一个环节

多数系统的策略是「永不删除」。而永不删除会悄悄毁掉检索质量:库里被陈旧条目一点点填满,能找回的比例随条目总数增加而下降。 只要一直写、却从来不淘汰,这个结果就是确定的。

有四件事必须分开,它们经常被混成一句「清理」:

  • 降权:不删,只是把优先级调低。触发条件是长期没人用。要保住的是「可以反悔」。
  • 作废:标一句「曾经为真,现在不成立了」。触发条件是事实被推翻。必须保住的是这段历史,以及它为什么被推翻。
  • 遗忘:真的删掉。触发条件是隐私、合规、或者明确记错了。必须一并覆盖镜像、索引、备份、快照——否则等于没删。
  • 升级:从「用到才查」提到「永远在场」,或者从一条事实升成一套流程。触发条件是反复被用到。要守住的是预算上限。

回到 pnpm:「我现在改用 pnpm 了」这条事实成立之后,「以前用 npm」也不该被删掉——它正是「为什么别让我再碰 npm」的答案。

作废 ≠ 删除。 一条事实不作数了,别急着删——删掉它,会让助手在未来重新犯同一个错,因为它看不到「这条路走不通」。正确的做法是留着它、标上「已作废」,等每次要用的时候再判断它还算不算数。两个原始实例,说的是同一件事:

  • Zep:原文大意是「一条被作废的事实会连同它的完整历史留在图里,而不是被删掉」,而且那条边上的记录只增不覆盖(第一次抽取 + 后续确认 + 推翻)。
  • Mem0:用 expiration_date 表达过期——过期条目从 search 和 get_all 里藏起来了,但按 ID 仍然取得到,数据并没有被删。

那条被忽略的「升级」铁轨

这里有个坏习惯值得单拎出来:信号记下来了,却没人用它。 记录每条记忆被用过几次、最后一次是什么时候,成本很低,所以不少实现都做了;但这些数据最后往往只被拿来生成一份「最常被用到 / 从来没人用」的清单给人看——没有自动降权、没有淘汰、也没有升级。数据记了,决策没有发生。

而升级恰恰是少见的能同时做到「准」和「省」的机制:

1
2
3
经常被用到 + 很确定   →  升级为常驻(放进 L1/L2 的固定预算)
长期没人用 → 降权 / 归档到冷库
被推翻 → 标作废,并从常驻预算里移除

没有这条铁轨,「常驻还是去查」就只能二选一,而两个极端都有已知的翻车方式。

七、行为层:何时记、何时用、何时忘、何时问

前面的都是机制。但「像不像一个合格的助手」,机制只决定一部分。

什么时候该记——三个条件同时满足才值得写:重复出现(第二次出现才记,第一次往往只是个例——比如我第一次说「我不喜欢这个写法」可能只是随口一句,第二次说才值得写下来)、会改变以后的做法(不是「有意思」,是「会影响我下次怎么做」)、跨对话还有效。不该记的:能从代码仓库里推出来的(会腐坏,源头一改,记忆就变成误导)、有更权威来源而且会变的(文档、API 签名)、跟隐私有关但没有明确用途的。

什么时候该用——L2 约束无条件在场,不能靠去查;L3 事实在用之前查一下。还有一条容易被忽略:记忆是背景资料,不是命令。 必须做来源标注和围栏(「以下不是新的用户输入」),否则记忆会变成提示注入的载体,也会让模型把「曾经看到过的事」当成「必须执行的指令」。

什么时候该忘——被推翻就标作废;有过期语义的按时间降权;被更权威的来源取代就降级成历史。(为什么是标作废而不是删,第六节说过。)

什么时候该问——可以拍成一个 2×2:

影响小 影响大
没把握 先做,事后说一声 问
有把握 直接做 直接做

但有一条铁律:永远不要为了「记录」而打断用户。 记忆的价值来自减少打扰,一个总在确认「要不要记住这个」的助手是净负值。

最后是这篇文章里我最想记住的一句话:

助手感不来自「记得多」,而来自「该在的时候在」。

由此得到一个不对称的优先级:漏用一次禁令的伤害,通常大于多记十条无用事实。 所以资源该优先投给 L1/L2 的「保证在场」(预算、可靠性、不可写性、人审),而不是投给 L3 的容量和找回率。这也解释了为什么纯检索式系统会给人「它好像忘了我是谁」的感觉——它把最重要的 L2,交给了最不可靠的那条路径。

八、反例:每条原则的过度应用

一个只有正向原则的框架会诱发过度工程。每条原则都配一个反向代价:

原则 用过头会怎样
分层 五层变十五层,维护成本超过收益
写入时判定 每次写入多一次模型调用;合并错了没有回滚接口,两条并成一条就拆不开了
常驻注入 预算吃掉上下文,模型变笨;而且错事实当场改不掉
纯检索 L2 漏用;一条都搜不到时悄无声息
全自动化 模型自我授权:把猜到的偏好写成必须遵守的规矩
人审 注意力被低频事实耗尽;而且审错了层(去审 L3 事实)
永不删除 检索空间被陈旧条目填满;隐私与合规风险
做记忆这件事本身 维护税超过「重新交代背景」的成本

九、怎么用:拿这五问套一遍

这套框架的用法不是「照着搭」,是「拿去审」。找任意一个想做记忆的系统——别人的产品、自己上一版的设计、或者一个还没动手的想法——对每一层把 Q1–Q5 问一遍,然后看哪些格子是空的。

三个最容易空的地方,也最值得优先填:

  1. Q2「凭什么认出是同一条」:没有它,重复率就是一个随机数。
  2. Q4 失效策略:没有它,系统会缓慢地变成一堆陈年噪音。
  3. L2 那一整行:如果 L2 的 Q5 写的是「去查」,那它的可靠性上限就被检索能力锁死了——而这是一条不该被依赖的路径。

再补三个必答问题:

  • 哪一层是机制保证的,哪一层只靠自觉?
  • 使用信号被记下来了吗?被用上了吗?
  • 「遗忘」覆盖了镜像、索引、备份吗?

怎么知道自己做对了

现在主流的几个「记忆考试」——LoCoMo、LongMemEval、BEAM——都是拿一段很长的多轮对话去问系统:关于这段对话,你能不能回答出问题。它们测的是「能不能把说过的事回忆出来」,不是「能不能用记住的东西把活干对」,这是两件事,别拿前者的分数当后者的保证。

更根本的原因是:记忆质量取决于你的写入分布(你记什么、怎么措辞、用什么语言)和你的查询分布(你会问什么、模型会猜什么词)——这两件事没人能替你估。所以探针只能自己搭,好在不用复杂:按五层各造一批查询(每层至少 10 条;L5 只列目录,不算在这篇的范围内),标清楚每条「期望命中哪一条记忆」以及「是不是必须命中」,然后分层设及格线——L2 约束要求 100% 在场,达不到就是设计失败,不是调参数的问题;L3 事实能找回多少可以宽松一点,但要盯住有多少条压根一次都没被找回来。

最后一件事:先证明记忆有必要。 拿「不做记忆、每次重新交代」当基线——如果维护税高于重新交代的成本,停下来比做下去更划算。

十、还没想清楚的

诚实列一下这个框架没解决的问题,免得它显得比实际更完整:

  1. 偏好的「把握」怎么量化? 「用户说过」和「系统观察到」的确定程度不一样,这个差别怎么进入决策。
  2. 主动性的时机判据:什么时候该主动用记忆,而不等被问?这和「什么时候该问」是同一枚硬币,但缺少可操作的标准。
  3. 记忆污染的下游检测:一条错误记忆可能在很久以后才造成危害,怎么尽早发现。
  4. 跨设备 / 跨 agent 的记忆一致性:同一件事多端写入的冲突怎么消解。
  5. 作废的传染性:A 被推翻了,依赖 A 的 B、C 要不要一起作废。
  6. 记忆的害处怎么度量。 「记忆是有用的」在几乎所有讨论里都是默认前提,这套框架里的每条取舍也都建立在「利大于弊」上——但它带来的坏处(污染、把错误固化、上下文被无关条目占掉)一直没有人正经测过。

来源