ENZH

它记得你昨天也熬夜了

📊 幻灯片

Clawd Soul · 第 2 篇 / 共 5 篇


上一篇讲了怎么用散文给 AI 写性格。但性格再好,记不住你,也陌生人。这篇讲记忆。


记忆是关系的唯一载体

对 Clawd 说"我好累"。它回:"你昨天也是这么晚……"

后台发生的事:Active Memory 模块拿"好累"当关键词,在过去交互记录里做了次混合检索,找到"用户昨天凌晨一点仍在活跃",把这条记忆注入当前 prompt。模型看到上下文,自己决定怎么用。

听着简单。但这句话区别:有记忆,宠物;没记忆,陌生人。

性格档案给它角色。记忆给它跟你的关系。角色和朋友,区别是有没有共同经历。

所有 AI 助手都在解决"怎么回答得更好"。宠物要解决的不一样——怎么让你觉得它认识你。答案不是更聪明模型,是更完整记忆。


三层存储,各管各的

存储怎么来的容量
片段记忆 (Episodic)SQLite + FTS5 + sqlite-vec每次观察、聊天、截屏分析自动写入无限(本地磁盘)
长期记忆 (Long-term)JSON 文件每晚"做梦"从片段中提炼最多 100 条
对话记忆 (Conversation)JSONL + 摘要每条消息实时写入,超限时压缩动态循环

三层不为好看。每层解决不同时间尺度问题。

片段记忆是原材料。每 45 秒屏幕观察、每条聊天消息、每次截屏反应,全写 SQLite。同时建两种索引:FTS5 全文搜索(关键词命中),sqlite-vec 存 768 维向量嵌入(语义相似度)。两条路后面用到。

片段只进不出。积累越多,宠物能"回忆"的素材越丰富。本地 SQLite,磁盘成本几乎为零。

长期记忆是精华。每晚 23:30,系统从当天片段中提炼 3–5 条用户重要事实,写入 JSON 文件。上限 100 条——超了按重要性淘汰最旧的。100 条直接拼进 prompt 稳定前缀,每次对话都带。

长期记忆就是宠物"永远知道"的事。喜欢火锅、养了只真猫叫橘子、最近在赶 deadline——不需要每次检索,就是记得。

对话记忆管当前会话。消息按 JSONL 格式实时追加。积累到约 50 万 token,系统把最旧 70% 消息交给模型做摘要,摘要存下来,只保留最新 50 条原始消息。摘要进稳定前缀,原始消息在动态区。

70/30 切割点试出来的。太少压缩不够,context 很快又满;太多丢对话细节。但压缩前还有一步——下面讲。


自动回忆:核心不是存,是想起来

存谁都会。难的是:用户说句话,系统要在几百上千条记忆里找到最相关几条,塞进 prompt,让模型自然"想起来"。

Active Memory 做的事。每次生成回复前,系统跑 autoRecall

  1. 拿用户最新消息(或屏幕内容)当查询
  2. 混合检索:BM25(关键词匹配)+ 向量相似度(语义匹配),同时跑
  3. 时间衰减:score *= exp(-0.01 * ageDays) —— 越新记忆权重越高
  4. MMR 去重:0.7 * relevance - 0.3 * max_similarity_to_selected —— 强制多样性

第 2 步为什么要两条路?用户说"火锅",BM25 精确命中"火锅"词。但用户说"好饿",只有向量检索能把"好饿"关联到之前饮食偏好。单独用哪种都不够。

第 4 步更实际。没 MMR,检索可能返回五条都是"火锅"——提过很多次。但 prompt 空间有限,该返回一条火锅、一条加班、一条猫,让模型有更多素材选。0.7 和 0.3 手调的,偏相关性但留出多样性。

每轮对话结束,系统跑一次 extract:从用户消息和宠物回复中提取新事实,写进片段记忆。记忆随每次对话生长。


做梦:睡眠巩固记忆

每天 23:30,宠物"做梦"。

流程不复杂:

  1. 取当天所有片段记忆
  2. 交给模型,选出 3–5 条用户最重要的新发现
  3. 写入长期记忆
  4. 长期记忆超 100 条时,淘汰最旧或最不重要

灵感来自人类睡眠记忆巩固。白天经历太多太碎,睡觉时大脑把零散信息整理成长期记忆。宠物"做梦"做同样事——几十条片段压缩成几条本质判断。

"用户在 14:32 说了'好饿'"——片段。做梦后变成"用户经常下午两三点饿,可能午饭吃得少"——长期记忆。信息量一样,抽象层级不同,占用 token 少得多。

还有个容易忽略的细节:压缩前刷新。对话记忆做摘要压缩前,系统先跑 extract,把所有重要事实存进片段记忆。不做这步,早期对话信息在压缩时丢失。长对话尤其明显——第一个小时聊的,第三个小时可能已被摘要吞掉。

机制看着小,但保证了一件事:不管对话多长,重要的事永远不会被忘。


八层 prompt:把记忆变成对话

记忆存好、检索到,最后一步拼进 prompt。Clawd 每次 AI 调用由 8 层拼装。

稳定层(跨调用复用,命中缓存):

内容大约 token
1 · Identity"你是一只住在桌面上的小动物"~50
2 · Soul完整性格档案~800
3 · Long-term Memory长期记忆——关于用户的稳定事实~500

── 缓存边界 ──

动态层(每次调用都不同):

内容说明
4 · Active MemoryautoRecall 检索到片段按相关性注入
5 · Daily Context当前时间、用户活跃模式"现在凌晨两点,用户已连续工作 4 小时"
6 · Mode Rules当前模式行为规则observe / chat / react / heartbeat / diary
7 · Drive Hints宠物"想"聊的话题基于最近观察生成
8 · Anti-Repetition最近回复列表防止重复说同样话

分层关键在第 3 层和第 4 层之间缓存边界。

前三层稳定——性格不变,长期记忆一天最多更新一次。构成 prompt 固定前缀。用 OpenAI 或 Azure prefix caching,这部分 token 第一次调用后基本不再计费。对每 5 分钟可能说话的宠物,这优化直接决定运营成本能否承受。

后五层每次都变。Active Memory 跟用户消息变,Daily Context 跟时间变,Mode Rules 跟场景变。

五种模式各有自己规则集:

  • observe — 静默观察,不说话,只记录屏幕内容
  • chat — 用户主动聊天,正常对话
  • react — 看到屏幕上有趣东西,主动吐槽
  • heartbeat — 每 5 分钟一次,宠物自己决定要不要说话
  • diary — 每天 23:00 写日记,回顾一天

heartbeat 最微妙。大多数时候沉默。但偶尔——用户加班到凌晨,或连续三小时没动键盘——说一句。"偶尔"的节奏感,靠 Daily Context(知道现在几点、用户多久没动)和 Drive Hints配合。

Anti-Repetition 层解决实际问题:模型喜欢重复自己。上条说"该休息了",下条又说"该休息了"。这层把最近 N 条回复塞进 prompt,明确告诉模型"不说这些"。简单但有效。


回到那句话

八层引擎、混合检索、时间衰减、MMR 去重、做梦巩固、缓存边界——都是工程。

用户感受到的只有一件事:它说了句"你昨天也是这么晚"。

一句话。但就这一句,它知道你在干嘛,记得你。不是数据库有条记录叫"用户昨天凌晨一点活跃"——像朋友一样,你说"好累",它自然想起昨天的事。

下一篇讲更奇怪的挑战:怎么让 AI 学会"没用"。


© Xingfan Xia 2024 - 2026 · CC BY-NC 4.0