那本笔记我翻了半个多钟头,翻完有点发愣。是很多年前的一个 txt,标题就叫“我”,里面一条一条列着我对自己的结论:不喜欢写界面,不碰前端,看不进去某类小说,跟某种人处不来。写下的时候每一条都很笃定,后来这几年又添过几回,可从来没有哪一条被划掉过;它就那么一路长下去,长成一个只增不改的清单,而我今天早上还拿其中三条,跟一个刚认识的人解释我是个什么样的人。
前几天读到一篇文章,一个人在 dev.to 上写的,说 agent 的记忆坏掉,多半不是因为忘了重要的东西,而是因为记得太多、已经分不清哪些信息还算数了。他列的症状我一看就认得:自相矛盾,反复问已经回答过的问题,把早就被推翻的旧结论当成现役事实端出来,语气还格外笃定。这类毛病我以前都归到提示词写得不好上头,他说不是。
他举的那个例子我看了两遍。第一次会话里用户说偏好 TypeScript,第四十次改成要 Python,两句话在语义上挨得那么近,任何相似度都分不出哪一句是现在的。更麻烦的是下面那层,如果第一条因为活得久、被更多会话强化过,它在朴素的 top-k 里排在新说法前面几乎是必然的。出现频次和时间近因从来不是一回事,而多数记忆层只记其中一样。
读到这里,我想到的不是代码。
我那句“不碰前端”,大概第三年就不太成立了。问题不在它说错了,在于从头到尾没有任何一处机制,负责把它标成作废。它只是又被我说过几次,说得越多,越像真的。我一度觉得这事不难,加个时间戳就好了。不,这么说也不准确,难的从来不是记时间,是承认后一句比前一句更算数;这一点机器不认,人也不总是认。
他主张把记忆当缓存建,不当日志建,提了三样东西:显式取代、写入时的矛盾检查、显著性衰减。头两样是工程活,总有人会去做,我没什么好说的;最后一样我琢磨了很久。写进去的频率,被当成了相关的频率,这两件事差得很远。一条事实之所以排在前面,往往只是因为它被写下的次数多,并不是因为它现在还管用。
评论区有个人做记忆层,说自己试过把近因和使用衰减直接接进召回排序,各项基准全线回退,当天就回滚了;后来改成年龄只能动一条事实的可信标签、不能动它的排名,五天没被召回就把置信度从 1.0 降到 0.7,十天降到 0.5,可它该浮出来的时候还是照常浮出来。这段我读着觉得他挺诚实的。动手想让机器按岁数忘事,结果发现按岁数忘本身就是错的。
还有一条评论,我抄在纸上了。大意是说,衰减这个信号缺一个负例:一条本该被检索、却从来没有被检索过的事实,是攒不到任何显著性的,它会安安静静地衰减出热索引;而这件事本身不会被登记下来,因为这个信号记录不了自己的缺席。
我把这话在纸上又抄了一遍。
一个从来没被想起来过的人,在你的记忆里不会留下“他没被想起过”这样一条记录,因为没被想起就是没被想起。它不吵不闹,不占任何统计口径,也不触发任何告警,等你哪一天真的需要它,它已经在归档区里了。这种东西我一直叫压舱石,躺在船底,从不参与任何检索,平时也想不起来,可少了它,船是会翻的;而麻烦恰恰在于,压舱石这个概念本身没法写进一个只认排名的系统里。
评论区还争过一个我挺感兴趣的点:怎么分清“用户换了主意”和“用户对两件事本来就有两种偏好”。比如两个仓库各有一套写法,那不是矛盾,是并存。写文章那人回答说,他们把上下文和事实一起编码,检测的时候再校验一遍,可也承认这两种情况分不清的时候还是会出错。这个我太熟了。有些话我以为自己在改主意,其实只是换了个场合