它记得你昨天也熬夜了
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:
- 拿用户最新消息(或屏幕内容)当查询
- 混合检索:BM25(关键词匹配)+ 向量相似度(语义匹配),同时跑
- 时间衰减:
score *= exp(-0.01 * ageDays)—— 越新记忆权重越高 - MMR 去重:
0.7 * relevance - 0.3 * max_similarity_to_selected—— 强制多样性
第 2 步为什么要两条路?用户说"火锅",BM25 精确命中"火锅"词。但用户说"好饿",只有向量检索能把"好饿"关联到之前饮食偏好。单独用哪种都不够。
第 4 步更实际。没 MMR,检索可能返回五条都是"火锅"——提过很多次。但 prompt 空间有限,该返回一条火锅、一条加班、一条猫,让模型有更多素材选。0.7 和 0.3 手调的,偏相关性但留出多样性。
每轮对话结束,系统跑一次 extract:从用户消息和宠物回复中提取新事实,写进片段记忆。记忆随每次对话生长。
做梦:睡眠巩固记忆
每天 23:30,宠物"做梦"。
流程不复杂:
- 取当天所有片段记忆
- 交给模型,选出 3–5 条用户最重要的新发现
- 写入长期记忆
- 长期记忆超 100 条时,淘汰最旧或最不重要
灵感来自人类睡眠记忆巩固。白天经历太多太碎,睡觉时大脑把零散信息整理成长期记忆。宠物"做梦"做同样事——几十条片段压缩成几条本质判断。
"用户在 14:32 说了'好饿'"——片段。做梦后变成"用户经常下午两三点饿,可能午饭吃得少"——长期记忆。信息量一样,抽象层级不同,占用 token 少得多。
还有个容易忽略的细节:压缩前刷新。对话记忆做摘要压缩前,系统先跑 extract,把所有重要事实存进片段记忆。不做这步,早期对话信息在压缩时丢失。长对话尤其明显——第一个小时聊的,第三个小时可能已被摘要吞掉。
机制看着小,但保证了一件事:不管对话多长,重要的事永远不会被忘。
八层 prompt:把记忆变成对话
记忆存好、检索到,最后一步拼进 prompt。Clawd 每次 AI 调用由 8 层拼装。
稳定层(跨调用复用,命中缓存):
| 层 | 内容 | 大约 token |
|---|---|---|
| 1 · Identity | "你是一只住在桌面上的小动物" | ~50 |
| 2 · Soul | 完整性格档案 | ~800 |
| 3 · Long-term Memory | 长期记忆——关于用户的稳定事实 | ~500 |
── 缓存边界 ──
动态层(每次调用都不同):
| 层 | 内容 | 说明 |
|---|---|---|
| 4 · Active Memory | autoRecall 检索到片段 | 按相关性注入 |
| 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 学会"没用"。