Skip to content

1. 新会话开局该带哪几条 ​

一个用了一年的项目记忆会有几百条:用户的协作偏好、若干次发布踩过的坑、几个外部系统的位置、几次失败的调试路径。新会话开始时,全都带上会挤占窗口,只带索引又可能漏掉关键条。

这个问题有两个维度,通常只被讨论了第一个:取哪几条,以及什么时候付成本。第二个维度往往更影响结果——同样取五条,全放在请求开头每轮发送,与只在命中时展开,Token 花费差一个数量级。


2. 注入成本的结构 ​

先把成本算清楚,后面讨论策略才有依据。

设库中有 \(n\) 条记忆,索引每行 \(\ell\) 字节,单条正文 \(m\) 字节,摘要体积 \(\sigma\) 字节,每轮取回 \(k\) 条,任务共 \(t\) 轮。三种常驻方式的注入总量 \(c\) 为:

\[c = \begin{cases} t\,n\,\ell & \text{索引常驻} \\[4pt] t\,\sigma & \text{摘要常驻} \\[4pt] t\,n\,\ell + t\,k\,m & \text{按需挑选} \end{cases} \]

第四种是引用快照,它的大小由被引用会话的体积决定,没有稳定估算方式,后面单独讨论。

代入一组具体数值:\(n = 400\),\(\ell = 60\) 字节,\(m = 3\) KB,\(k = 3\),\(t = 20\),\(\sigma = 10\) KB。

方式计算总量说明
索引常驻\(20 \times 400 \times 60\)480 KB与库大小成正比
摘要常驻\(20 \times 10\)200 KB与库大小无关
按需挑选\(480 + 20 \times 3 \times 3\)660 KB索引加命中项
引用快照视引用内容不可估由用户行为决定

这组数字给出几个反直觉的结论。

第一,在 \(n = 400\) 这个规模上,按需挑选的总量高于索引常驻。原因是索引仍然要注入(否则无从挑选),而命中项额外增加 180 KB。只有 \(m\) 很小或 \(k\) 很大时,按需才明显更省。

第二,按需挑选的真正收益不在总量,在每轮的增量。索引常驻每轮固定 24 KB,按需在未命中的轮次只付 9 KB(命中三条时)。对缓存友好的系统,稳定的 24 KB 比波动的 9 到 18 KB 更省——因为稳定前缀能命中缓存。

第三,摘要常驻的成本与库大小无关,这是它最大的优势。库从 400 条涨到 4000 条,摘要在 10 KB 上限内仍然是 10 KB,索引会涨十倍。

这三条合起来解释了一个现象:为什么 Codex 选摘要常驻而 Claude Code 选索引常驻。Claude Code 的场景是单项目长期使用,\(n\) 有上限(200 行);Codex 的场景是跨项目通用,\(n\) 可能很大,必须有与规模无关的常驻成本。

2.1 缓存对成本的重构 ​

上面的算式忽略了缓存。设每轮不变的部分为 \(f\),变化的部分为 \(v\),缓存命中价格为正常价格的 \(\alpha\),则实际成本变为:

\[c' = \alpha\,f + v \]

这个式子改变了选型逻辑。索引常驻的 480 KB 里,绝大部分是每轮不变的 \(f\),按 \(\alpha = 0.1\) 算,实际成本降到 48 KB 加变化部分。按需挑选的索引部分同样享受这个折扣,但命中项是变化的,计在 \(v\) 里,不打折。

所以真实成本排序与表面排序不同:

方式表面成本缓存后成本(\(\alpha = 0.1\))
索引常驻480 KB48 KB
摘要常驻200 KB20 KB
按需挑选660 KB48 KB + 180 KB = 228 KB
引用快照不可估不可估

按需挑选在缓存上吃了亏:它的命中项每轮都变,破坏了前缀稳定性。这解释了一个实现细节——Claude Code 把按需选中的记忆作为附件注入,附件位于消息列表的特定位置,不插进系统提示;这样至少不会破坏最前面的稳定前缀。


3. 取回链路 ​

一次记忆取回通常经过六步。

  1. 生成查询。查询不完全等于用户的话,通常要带上任务类型与当前工作目录。
  2. 按作用域过滤。用户、项目、类型、权限。这一步必须在召回之前做,先召回再过滤会把越权内容读进内存。
  3. 多路召回。语义相似度找"意思接近",全文索引找"精确词命中"。
  4. 重排。综合相似度、置信度、时效、来源可信度。
  5. 去重与冲突检测。同一事实的多条记忆合并,互相矛盾的不进入上下文。
  6. 压缩成片段。带上类型标签与来源标记,长度受预算限制。

第三步与第四步的分工常被忽视。召回负责"不漏",重排负责"排序准"。只做召回不做重排时,一条三年前的相似条目和一条昨天的相似条目会并列返回,模型无法判断哪条更该信。

取回还要带类型。同样是"用户偏好中文",作为偏好注入可以改变输出语言,作为事实注入则可能被当作约束误用。片段前面标注类型,模型才有依据决定如何使用。

3.1 重排的打分 ​

重排要综合四个信号:相似度、置信度、时效、来源可信度。四个信号不能简单加权求和,因为它们的量纲与含义不同。

相似度是连续值,通常落在零到一之间。置信度是写入时给出的估计,也在零到一之间,但含义完全不同——它表示"这条记忆当时是否被确认",不表示"现在是否适用"。时效是时间差,需要先转成分数。来源可信度取决于来源类型:用户显式陈述高于模型推断,带文件与命令的观察高于印象式归纳。

一个可用的两段式设计:先用硬条件过滤,再对剩余项排序。

text
过滤:
  作用域匹配(用户、项目、权限)
  未过期,或过期但最近验证时间在阈值内
  无未解决的冲突状态
  置信度高于下限

排序:
  score = sim × 0.5 + fresh × 0.3 + src × 0.2
  最终权重 = score × confidence

置信度取乘数,理由是它表示"这条能不能用",与"这条有多相关"是两件事。加数会让一条高相似但低置信的记忆排到前面,乘数会让它自然沉底。

时效分可以写成指数衰减:

\[f(d) = \exp\left(-\frac{d}{\tau}\right) \]

其中 \(d\) 是距上次验证的天数,\(\tau\) 是时间常数。取 \(\tau = 90\) 天时,一周前的记忆得 0.92,一个月的得 0.72,三个月的得 0.37,半年的得 0.13。这个曲线在"一个月内"区间内的差异不大,在"超过三个月"之后迅速衰减——符合"事实可能变化"的直觉判断。

3.2 并列与互斥的判定 ​

重排之后还有一步:判断取回的多条记忆之间是并列还是互斥。

以"这个项目的部署命令是什么"为例。库里有一条三年前的"部署用 npm run deploy",一条三个月前的"部署改到 bash deploy.sh",一条上周的"部署前必须先跑迁移"。三条都含"部署",都会被召回。

前两条是互斥:同一个属性(部署命令)上有两个不同取值。第三条是并列:它说的是另一个属性(前置条件),与前两条可以同时成立。

两类关系的处理方式相反:

  • 并列:全部保留,按相关度排序;
  • 互斥:只能保留一条,或全部标为待确认。

判定依据是记忆内容的 subject 与 attribute,文本相似度在这里不够用——前两条相似度极高但必须二选一,第三条相似度也高但必须保留。同一个主体上的不同属性是并列,同一个属性上的不同取值是互斥。

这条判定对写入有反要求:写入时必须带上主体与属性字段。没有这两个字段,取回阶段只能靠模型猜,而猜测在多条并存时不稳定。

3.3 注入位置的差别 ​

同样一段记忆,放在不同位置,效果不同。

放在系统提示里时,它表现得像规则,模型更倾向于无条件遵守;放在用户消息里时,它表现得像背景信息,模型会结合当前对话判断。这两者的差别在偏好类记忆上最明显:一条"用户偏好中文"放在系统提示里,模型会全程中文;放在用户消息里,模型可能只在那一轮用中文。

Codex 把记忆作为开发者角色的指令注入,单片段 8,900 字节截断。DeepSeek Harness 把指令链渲染成一条持久用户消息。Claude Code 把索引放进系统提示读取的项目文件清单,把按需选中的记忆作为附件注入。

三种位置对应三种强度:开发者指令介于系统提示与用户消息之间,既有规则意味又允许对话覆盖;持久用户消息更弱,模型会把它当作背景;系统提示最强,但会占用最稳定的前缀,改动会破坏缓存。

选择位置时要考虑两件事:这条记忆需不需要被覆盖?如果它是硬性约定(不许提交密钥、必须跑测试),放得越强越好;如果它是偏好(喜欢表格、偏好简洁),放太强会让模型在该变通时不知变通。


4. 四种注入方式 ​

方式常驻成本触发时机失误表现
索引常驻索引全文,约 25 KB每轮索引膨胀后挤占窗口,条数上限被撑破
摘要常驻摘要全文,约 10 KB每轮摘要为了塞进预算而过度压缩,细节全丢
按需挑选索引 + 被选中的几份请求前由模型或规则选择选错条目,或漏选关键条
引用快照被引用会话的体积用户显式引用快照冻结后不再更新,与当前事实脱节

三种主流实现最后都落在"轻量常驻 + 按需展开",但细节不同。

Claude Code 默认把 MEMORY.md 索引整段注入,索引上限 200 行、25,000 字节。索引每条只有一行,格式是标题加一句话钩子,指向一个主题文件。当索引接近上限时,提示词要求智能体合并细节或把内容移回主题文件。较新的实现加了一条按需路径:用一次模型调用从文件清单里选出最多 5 份相关记忆,作为附件注入,索引本身不再常驻。

Codex 常驻的是摘要,索引按需检索。memory_summary.md 分四段——用户画像、用户偏好、通用技巧、记忆索引——整体控制在 10,000 字节以内,首行固定为版本标记。更详细的内容放在 MEMORY.md 这份可 grep 的手册里,摘要中只给路由提示。读取侧以开发者指令的形式注入,单个片段超过 8,900 字节就截断,并提示模型去看文件。

DeepSeek Harness 没有记忆库,常驻的是工作区指令链。它按"用户全局 → 共享 agents 根 → 项目根到工作目录"的顺序渲染,预算 65,536 字节。超出预算时先整份丢弃最宽泛的文件,再截断最具体的文件,并生成一条可见通知,指名哪些文件被省略或截断。

三种做法对应三种判断:索引是"每条都值得知道位置",摘要是"少量结论值得每轮看到",指令链是"约定每轮都要遵守"。判断标准只有一条——这条信息是不是每一轮都要用。

4.1 按需挑选的召回质量 ​

按需路径的核心是一次模型调用,它的质量直接决定注入质量。Claude Code 的选择提示词有几条约束值得看:

  • 最多选 5 份;
  • 只在"确定有用"时选,不确定就不选,可以返回空列表;
  • 排除索引本身(已在上下文里);
  • 排除本轮之前已经展示过的;
  • 如果提供了最近使用的工具列表,不要选那些工具的用法文档——模型正在用它们,已经知道怎么用;但要选那些工具的注意事项与已知问题,因为正在使用正是这些问题最相关的时候。

最后一条是一个精细的判断:它区分了"参考文档"与"警告"两类内容。前者在模型正在使用该工具时是冗余的,后者恰好相反。

这条约束的代价是选择器需要知道本轮用过哪些工具。这要求选择调用拿到额外的运行时状态,而不能只给一个查询字符串。

按需路径的失效模式有两种:漏选和错选。漏选的代价通常更大,因为模型不知道有这个东西存在;错选的代价是浪费 Token 与注意力。所以提示词倾向于"宁可少选"——这与直觉相反,但对记忆场景是对的:模型至少还能通过文件工具自己去翻,而错误信息会主动误导。


5. 渐进披露 ​

"常驻轻量、按需展开"这件事,在多个系统里以不同名字出现,本质是同一个结构。

第一层是索引或摘要,每轮可见,只放"有什么"和"在哪里"。第二层是主题文件,命中后才读,放"具体是什么"。第三层是原始轨迹,只在核对与追溯时读,放"当时到底发生了什么"。

这三个层次与技能系统的加载方式、与知识库的分层摘要(一句话摘要、概览、全文)是同构的。原因也相同:模型能吸收的上下文有限,把预算花在"先判断相关性"上,比花在"携带全部内容"上收益高。

5.1 三层之间的传递损耗 ​

每一层展开都有信息损耗,理解损耗发生在哪里才能设计好索引。

索引层的损耗最严重:一条记忆被压缩成一行 60 到 150 字节的描述。这个描述如果只写"发布流程",后来者无法判断它是否包含自己要找的内容;如果写成"发布前必须跑迁移、发布后要验证健康检查",信息量就足够,但每条都这么写会撑破索引上限。

主题文件层的损耗小得多,但仍有取舍:完整正文可能几 KB,而注入预算有限,所以要截断。截断的位置很关键——截在结论之前,模型只看到背景;截在来源之后,模型拿到了结论但无法核对。

原始轨迹层几乎没有损耗,但有访问成本:读一次转录要几千到几万 Token,而且多数内容与当前问题无关。

这三层损耗的分布决定了索引写作的原则:索引要写到"能判断相关性"的粒度,主题文件要保证"结论与来源完整",轨迹只在需要核对时读。

5.2 索引与主题的一致性 ​

三层结构有一个容易忽略的维护要求:索引与主题文件必须保持同步。索引指向一个已被删除的文件、或一个已被改名但索引未更新的文件,比没有索引更糟——模型会认为自己查过了。

这类不一致的产生有几种典型路径:

  • 整理时删除了主题文件,忘了删索引行;
  • 主题文件被改名,索引未更新;
  • 索引行的描述与主题文件内容脱节(内容改了,描述没改)。

第三种最隐蔽。索引说"这条讲测试流程",主题文件里已经改成"发布流程",模型按索引读到后会发现内容不符,但通常不会回头质疑索引。

防范办法是把检索引成一致性检查:每次整理时验证每个索引行指向的文件存在、且描述与内容关键词有重叠。这个检查很便宜,但很少有实现做。


6. 新鲜度 ​

记忆是某个时点的观察,不是当前状态。这句话需要写进注入的内容里,否则模型会把它当作当前事实。

Claude Code 的做法是给每条记忆附年龄。年龄用相对时间表示("今天""昨天""47 天前"),因为相对时间比时间戳更容易触发核对行为。超过一天的记忆附一句说明:

这条记忆是 47 天前的。记忆是某个时点的观察,不是当前状态——关于代码行为或文件位置的描述可能已经过时。在作为事实陈述前,请核对当前代码。

这段文字同时做了三件事:给出年龄、说明性质、要求核对。三件事里最容易漏的是第二件。只说"这条记忆很旧",模型可能理解为"仍然有效但陈旧";明确说"不是当前状态",模型才会去核对。

Codex 用了另一种表达:它要求模型对有漂移风险的事实做判断——漂移风险高且核对成本低时先核对;漂移风险高但核对成本高时,可以基于记忆回答,但必须说明这是记忆来源、可能过时,并提示可以刷新。这个分层比"一律核对"更实用,因为有些核对本身会打断用户。

6.1 为什么相对时间更有效 ​

这背后有一个具体机制:模型对绝对时间戳的处理依赖日期计算,而对相对时间短语的处理接近模式匹配。

一个 ISO 时间戳 "2027-01-15T10:20:00+08:00" 要被理解为"很久以前",模型需要先取当前时间、做减法、再判断差值是否显著。这三步都有出错空间,而且当前时间往往不在上下文里。

"47 天前"这个短语不需要计算——它直接给出了差值,模型只需判断 47 是否显著。同理,许多实现会在上下文里注入"今天是某年某月某日",目的是给相对时间一个参照点,让模型不必自己算差值。

这个观察有一个推广:任何需要模型做算术判断的信息,最好由程序算好再给。这与状态栏的设计原则一致——把归纳工作从模型侧移到程序侧。

6.2 陈旧提醒的副作用 ​

陈旧提醒不是没有代价的。一条每天都附"请核对"的记忆,会让模型频繁中断去核对,即使内容确实有效。

权衡的依据是核对成本与出错代价之比:

情况核对成本出错代价建议
涉及具体文件与函数低(读一次文件)高(改错文件)一律核对
涉及命令与配置低(读一次配置)中核对
涉及用户偏好无法核对低(用户会纠正)不核对,直接使用
涉及外部系统状态高(可能无法访问)中提示可能过时,由用户判断

第一行解释了 Claude Code 为什么专门写一条"记忆提到某个函数或开关,那是它写这条记忆时存在的证据"——涉及代码位置的断言是最典型的"低核对成本、高出错代价"。


7. 记忆与指令的区分 ​

注入时的另一个判断是"这算记忆还是算指令"。两者的处理方式不同:

维度记忆指令
谁写智能体或后台管线人
能否自动整理能不能
版本控制通常不在仓库内随仓库走
冲突裁决用户或来源核对评审
生效范围通常单个用户所有贡献者

把项目约定写进记忆,会让约定变成某个智能体的个人理解,且随时可能被"整理"掉;把用户偏好写成指令,会让每次改偏好都要动仓库。两种错位都会在几周后变成"这个系统怎么突然不听话了"。

一个具体的判据是:这条内容如果明天被删掉,谁会不高兴?只有当前用户不高兴的,是记忆;整个团队会不高兴的,是指令。

7.1 混合内容的处理 ​

实际内容常常是混合的。一条"这个项目用 pnpm,我习惯用 pnpm 的 workspace 功能"里,"项目用 pnpm"是指令级的事实,"我习惯 workspace"是个人偏好。

处理办法是拆开写入:前者进团队指令(或团队记忆),后者进私人记忆。拆开的判据是"是否对所有贡献者成立"。

不拆的后果是两边都错:写进指令会让一个偏好变成强制约定;写进记忆会让项目事实只对一个人生效,其他人仍然用错包管理器。


8. 取回失败的五种表现 ​

表现直接原因该在哪一层修
召回无关内容索引描述模糊,或查询没带任务上下文索引质量与查询构造
同一条重复出现写入时未做近似去重写入判据
取回两条互相矛盾的记忆冲突未在存储层标记整理与冲突处理
读到其他用户的记忆作用域过滤在召回之后取回链路顺序
引用了已失效的事实缺少有效期或最近验证时间存储字段与检索过滤

五种里有四种应该在写入与存储阶段修,只有第一种属于检索阶段。这说明记忆系统的质量问题大多在上游产生,在下游暴露。出现"怎么老是取回错的东西"时,先检查写入的索引描述是否足够区分,比调检索参数更有效。

下面把四种上游问题的具体成因说清,因为它们对应四种不同的修补动作。

同一条重复出现。 成因通常是两次任务都发现了同一事实,而第二次写入时没有检查已有条目。检查方式不能只靠文本完全相同。可靠的办法是按主体加属性做键:主体是项目、属性是包管理器,写入前先按这个键查一遍。

两条记忆互相矛盾。 成因是事实变了而旧条目没有标记。修补动作在存储层:每条记忆带替代字段,新条目声明替代哪些旧条目;整理时按这个字段把旧条目降权,取回时默认只取未被替代的。如果两条都没有声明,取回阶段就只能靠模型判断,而模型判断的结果不稳定。

读到其他用户的记忆。 成因是过滤顺序错了。这个错误特别隐蔽,因为正常的测试数据里只有一份用户,测不出来。修补动作是把它做成结构性约束:取回函数的签名里强制要求用户与项目参数,没有这两个参数就构造不出查询。

引用了已失效的事实。 成因是缺少有效期字段,或有效期没被检索使用。修补动作分两步:写入时给每条带验证时间,整理时更新;检索时把超过阈值且久未验证的条目标记为"需要核对",不直接过滤掉——直接过滤会让模型完全看不到相关线索,标记则保留了线索同时提示了风险。


9. 预算怎么算 ​

注入内容的大小决定它能带多少条。三类记忆的典型体积差别很大:

内容典型体积说明
索引一行40–80 字节标题加一句话钩子
摘要一段300–800 字节用户画像或偏好列表中的一段
主题文件一份2–8 KB一条记忆的完整正文与来源
原始轨迹一段5–50 KB需要核实时才读

按这个量级算,一份 25 KB 的索引大约能放 300 到 600 条指针;10 KB 的摘要只能放十几段,所以摘要必须只写最高频的结论。

按需挑选的预算算法不同。它按"选几份、每份多大"分配,不按"总量除以单条"分配。Claude Code 的按需路径限死最多 5 份,同时每份有字符上限,所以最坏情况是 5 乘单份上限。这个设计把不确定性压到了可预测的范围:无论索引膨胀到多大,注入的增量都有上界。

引用快照的预算最麻烦,因为被引用的会话体积不可控。DeepSeek Harness 的做法是先丢较早的非检查点消息,再缩短保留的文本;仍然不够就用外置存储保存全文并给出定位。这个顺序值得注意:先减内容再降精度,最后才动用外部存储,因为外部存储意味着模型要多一次读取。

9.1 预算与命中率的权衡 ​

取回条数 \(k\) 不是越大越好。设单条记忆对当前任务的边际价值随 \(k\) 递减,单条注入成本固定,则存在一个最优点。

一个简化的模型:第 \(i\) 条被取回的条目被实际使用的概率是 \(p_i\),单条消耗 \(c\) 成本,任务失败的总代价是 \(L\)。那么取回 \(k\) 条的净收益是:

\[g(k) = L\left(1 - \prod_{i=1}^{k}(1 - p_i)\right) - k\,c \]

这个式子的实用含义是:当 \(L\) 很大(任务失败代价高,例如生产事故排查)时,\(k\) 应该更大;当 \(L\) 很小(一次格式调整)时,\(k\) 应该更小。所以固定的 \(k\) 值在不同任务类型下都不是最优,按任务类型调 \(k\) 才有意义。

实践中这个结论表现为:调试类任务倾向多取几条(宁滥勿缺),格式类任务倾向少取(宁缺勿滥)。


10. 与技能加载的对照 ​

渐进披露不是记忆独有的做法,技能系统用的是同一套结构。把两者放在一起看,能看清哪些是通用原则、哪些是记忆特有的。

维度技能加载记忆注入
第一层技能名与一句话说明索引行或摘要
第二层技能的完整说明文件主题文件
第三层附带脚本、模板、示例原始轨迹与来源
谁决定展开模型判断任务是否匹配模型调用或规则筛选
展开失败的后果用了错误的技能,任务方向偏用错了记忆,结论基于过时事实
过期机制版本随仓库更新需要显式的时间与验证字段

差别在最后一行的两处。技能的过期由仓库更新自然解决——文件改了就是新版;记忆没有这种自然机制,必须显式记录时间并主动核对。这也是为什么记忆必须带年龄提示而技能不需要。

另一个差别是展开的判断依据。技能的匹配靠描述与任务语义的相似度,判断相对可靠;记忆的匹配还要考虑时效与冲突,判断更容易出错。这解释了为什么 Claude Code 的按需选择提示词反复强调"不确定就不要选,可以返回空列表"——在记忆场景里,漏选一条的代价通常小于错选一条。


11. 衡量取回质量 ​

取回环节的指标与写入环节不同:

指标含义
命中率需要记忆的任务中成功取回相关条目的比例
无效 Token 占比注入内容中未被模型使用的比例
重复说明次数用户被迫重新陈述同一件事的次数
注入延迟从开始取回到内容进入上下文的时间
陈旧引用率取回条目在核对时被证伪的比例

重要性排序上,重复说明次数排第一,因为它是用户能直接感知到的。命中率与陈旧引用率是诊断用的中间指标——命中率低要知道是没取回还是取回了没用,陈旧引用率高要回头看写入时的来源与验证时间。

采集这几个指标有一处实操困难:无法直接观测"模型是否用到了某条记忆"。可行的替代是看引用——如果系统要求模型在回答里标注依据的记忆,那么被引用的条目就是被使用的。这需要把引用做成机制,不能靠模型自觉。

Codex 就是这么做的:当一次回答用到某份轨迹摘要,模型要在回复末尾附一个引用块,列出文件路径、行号与一句用途说明。摘要本身不参与引用,因为它是路由层,不承担证据职责。这个设计让"无效占比"变得可测:注入的条数与被引用的条数之比。

有一点需要注意:不要把注入量当作优化目标。多注入一些"可能有用"的内容,短期看不会出错,长期会让模型的注意力被稀释,也会让用户在看到上下文占用时产生疑虑。

用缓存后的成本衡量,这一点更明显。常驻内容按 \(\alpha\) 折扣计价,变化的命中项按全价计价,所以"多带一点"的真实开销取决于它落在 \(f\) 还是 \(v\) 里。稳定地多带 4 KB 索引,实际只多付 0.4 KB;每轮浮动地多带 4 KB 命中项,就是每轮多付 4 KB。