模型没有记忆。 它每次被调用都是一次全新的计算,上一句说过什么,它不知道——所谓「记得」, 是每次都把前面的话重新读一遍。 所以「记忆」在这套体系里不是一种能力,是一种组织方式:什么放窗口里、什么放文件里、 什么时候把哪一段搬进来。
上一章把四层架构摆出来了,从这一章开始一层一层往下讲。 记忆层是第一层,因为它最便宜、最常用,也最容易被写坏。 会话内上下文那套东西在前面讲循环时已经提过(第 1 章 1.1 节),这一节只补一件事: 为什么必须往外搬。
一、「记得」是重读,不是储存
先把一件事说清楚:模型这一侧没有任何状态。 你发过去的每一段文字,它算完就散了。 下一轮再问,它拿到的是一份新的输入——里面如果有上一轮的话,那是因为有人把那些话又贴了一遍。 所以「它记得预算三十万」这句话,准确的说法是「有人把『预算三十万』这几个字又送了一遍」。
这个区别听起来抠字眼,但它决定了后面所有做法的方向: 既然记忆靠重送,那问题就变成「送什么、送多少、什么时候送」。 整套记忆机制,本质是在回答这三个问题。
二、三种载体,三种代价
第一个载体是上下文窗口,也就是这一次对话里能看见的东西。 它最快,也最贵:每一轮都要重算一遍,而且窗口一满,最早的先被挤掉。 第二个载体是文件。写进 CLAUDE.md 或者某个笔记文件,下次会话开始时读回来。 它不丢,但每次读都要花 token。 第三个载体是外部检索。文件很多的时候,先按关键词或者向量找,只把相关的那几条捞回来。 它容量最大,代价是多一次查询,而且可能找不准。
演示里把三种都跑了一遍:五十轮对话、六件要说的事、窗口按 600 token 算。 只靠上下文的那一组,最后六件事一件不剩——因为滑窗按期丢,而最早说的恰恰最重要。 落盘的两组都保住了六件。区别在读回来的成本:全读 109 token,按需检索只花 22。
这三个数放一起看,结论很清楚:贵不在存,贵在每轮都要带。
三、为什么「窗口开大一点」不是答案
窗口从 8k 开到 200k,五十轮确实装得下了。但这条路走不远,两个原因。 第一个原因是长对话没有上限。装得下五十轮,装不下五百轮。而真实项目里的会话,恰恰是越到后面越长。 第二个原因更实在:窗口里塞的东西,每一轮都要重付一次钱。
装六条事实一百来 token,装五十轮闲话一千多。 后者的钱花在「它曾经说过什么」,而不是「它现在需要什么」。 还有一个隐性代价:窗口里无关内容越多,真正相关的那句越容易被淹没。 这不是玄学,是注意力机制本身的取舍——它要在所有内容之间分配权重。
四、什么不该记
既然窗口里的每一句都要付钱,那「该记什么」就有了一个反向判据: 模型能不能从仓库里自己看出来?能,就别写。 第一类不该记的是「读得出来的东西」:目录结构、函数签名、依赖版本。 这些让它自己 ls 一下、grep 一下就有,写进记忆只是把同一份信息存了两遍。 第二类是一次性的东西:这次只改 README、这次别动数据库。这些放会话里说,写进文件会污染下一次会话。
第三类是变化很快的东西:当前分支、最近的提交、昨天谁改了哪一行。写进文件当天就过期,不如让它现查。 该记的只有一类:猜不出来的约定。比如「金额一律用 Decimal」——这条从代码里看不出来, 因为代码里可能有一半在用 float。 再比如「不要改 migrations 下已提交的脚本」——这是流程约定,代码里没有痕迹。 还有业务口径,比如「结算金额含税,报价单不含税」,这种更是只有人知道。
按这个判据过一遍,通常能砍掉一半。砍完之后剩下的,才是真正值钱的记忆。
五、所以记忆的工程答案
一句话:把该记的搬到窗口外面,用的时候再取回来,窗口里只放这一刻要用的。 这个原则往下推,就长出了后面几节的全部内容。 项目级的规矩要每轮都在,所以做成 CLAUDE.md,写得越短越好(2.3 节)。
只跟某个目录有关的规矩,不该每轮都在,所以做成按路径加载(2.4 节)。 更大块的知识(比如公司的手册),连「每轮都在」都不该,所以做成技能,用到才读(第 3 章)。 要干一件噪声很大的活(比如读两百个文件),连读的过程都不该进主窗口, 所以派个子智能体去(第 4 章)。
四条路,一条原则。记住这条原则,比记住四种做法有用。
六、动手 Demo:关键事实在三种载体下的存活率
造一场五十轮的对话:前六轮陆陆续续交代六件要记住的事,后面四十四轮全是闲聊。 窗口按 600 token 算,超出就丢最旧的。 三种载体各跑一遍:只靠上下文、落盘后每轮全读、落盘后按需检索。 看最后还剩几件事,以及当轮的上下文有多重。
win, win_tok = [], 0
for q, a in turns:
win.append((q, a))
win_tok += tokens(q) + tokens(a)
while win_tok > 600 and win: # 窗口满了,丢最旧的
oq, oa = win.pop(0)
win_tok -= tokens(oq) + tokens(oa)
alive = [f for f in FACTS if f in u"".join(q + a for q, a in win)]
实跑输出:
--------------------------------------------------------------------
关键事实在三种载体下的存活率
--------------------------------------------------------------------
一场五十轮的对话,前六轮里陆陆续续交代了六件事。窗口按 600 token 算,超出就丢最旧的。
看最后还剩几条。
① 只靠上下文:窗口一满,最早的先没
--------------------------------------------------------------------
五十轮走完,窗口里剩 21 条对话,共 592 token。
六件事里没了 6 件:
× 预算三十万,含税
× 工期四十五天,从下月一号算
× 户外灯具防护等级不低于 IP65
× 验收分两次,先隐蔽工程再整体
× 甲方联系人张工,电话在合同附件里
× 合同编号 HT-2024-118
! 这是滑窗的必然结果:它按时间丢,不按重要性丢。
而那六件事恰恰是最早说的、也恰恰是后面四十轮反复要用到的。
② 换成写进文件:六条一发现就追加到 notes.md,101 字,109 token。
③ 要用的时候再取:这一轮问的是「验收怎么安排」,只把相关的一条捞回来。
· 验收分两次,先隐蔽工程再整体
三种载体的账
--------------------------------------------------------------------
| 载体 | 最后还在 | 当轮上下文 | 读回成本 | 风险 |
|---|---:|---:|---:|---|
| 只在上下文 | 0/6 | 592 | 0 | 滑窗丢 |
| 落盘,每次全读 | 6/6 | 701 | 109 | 不丢,但每轮都带 |
| 落盘,按需检索 | 6/6 | 614 | 22 | 不丢,可能检不到 |
! 三行的第三列差不多,差别全在「能不能找回来」上:第一种丢了 6 条,后两种一条不落。
而按需检索只多花了 22 token。
所以「记忆」这件事的工程答案不是「记得更多」,是「把该记的搬到窗口外面,用的时候再取回来」。
窗口里只放当前这一刻要用的。
为什么不能靠「窗口开大一点」
--------------------------------------------------------------------
窗口翻十倍,五十轮就能全装下。但对话再长十倍呢?装不下的那天还会来。
而且窗口是每轮都要重付的:装下六条事实只要一百来 token,装下五十轮闲话要一千多。
后者的钱花在了「它曾经说过什么」上,不是「它现在需要什么」上。
! 外置记忆不是为了绕开窗口限制,是为了【每一轮只为这一轮付钱】。
①五十轮走完,窗口里剩 21 条对话、592 token。六件事一件不剩。 这不是极端例子,是默认结果:滑窗按时间丢,而那六件事都在最早的六轮里。 注意它丢的时候不区分「闲聊」和「预算三十万」。对它来说都是一段文字。 ②落盘之后六条全在,101 个字、109 token。写的时候只花一次,读的时候每次都要花。 ③按需检索这一轮只捞回「验收分两次」那一条,22 token。同样是六件都在,这一轮只付了两条闲话的钱。 三种载体的账摆在一起:当轮上下文差不多都是六百上下,差别全在「能不能找回来」。 第一种丢了六条,后两种一条不落;而按需检索只比第一种多花 22 token。 ④最后一段是这一节的落点:窗口翻十倍,五十轮能装下,五百轮还是装不下。 而且窗口里每一轮都要重付。装闲话的钱和装事实的钱,单价一样,价值差得远。
30 秒实验
- 把窗口从 600 改成 1200,重跑第 ① 步。六件事能剩下几件? 如果把后四十四轮换成同样长的正经讨论,结果会变吗?
- 把 FACTS 的六件事改成在最后六轮才说,重跑第 ① 步。存活率变成多少?这说明滑窗的失效条件是什么?
- 给「按需检索」加一条:每次都把最近三条也带上。读回成本涨多少?这样做换来了什么?
常见误区
- 以为模型有记忆。它没有。所有「它记得」都是有人把内容又送了一遍。 搞不清这一点,就会把「怎么让它记住」当成一个模型问题,而不是组织问题。
- 靠把窗口开大来解决遗忘。窗口再大也有上限,而且每轮都要为窗口里的全部内容付费。
- 把所有东西都写进 CLAUDE.md。那是常驻的,等于每轮都重读一遍全部资料。
- 以为「写进文件」就等于「记住了」。文件在磁盘上,不在上下文里——不读回来,模型看不见。
- 只做检索不做兜底。检索可能召回不到,而关键事实丢了是要出事的。 重要的事除了检索,还得有一处强制注入。
自测三问(点开对答案)
1. 「模型记得上一轮说过的话」这句话准确吗? —— 不准确。模型每次调用都是一次独立计算,它没有跨轮状态。 所谓记得,是上一轮的内容被重新放进了这一轮的输入里。 2. 三种记忆载体各自的主要代价是什么? —— 上下文:每轮重付,窗口满了丢最旧的。文件:不丢,但每次读都花 token。 外部检索:容量最大,但多一次查询,且可能找不准。 3. 为什么说「窗口开大一点」不是记忆问题的答案? —— 因为长对话没有上限,窗口再大也会撞墙; 而且窗口里的内容每轮都要重付,装一堆当下用不到的历史,等于每轮都在为过去付费。知道了「要往外搬」,下一节看它到底被分成了几层、每层什么时候进上下文。
- ☐ (本篇完,下一篇 2.2 五层记忆体系)
相关笔记
- Agent 框架:循环里上下文只增不减
- 五层记忆:五层怎么叠加
- Claude Code 橙皮书 · 索引
- 🐎 Claude Code 实战 · 索引