用户记忆系统
记忆不是记录用户说过的每一句话。就像和朋友相处,我们不会记住每次对话的原文,而是逐渐形成一个关于对方的预测模型——爱好、习惯、价值观。用户记忆系统的本质,就是主动地、持续地构建一个关于用户的简洁而有效的预测模型。
要构建真正具备个性化、连续性服务的 agent,用户记忆(User Memory)系统是不可或缺的核心能力。记忆并非简单记录用户说过的每一句话。正如我们与朋友相处时,不会记住每次对话的原始内容,而是通过持续交互,在脑海中逐渐形成一个关于对方的生动模型——他的爱好、习惯和价值观。
用户记忆系统的本质是一个主动的、持续的学习过程,其目标是构建一个关于用户的简洁而有效的预测模型。它投入额外的算力(通过专门的 LLM 调用来分析、总结和结构化信息),将分散在冗长对话历史中的关键信息显式提取和压缩。这与上下文学习形成对比——用户记忆是持久的、可审查的,上下文学习则是临时的、会话结束就消失。
提取过程有三个关键特征:选择性——不记“搜索返回了 3 个选项”这类临时信息,只保留对未来有用的事实;抽象化——“我喜欢靠窗座位”被提炼为一条通用偏好,而非绑定到某次航班;结构化——每条记忆标记类型(偏好、限制、账号),便于后续检索。
记忆能力的评估:三层次框架
动手设计之前先立标尺:什么样的记忆系统算“好”?学术界有 LoCoMo 等长期对话记忆基准,在此基础上可归纳出一个更贴合 agent 场景的三层次评估框架:
- 第一层 基础回忆——准确存储和检索用户直接提供的、结构化、无歧义的信息(“我的会员号是 12345”),在需要时精确返回。这是基本可靠性。
- 第二层 多会话检索——面对来自不同对象、不同时期的多个会话,检索出所有相关信息并推理判断。用户有两辆车时问“为我的车预约保养”,系统要找出两辆并主动询问是哪辆,而不是随便猜。
- 第三层 主动服务——综合跨越多个甚至很久以前的会话,提供有预见性的主动帮助:预订国际航班时主动关联数月前的护照信息、发现即将过期并预警。这是“助理”级别的最高标准。
记忆的层次结构:放哪里
记忆系统的设计可以拆成三个独立维度——放哪里、怎么存、存什么。先看“放哪里”。记忆需要分层,就像人有短期工作记忆和长期记忆:
- 轨迹(Trajectory)是一次运行的完整历史(用户消息 + 模型回复 + 工具结果),按时间追加、只增不改,为决策提供即时上下文——“我刚才说了什么、用户如何回应、工具返回了什么”。它是单次会话的流水账。
- 用户长期记忆是跨会话、跨实例的持久化存储,通常以键值对与用户 ID 绑定,存偏好、交互摘要、提取的知识点。它会被反复改写、合并、淘汰——是档案,不是流水账。
四种存储格式:怎么存
同一条用户信息,可以用不同粒度和结构表示。四种渐进式格式代表了记忆粒度与结构复杂度的递进:
- Simple Notes——每条记忆是一个最小、不可再分的事实(“用户邮箱:john@example.com”)。开销极低(O(1)),但信息关联性丢失:同一份工作被拆成三个独立事实,内在联系被割裂。
- Enhanced Notes——每条记忆是一段包含完整上下文的话,保留叙事结构、语义完整。代价是存储冗余、更新复杂,且段落越长越不利于向量检索。
- JSON Cards——三层嵌套(类别→子类别→键值对),支持部分更新、可预测可扩展。但刚性结构假设信息可清晰分类,多维信息被强制归入单一类别会丢失。
- Advanced JSON Cards——从信息存储升级到知识管理:每张卡片除事实外,还带来源背景(backstory)、主体身份(person)、与用户的关系(relationship)和时间戳,解决“张医生是我的牙医还是我父亲的心脏科医生”这类消歧问题。代价是生成和维护成本高。
四者体现了记忆设计的根本张力:简单性与表达力的权衡。实践中的选择标准是:关键且少量的数据(偏好、关键人物关系)用 Advanced JSON Cards 保证可检索;大量且非关键的对话事实用 Simple Notes 降低成本——多数生产系统混合使用。
进阶表示:从文本到参数化记忆
前面四种格式无论简单复杂,本质上都是文本——于是记忆的“存”和“用”始终是分开的两步:先把相关文本捞回来,再交给容易出错的 LLM 去读、去算。文本记忆擅长召回单条事实,却难以在众多记录上做聚合统计、发现相互矛盾的事实、或强制执行逻辑规则,因为这些操作都要靠 LLM“心算”。
User as Code:把介质换成可执行代码。 解法是把表示的介质从文本换成可执行代码——用带类型的 Python 对象保存用户状态、用普通函数编码约束规则,让“表示用户”和“推理用户”发生在同一个可被解释器运行的介质里。它把更新拆成两阶段:记忆阶段(每次会话后把事实逐条抽成字符串,追加到一个只增不删的事实日志)与结构化阶段(周期性地从完整日志重新生成整份带类型的代码,把事实组织进 dataclass)。这正是数据库“预写日志 + 周期性检查点”的经典设计第一次用到 LLM 记忆上:只增日志保证不丢事实,周期检查点把它压缩成整洁、可查询的结构。
有了带类型的状态,此前只能靠 LLM“读文本再心算”的三件事,都变成确定性的代码。其一,聚合统计。“我去年出了几次国”,文本记忆要把所有行程召回再逐条数,记录一多就出错(论文实测检索式记忆在这类问题上正确率只有 6%–43%),而在代码里是一行 sum(...),正确率接近 99%。其二,冲突发现。把“当前用药”和“过敏史”放在一起,一个函数按药物类别交叉比对,就能揪出散在不同对话里、文本形态下几乎不可能自动关联的矛盾。其三,约束执行。把检查函数固化下来,状态每次更新自动触发(如护照到期距出国行程不足 180 天就报警),不需用户开口、也不需检索就能主动提醒。代价是需要一套代码生成与执行的工程支撑,且对结构化程度不高的杂项事实并无优势——所以 notes 字段仍为文本保留一席之地。
User as Code 仍是模型之外的外部存储,用时要先检索、再让模型在上下文里推理。沿“表示介质”这条线继续向内,记忆还能写进模型自身的参数,引出两种更前沿的形态。User as Engram(写进局部参数):不为每个用户训练 LoRA——那样训出的 fact-LoRA 直接提问几乎能完美复述,一旦要在事实上做间接推理便失灵,因为冻结骨干从没学过如何“查阅”临时挂载的适配器——而是把一条用户事实精准写入 Engram 模型一个空闲的哈希 N-gram 槽位;这类模型预训练时已学会哈希查表调取记忆,并由上下文门控决定何时调取,于是新事实会在该被想起时被想起。不同用户的事实落在互不相交的槽位,彼此叠加而不串扰。Parametric Multimodal(存下无法言说的感知):关于用户的记忆还有感知性的一半——一张脸、一段比上周更疲惫的嗓音、一位画家的笔触——经不起转写成文字。解法是让感知以感知的形态保存:为冻结模型外挂一个小记忆库,每个身份一行,键是现成编码器(人脸用 ArcFace、画风用 CLIP)算出的感知向量,值是模型某个标记词的嵌入;生成时当前感知作为查询在库上做注意力,把输出引向匹配标记,全程不经文字。注册新身份只需加一行、无需训练,效果反而超过直接向量检索——因为它在语言模型自身的表示空间里比对感知,这把“尺子”比编码器原生相似度更锐利。
从纯文本、到可执行代码、再到局部参数乃至连续感知,是用户记忆的表示由“外”及“内”的一条连续谱:外侧易更新、可审查、可迁移,内侧更紧凑、更擅长即时推理,也能承载文字无法转写的感知。
认知科学基础:存什么
从认知科学看,人类记忆的复杂性为 AI 记忆设计提供了启示——补上“记忆内容的类型”这个维度。认知科学把记忆分为工作记忆和长期记忆。工作记忆对应 agent 的上下文窗口,处理当前任务的临时信息(轨迹是其中最核心的内容,但工作记忆还可能包含从长期记忆激活加载的信息)。长期记忆再细分三种,每种都能在 agent 记忆里找到直接对应:
- 情景记忆(Episodic):具体事件和经历。“用户订了下周五去东京的 ANA 航班”——记录了一个具体事件的时间、对象和细节。
- 语义记忆(Semantic):从具体事件抽象出的一般知识。“用户是素食者”“用户偏好靠窗座位”——从多次交互提炼出的稳定特征。
- 程序记忆(Procedural):行为模式和流程。从反复订机票中学到的通用流程——“先搜直飞 → 确认座位偏好 → 用常旅客号码 → 订餐”。
到这里出现了三套正交的分类体系,一张表厘清关系:
| 分类体系 | 回答的问题 | 具体类别 |
|---|---|---|
| 记忆层次 | 存在哪里? | 轨迹、用户长期记忆、业务状态 |
| 存储格式 | 怎么存? | Simple / Enhanced Notes、JSON Cards、Advanced JSON Cards |
| 认知类型 | 存什么? | 情景(事件)、语义(知识)、程序(流程) |
三套是正交维度,可自由组合:一条“偏好靠窗座位”的语义记忆可用 Simple Notes 存在用户长期记忆里;一段“先搜直飞 → 确认座位 → 用常旅客号”的程序记忆可用 Advanced JSON Cards 存。选哪种格式取决于工程需求(简单性 vs 表达力),存什么类型取决于业务场景。
记忆框架案例
开源社区已有多个记忆管理框架,以 Mem0 和 Memobase 为例看两种设计理念的取舍。
Mem0:提取—对比—决策的两阶段流水线。 每段新对话结束,Mem0 调 LLM 结合最近对话与已有记忆摘要,提取候选记忆(简洁事实,如“用户搬到了上海”);再对每条候选先做向量检索找出语义相近的旧记忆,由 LLM 对比后四选一——ADD(全新入库)、UPDATE(补充或修正)、DELETE(新信息否定旧记忆)、NOOP(重复,不动)。用户说“搬到上海”会检索到旧记忆“住在北京”,判为 UPDATE,而非并存两条矛盾记录。它把“选择性提取”和“冲突解决”统一在同一机制里——每条记录都与既有记忆显式对账。工程上嵌入与存储分离、可独立替换,并有图记忆变体 Mem0-g,把记忆表示为实体—关系图,以改善多跳、时序类问题。
Memobase:用户画像加事件记忆。 与其做通用流水线,不如聚焦“用户画像”。它把记忆分两部分:用户画像是一组开发者可配置的槽位,按主题—子主题两级组织(basic_info → 姓名、interest → 游戏偏好),存稳定属性;事件记忆按时间线记录经历,回答“上次讨论预算是什么时候”这类时间相关问题。工程上用缓冲批处理摊薄 LLM 调用成本,查询侧只读整理好的画像和事件以保证低延迟。
两者各覆盖设计空间的一部分:Mem0 的事实条目近语义记忆,Memobase 的画像近语义记忆、事件近情景记忆。按认知科学分类可设想一种多类型记忆协同的参考架构(是对设计空间的概括,非某个具体实现):情景记忆存带丰富元数据(时间戳、情感标记、任务标识)的事件序列,支持多维检索;工作记忆管理当前任务状态,与长期记忆动态交互——重要信息选择性转入长期记忆,相关长期记忆被激活加载。要点是认知科学的分类能落地为工程组件,但实际框架往往只实现其中一两种,按业务取舍比“大而全”更符合工程现实。
记忆压缩与整理机制
交互持续,记忆系统面临存储与检索效率的双重挑战:简单累积会导致记忆爆炸,既耗空间又降低检索准确性。实践中用多层压缩:
- 重要性评分筛选:综合访问频率、时间衰减、情感强度、信息独特性四个因素打分,低于阈值的标记为可压缩或删除。(被访问 5 次、3 天前、带强情感、无重复的记忆得分高;只访问 1 次、90 天前、无情感、与 3 条高度重复的可能低于阈值。)
- 聚类摘要:相似记忆分组,每组生成代表性摘要(多次天气对话压成“用户经常询问天气,特别关心降雨”),原始详情存档到二级存储。
- 抽象泛化:从具体情景记忆提取一般规律,转为语义或程序记忆(从多次购物学到“偏好性价比高、重视用户评价”)。
冲突检测用版本化:保留历史版本同时标记最新版;当前地址只留最新,工作经历保留完整历史。
划清边界:本节讲的是记忆存储层的整理算法(哪些该筛选、聚类、抽象成什么形态);上下文压缩解决的是单次会话的窗口问题,两者层次不同。而“在线追加证据、离线集中整理”的两阶段思路,持续进化把它推广到 agent 的行为进化。
隐私保护:日志脱敏
用户记忆系统的一个核心挑战:让 agent 既能用用户信息提供个性化服务,又不让敏感数据暴露在 LLM 上下文和系统日志里。一个务实做法是用本地小模型(如经 Ollama 调用的 Qwen3 0.6B,可在 CPU、消费级设备上跑)做 PII 检测与脱敏——之所以本地而非云端 API,是因为日志本身可能含敏感信息,发去云端脱敏就违背了初衷。它能识别结构化(身份证号、银行卡号)、半结构化(地址)和自然语言表达的敏感内容(“我的密码是 abc123”),结果按 JSON Schema 结构化输出(类型、位置、置信度)。相比正则,基于 LLM 的脱敏召回率达 95% 以上、假阳性更低;超高吞吐场景可混合——正则快速过滤明显模式,LLM 深度分析剩余文本。
工程实践
Zapvol 的记忆活在一个窄口 MemoryStore port 之后:记忆以 .md + YAML frontmatter 存储(人类可读、agent 可操作、grep 可搜),Desktop 用 fs、服务端用 R2/DB,同一套 service 逻辑不变、只替换 store 后端。它独立于 per-task 沙箱、按用户隔离(openUserMemoryStore({ userId }))。目前落地了三层里的第一层(Auto-Memory,跨会话持久、按用户隔离);Session / Team 记忆计划中。
存什么用四种类型区分——user(角色偏好)、feedback(对做法的纠正/确认)、project(非代码可推导的项目上下文)、reference(外部资源指针);明确排除可从代码推导的、临时的、已存在的内容——防膨胀的关键是“什么不该存”。
写入是双通道互斥:通道 A 显式写入(save_memory 工具,用户说“记住这个”或 agent 判断需要时);通道 B 后台自动提取(每轮结束后 finalizeExecution 入队 memory.extraction job,用轻量模型 + Zod schema 从最近对话提取结构化候选)。两通道互斥——若本轮已调用 save_memory 则跳过自动提取,防重复。读取也是两条:启动时把 MEMORY.md 索引注入系统提示词,运行时 agent 主动调 recall_memory(当前关键词匹配 top 5,后续可升级为向量相似度)。
这套设计对应 book 的判断:记忆是持久且可审查的预测模型——.md 文件可 grep、可人工检查;类型标注兑现“结构化”;自动提取兑现“选择性 + 抽象化”。
参考:模块与安全
packages/backend/src/agent/memory/
memory-store.ts — MemoryStore port + createFsMemoryStore + openUserMemoryStore
memory-service.ts — 核心 CRUD(scan / save / delete / loadIndex / rebuildIndex)
memory-recall.ts — recall_memory 搜索
memory-extraction.ts — 后台自动提取(在 job 中运行)
| 威胁 | 防护 |
|---|---|
| 路径穿越 | MemoryStore 契约拒绝穿越,由各后端强制执行 |
| 跨用户访问 | store 按 userId 打开,agent 只能经工具访问、不能直接触达 store |
| 记忆膨胀 | 文件数上限 200、索引 200 行 / 25KB |
相关阅读
- 提示工程——记忆索引经
MEMORY_PARTIAL注入系统提示词。 - 上下文压缩——留在窗口之外的记忆无需压缩,只需召回,与摘要地板互补。
- 记忆设计——框架无关的记忆设计空间:分类法、写入与召回取舍、失效机制。
来源
- Maharana et al. LoCoMo: Long-term Conversational Memory. arXiv:2402.17753, 2024