上周有人在群里甩了个 arXiv 链接,2609.13771,Homeostatic Continual Learning,9 月 12 号挂上去的。18 页,12 张图,1 张表。图比一般论文密,看得出来作者是真想把机制画清楚。
我点开它的时候,正在改自己那个 agent 的记忆层,改得一肚子火。
这论文跟我平时捣鼓的东西看着不搭边——它讲的是一个会动的智能体怎么在环境变化里持续学,还不把旧东西忘干净。但这恰好是我这半年最头疼的一件事。那天晚上代码没改,PDF 从头翻到尾。
先说我为什么火大。
记忆文件是去年随手建的,开头十几行,"这个项目用 pnpm""不要动 legacy 目录"。上个月我看了眼,四百多行。每次开新会话,system prompt 第一件事就是把它整份灌进去。我一直觉得这是对的——记得越多,它越懂我。
直到上个月它给我推了一个我三个月前就废掉的方案。
画外音:第一反应是骂它。骂完发现骂错了人。
两条记忆都在文件里。一条写"配置走 A 方案",另一条写"别用 A,换 B"。它们在 embedding 空间里挨得特别近,因为说的是同一件事。检索的时候距离差不多,模型随手挑一条。它没背叛我,是我自己给它留了个二选一。
这才是全量记忆最要命的地方。它默认信息是资产,多存不亏,用的时候再筛——前提是能筛对。而向量检索筛的是相似度,不是正确性。相似度这东西,恰恰分不清两条互相矛盾的记忆里哪条还成立。我给每条加时间戳,能缓解一点,缓解的方式是让模型自己看日期猜。等于把判断外包给了一个更弱的判断。
现在做 agent 记忆,默认路子还是那三条:全量存、向量检索、定期总结。三条合起来,等于把最难的问题整个跳过去——什么才配被记住。
论文里那个机制,一句话:智能体输出出现异常的时候,它不回头改自己,而是回到环境数据里,去定位那个异常点。
这方向跟我的本能是拧着的。我的肌肉反应是——出问题,改 prompt,加一条规则。改的是智能体自己。
可要是环境里有个东西跟我的假设对不上呢?那我改一百遍 prompt 都是白改。规则里写"这个接口返回 status 字段",接口早改成 state 了,我加十条"请仔细检查"它也变不出 status 来。
去年冬天我让 agent 挂机跑批处理,任务很简单,两万多条记录按标题去重。第二天早上它交回来,标了三百多条重复。我抽了几条看,三条里有一条根本不是同一件事。当时的处理方式是——花小半个晚上,在指令里加了一句"注意标题里可能有多余空格"。加完重跑,还是错。
后来才发现那批数据里有全角空格,还有几条标题末尾挂着不可见的换行符。那晚上我干的所有事,都是往智能体身上加规则。真正该干的是回到数据里,把被判成重复的那些抽几十条出来,看看它们到底长什么样。十分钟的活,我绕了一晚上。
"去定位环境数据中的异常点",说的就是这个。它把校正的对象从智能体挪到了环境假设上。这个挪动比它后面讲的那套世界模型值钱得多,至少对我来说是这样。
顺着这个思路我把记忆层重新想了一遍,发现第二个我一直没算过的代价:上下文预算。
四百多行压进去,大概小一万 token。单看不多。但它是每个新会话的固定开销,而且占的位置在最前面,后面所有推理都踩在它上面。我刚建文件的时候一条记忆值十条,现在十条里有三条过期、两条互相打架、五条是废话。等于我每次开工,都先往脑子里塞一坨纯度越来越低的背景,再指望它在这个基础上判断得准。
"实习生"这个说法我用了一年多,这回得修正一下:它现在的问题不是记性差,是记性太好,而且不会忘。
论文讲的"灾难性遗忘",跟我在 agent 项目里说的"遗忘"其实不是一回事。论文担心的是模型学了新任务、把旧任务的参数覆盖了,是模型层面的事。我这边正好反过来——模型什么都不忘,因为我什么都往里写。真正忘掉东西的是我自己。我忘了当初为什么定下某条规则。那条规则以一行光秃秃的结论躺在文件里,前提、场景、代价全丢了,剩下一个看着像废话的句子。
所以遗忘有时候不是缺陷,是缺省值。这是那篇论文读完,我唯一坚定的一点。
然后是我不太买账的部分。
论文说可以用这个思路构建世界模型:把世界里的对象分解成特征,把对象抽象成可比较的概念实例,再用特征把概念映射到意图。这话写进论文里特别顺,那 12 张图里有几张就是在画这个三层结构,我盯着看了很久。
第一遍读完我想翻白眼。特征是谁定的?
如果特征是我手写的,那这套东西就是我那堆手写规则换了个说法——不叫世界模型,叫配置文件。如果特征是从数据里自己长出来的,那我得知道它怎么长、长出来什么样。18 页里留给这部分的篇幅不算多,我看了三遍,也没抓住它是怎么从"一次异常定位"走到"一组可复用特征"的。
不排除是我没读懂。论文最后自己也写了,这方法离实用还有一段路。那就先记着——等哪天我真拿它搭出个能跑的东西,再回来写下半篇。别急着下结论,我在这上面翻过车:之前否过一个我没看完的东西,后来被自己打脸。
扯远了,回正题,讲我实际改了什么。
改动一,记忆不再每次会话结束就写。改成只在两类事发生的时候才写:我明确纠正了它,或者它的输出跟我预期不符。其余一概不写,对话日志该扔就扔。
改动二,每条写进去的记忆,必须带上触发它的那次环境数据。原来我的条目长这样:
- 部署脚本用 pnpm,不要用 npm
现在是这样的:
- 部署脚本用 pnpm,不要用 npm
触发:用 npm 装了一次依赖,锁文件被改写成 package-lock.json,
跟仓库里的 pnpm-lock.yaml 打架,CI 直接挂在这一步
多出来那两行不是装饰。它是我下次判断这条记忆还成不成立的唯一线索。哪天项目真换回 npm 了,我看一眼触发条件就知道这条该不该删,不用再去翻三个月前的对话记录。
然后我把整个文件从头到尾过了一遍。删的标准就一条:这条记忆说没说清它对应哪一次翻车。说不清的,全删。
删掉的大概是些什么?"优先用函数式写法""注释要写清楚""注意边界情况"。这些不是记忆,是我的审美,是我个人偏好的一份清单。它们放在记忆文件里的唯一效果,就是每次会话白烧一遍 context。
画外音:我甚至删掉了自己半年前郑重其事写的一条"记得检查边界情况" 😅。它跟那些"请仔细检查"的 prompt 一个毛病——太抽象,接不住,模型看到它跟没看到一样。
现在这个文件两页多一点。它变聪明了吗?我不知道,这个不好测,也没什么基准能跑。但它至少不再同时告诉我"用 A"和"别用 A"了。光这一个变化,就够我先把这版留着。
有条规则我打算一直守着:凡是要写进记忆的条目,先回答一个问题——它对应哪一次翻车。答不上来的,不配进去。
我知道这文件还会长回去。记忆这东西跟桌面一样,没人管就自动变乱,所以这条规则不是为了让它变短,是为了让我每次往里写之前多犹豫那么一下。
那篇 PDF 我留在收藏夹里了。不是因为它那套世界模型,是因为"异常才写回"这五个字,值得我把剩下的十七页半再啃一遍。
