Agent Memory 怎么设计:从三个翻车现场到一套可落地的设计框架
很多文章一上来就讲「短期记忆 / 长期记忆 / 向量检索」,读完你还是不知道自己的 Agent 该存什么、什么时候存、存错了怎么办。
这篇换个路子:先给你看三个真实翻车现场,让你建立"为什么需要记忆"的体感;再用一个三问框架把记忆系统设计的所有决策点串起来;最后用一张全景图对齐主流方案(mem0 / MemGPT / ChatGPT memory / LangChain / Zep),告诉你它们各自回答了哪几个问题、留了哪些坑。
读完你应该能拿着纸笔,画出一套属于自己业务场景的 Memory 设计,而不是闭眼背概念。
一、业务场景:三个翻车现场,三个问题
忘掉所有术语。想象你在用一个 AI 助手,看三个真实会发生的"翻车现场"。
翻车 1:记不住(跨会话失忆)
你:我对花生过敏,帮我记住。
助手:好的,我已经记住了。
(第二天,新开对话)
你:帮我订个午餐。
助手:好的!为您推荐宫保鸡丁——花生满满,很香哦!
你:???
为什么翻车:大多数 Agent 的"记忆"就是本次对话的消息列表。新开对话,消息列表清空——昨天的"花生过敏"随着上次对话结束就消失了。
用户感受:它每次都像第一次见我。
翻车 2:装不下(对话太长)
第 1 轮:描述问题
第 2-20 轮:各种尝试、报错、日志
第 21-49 轮:缩小范围,锁定方向
第 50 轮:你:所以按我们刚才的分析,下一步该查什么?
助手:(开始胡言乱语,忘记前面分析过的结论)
为什么翻车:模型一次能读的内容有物理上限(上下文窗口)。对话越长,塞回的历史越多:超限报错;没超限但很长 → 费用暴涨 + 注意力稀释(lost in the middle,模型对开头第 3 轮的约束经常视而不见)。
用户感受:聊得越久它越笨,而且越聊越贵。
翻车 3:管不了(记忆出错)
你:(口误)我对花生过敏。
助手:(记住了)
(此后每次推荐食谱都避开花生)
你:其实我说错了,我不过敏。
助手:(新记忆写进去,但旧记忆还在)
(之后行为随机:有时避开花生,有时不避)
多人场景更可怕:
同事 A:(说错)数据库密码每周三轮换
Agent:(记住了)
同事 B/C/D:(未来 3 个月,每次问密码都被告知"每周三轮换")
3 个月后才发现错了 → 已误导全团队 3 个月
为什么翻车:记住的只是文字,没有配套管理:不知道谁写的、错了怎么改、过时了怎么清。
用户感受:它记错的东西比它忘掉的东西更可怕——因为你会信任它。
三个翻车,三个问题,一个根源
| 翻车 | 问题 | 一句话 |
|---|---|---|
| 现场 1 | 记不住 | 会话结束,消息列表清零 |
| 现场 2 | 装不下 | 历史全量塞回,有上限、贵、笨 |
| 现场 3 | 管不了 | 光存文字不行,要能溯源/更正/过期 |
三个问题指向同一个根源:
模型是一条"金鱼"——每次调用都是一张白纸,只认识你这次塞给它的内容。
记忆系统就是围绕这条金鱼搭建的"外部设备":把重要的存到窗外(解决记不住),把塞回去的裁剪到装得下(解决装不下),把存的管理起来(解决管不了)。
你设计 Memory 要解决的,就是这三个问题,没有第四个。 后面所有的方案、框架、代码,都是对这三个问题的不同回答。
二、解决思路:一个本质定义 + 一个三问框架
2.1 本质定义:记忆系统 = 窗内窗外的搬运工
先建立物理直觉。模型不是流式"听"你说话,每次调用:
你发起一次调用:
把【所有内容】一次性发给模型
↓
模型读完这一坨,生成回复
↓
调用结束。模型对这次调用的记忆【立即清零】
三条关键认知:
- 一次性:读的是这次塞给它的完整内容,不是边听边想
- 无后台:这次没塞的信息,对模型等于不存在(它不会"去查"、“去回忆”)
- 即焚:回复完就失忆,下次调用又是一张白纸
那个"一次能塞多大"的物理上限,就是上下文窗口。窗口里装什么:
┌─────────────── 上下文窗口(比如 128K token)───────────────┐
│ system prompt(人设和规则) │
│ 工具清单(有哪些工具可调) │
│ 历史消息(这个对话之前说过什么) ← 会话越长这里越胖 │
│ 注入的外部内容(搜到的资料、取回的记忆) ← 记忆系统的主角 │
│ 你刚说的话 │
└────────────────────────────────────────────────────────────┘
窗口是稀缺资源,有三重稀缺:
- 物理上限(硬的):超限直接报错,没有商量。
- 钱(每轮重复计费):第 1 轮发 1K 付费 1K,第 50 轮发 50K 付费 50K,50 轮总付费 ≈ 1+2+…+50 ≈ 1275K,平方级增长。
- 注意力(越长越笨):lost in the middle,塞 100 轮历史,模型对开头的约束经常视而不见。窗口大了 ≠ 窗口好用了。
三重稀缺合起来:窗口不仅要"塞得下",还要"塞得少而准"。
窗口之外的所有信息(昨天的对话、用户档案、知识库)就是窗外:
┌────────────┐
│ 上下文窗口 │ ← 模型唯一能看到的世界
└────────────┘
↑ ↓ 只有两条路
┌─────────────────────────────┐
│ 窗外 │
│ · 昨天的对话(已结束) │
│ · 用户的档案、偏好 │
│ · 上上周的任务记录 │
│ · 公司知识库 / 文档 │
└─────────────────────────────┘
于是记忆系统有了本质定义:
记忆系统 = 一套在"窗内"和"窗外"之间搬运信息的机制。
搬运只有两个方向:
写入(窗内 → 窗外):这次对话里出现了重要的东西,挑出来存好
读取(窗外 → 窗内):下次需要时,把它取回来拼进窗口
行业黑话对照(后面会反复用):
| 黑话 | 白话 |
|---|---|
| 注入(inject) | 把窗外取回的内容拼进这次的请求里 |
| 召回(recall) | 读取方向的主力动作 |
| 提取(extract) | 写入方向"挑出重要东西"的动作 |
所有主流方案本质都是这个搬运工,差别只在三件事:搬什么、怎么搬、何时搬。 这三件事就是下面的三问框架。
2.2 三问框架:存什么 / 怎么取 / 怎么忘
任何记忆方案都只是对同样三个问题的不同回答。学会用这三问去拆,就不会迷路。
┌─────────────────────────────────────┐
│ 窗外(记忆库) │
└─────────────────────────────────────┘
↑ 问1 ↓ 问2 问3
存什么? 怎么取? 怎么忘?
(写入) (读取) (遗忘/治理)
│ │ │
挑重要的 需要时搬回 过时的、错的
搬出去 窗内 清理掉/管起来
三问顺序 = 一条信息的生命周期:进来(存)→ 被用(取)→ 退出(忘)。
问 1:存什么?(写入)
四件事要拍板:
a. 存原材料,还是存提炼物?
方案 A:把原始聊天记录整段存 (存录像)
方案 B:从对话提炼出一条条事实存 (存要点)
A 忠实但臃肿,检索时要再加工,没法直接管理;B 有损但干净,天生适合检索、去重、更新。主流选 B——长期记忆不存原始消息,存提炼后的结构化条目(带 subject / type / provenance / status)。原始消息归原始消息(会话日志/checkpoint),长期记忆的归长期记忆(提炼条目)。
b. 谁来判断"重要"? 三种流派:
| 流派 | 谁判断 | 例子 |
|---|---|---|
| 规则 | 关键词/格式命中 | 偏好关键词表(“我喜欢”“我偏好”) |
| 模型判 | LLM 读对话后决定 | mem0 的提取管线 |
| 显式 | 用户/模型主动说"记住这个" | ChatGPT memory、save_memory 工具 |
c. 什么时候写? 每轮写 / 会话结束写 / 模型随时调工具写。
d. 写之前查不查旧账? 新事实和已有记忆矛盾怎么办——直接并存会导致行为随机。要做冲突消解(mem0 用 LLM 决策 ADD/UPDATE/DELETE/NOOP,也可以用 subject 键比对 + supersede 标记取代)。
问 2:怎么取?(读取)
三件事要拍板:
a. 怎么知道"需要"? 匹配手段三档进化:
第 1 档:关键词匹配 内容含"过敏"就召回 (简单可测)
第 2 档:向量相似 语义接近就召回 (主流,需 embedding)
第 3 档:知识图谱 顺着关系推理召回 (高级,v2 候选)
b. 谁来发起取? 双通道:
被动通道:每轮自动把相关记忆拼进窗口(模型无感知,保鲜)
主动通道:给模型一个 search 工具,它觉得需要就自己查(灵活省空间)
成熟系统两条都要。
c. 取回来放哪、放多少? 记忆注入要吃窗口预算——注入 2K 记忆,可能就要先压缩掉 2K 旧历史。注入是只读搬运:每轮重新检索、重新注入、用完即弃,store 是唯一信息源(绝不把注入的记忆存回 state,否则下次提取会自我复制污染记忆库)。
问 3:怎么忘?(遗忘与治理)
两个层次:
a. 正常遗忘(信息过时):
| 方式 | 机制 | 例子 |
|---|---|---|
| TTL 过期 | 写时定好有效期,到期自动失效 | “促销到周五” |
| 新旧取代 | 新事实进来,旧的标记退役 | “过敏"→"不过敏” |
| 窗口滑出 | 只保留最近 K 条 | 滑动窗口 |
| 人工删除 | 用户/管理员手动清 | GDPR |
b. 治理(记忆出错/被滥用):
溯源:谁写的?什么时候?从哪次对话来的? (provenance)
更正:错了怎么改?直接删=销毁证据,正确做法是标记取代、留痕
权限:谁能读/写/删?个人记忆和共享记忆规则一样吗?
不对称原则:忘掉一条正确记忆,损失是一条信息;留着一条错误记忆,污染的是之后每一次回答。治理严格程度随共享范围升级。
2.3 分层模型:把三问安放到时间尺度上
三问框架告诉你"要回答哪些问题",但没告诉你"不同生命周期的信息要不要分开存"。这是下一个常见错误:
一个 ArrayList 存所有记忆:
- 今天的对话消息
- 上周的对话消息
- 用户偏好("喜欢深色模式")
- 任务工作状态("正在等 CI 通过")
- 公司知识库摘要
全混在一起,统一存取
为什么不行?生命周期完全不同:
今天的对话消息 活几小时
上周的对话消息 活几天
用户偏好 活几个月甚至永久
任务工作状态 活几分钟到几天
公司知识库 永久,更新慢
混在一起的两类灾难:
灾难 A(过度保留):今天的临时消息被当成永久记忆,半年后还在污染检索结果
灾难 B(误清永久):清理过期任务状态时,顺手把用户偏好也清了
分层不是为了好看,是因为生命周期不同的东西硬塞一起必然互相伤害。 照搬人脑(Atkinson-Shiffrin 模型:感觉记忆 → 短期记忆 → 长期记忆),Agent 三层:
┌─────────────────────────────────────────────────────────┐
│ 第 1 层:Working Memory(工作记忆) │
│ = 一次 Agent run 内的消息列表 │
│ 生命周期:一个 run(几十秒到几分钟) │
│ 存什么:当前这次推理的对话 │
│ 怎么取:不需要取,它本来就在窗内 │
│ 怎么忘:run 结束自动消失 │
├─────────────────────────────────────────────────────────┤
│ 第 2 层:Session Memory(会话记忆) │
│ = 一次会话的多轮消息累积 │
│ 生命周期:一次会话(几小时到几天) │
│ 存什么:这个会话从开头到现在的所有消息 │
│ 怎么取:每轮 run 开始时,拼进工作记忆 │
│ 怎么忘:会话结束归档或丢弃 │
├─────────────────────────────────────────────────────────┤
│ 第 3 层:Long-term Memory(长期记忆) │
│ = 跨会话持久化的事实条目 │
│ 生命周期:跨会话、跨 run,长期持久 │
│ 存什么:提炼后的结构化条目(不存原始消息) │
│ 怎么取:按需检索注入 │
│ 怎么忘:TTL 过期 / supersede 取代 / 管理员删除 │
└─────────────────────────────────────────────────────────┘
三层之间的搬运关系:
用户消息
↓
┌─────────────┐
│ Working Mem │ ← run 内活跃
└─────┬───────┘
│ run 结束同步回
↓
┌─────────────┐
│ Session Mem │ ← 会话内累积
└─────┬───────┘
│ 会话结束提取
↓
┌─────────────┐
│ Long-term │ ← 跨会话持久
└─────────────┘
↑
│ 下次会话开始时检索注入
└────── 回到 Working Mem
设计启示:你至少要为这三层各设计一套存取机制。很多系统只做了第 1 层(消息列表)就号称有"记忆",其实是翻车现场 1。
2.4 单会话内的问题:压缩 + 归档(解决翻车 2)
长对话的死亡螺旋:
第 1 轮:装窗 1K token
第 10 轮:装窗 10K token
第 50 轮:装窗 50K token ← 三重稀缺全中
三种解法:
| 解法 | 思路 | 优点 | 缺点 |
|---|---|---|---|
| 滑动窗口 | 只保留最近 K 条,老的直接丢 | 简单 | 丢得太狠 |
| 摘要替换 | 老消息总结成摘要替换原文 | 保留要点 | 丢了原文 |
| 压缩+归档 | 老消息总结成摘要 + 原文归档 + 保留最近 K 条 | 要点留窗内、原文可回溯 | 实现稍复杂 |
光"丢"不行,光"摘要"也不行,"摘要+归档"才是闭环。 这是 pi 的 compaction 思路,完整流程:
1. 估预算:所有消息字符数 / 4 > budgetTokens ?
否 → 原样返回(大多数轮次走这条)
是 → 进入压缩
2. 切分消息:
system 消息 → 全部保留(人设/规则不能动)
前 (n - keepRecent) 条 → 待归档段(要被压缩的)
后 keepRecent 条 → 近期保留段(原样留窗内,默认 2~6)
3. 调模型总结:待归档段 → 拼成长文本 → 调模型 → 拿到摘要
失败 → 降级截断(前 500 字符),不抛异常
(记忆系统的故障不应该拖垮主流程)
4. 重组窗口:[system..., summary(伪装成 user 消息), 最近 K 条]
5. 归档原文(关键!):
被压缩掉的原文 → 存成长期记忆条目(type=SUMMARY)→ 落库
两个关键设计点:
-
就地改写 state,而不是只改请求副本。因为持久化(Checkpoint)存的就是 state。如果只改请求副本不改 state:压缩时请求里是摘要+最近 K 条,但 state 里还是全部 50 条 → Checkpoint 存了 50 条 → 下次恢复读回 50 条 → 又超窗 → 又压缩 → 永远原地打转。就地改写 = 让持久化的 = 模型实际见过的。
-
摘要有损,归档兜底回溯。压缩本质是一种"受控遗忘":从工作记忆遗忘(腾空间),但归档到长期记忆(保可回溯)。
compaction 本质是三问框架里"怎么忘"在单会话内的回答:窗内的老消息被摘要替换(遗忘),原文归档进长期记忆(可回溯)。
2.5 跨会话的长期记忆:提取/检索/注入流水线(解决翻车 1)
翻车 1 的根因:会话结束 AgentState 丢弃,第二天新会话模型不知道昨天的"花生过敏"。需要两段管道:
写入管道(会话 A 结束时):从对话挑出"花生过敏",存进长期记忆库
读取管道(会话 B 开始时):从记忆库把"花生过敏"捞回,注入窗口
写入管道:四步走(写谨慎)
会话消息
↓ 第 1 步:提取候选
│ 只看 USER 消息(assistant 回复可能含幻觉,记下来等于把幻觉固化)
│ 关键词/模型判定命中 → 生成候选条目(type/importance/subject)
↓ 第 2 步:闸门 1——重要性阈值
│ importance >= 阈值 ? 否 → 丢弃
↓ 第 3 步:闸门 2——频控(去重)
│ 已有相同 subject 且 content 完全相同 ? 是 → 丢弃
↓ 第 4 步:闸门 3——冲突取代(supersede)
│ 已有相同 subject 但 content 不同 ?
│ 是 → 旧条目标记 SUPERSEDED(不物理删!)+ 新条目写 ACTIVE
│ 否 → 直接写 ACTIVE
store.write(entry)
四步全在写路径——写谨慎。 矛盾记忆不能并存,否则模型行为随机。
读取管道:三步走
1. 检索:
recall(scopes=[user:u1, channel:c1])
→ 隔离过滤(scope 不在列表里绝不返回)
→ 只回 ACTIVE
→ TTL 惰性过滤
→ 关键词/向量匹配
→ 按 createdAt 倒序 + limit
2. 渲染:
命中条目 → 拼成 "[Known memories]\n - [PREFERENCE] 记住我对花生过敏"
→ 包装成一条 user 消息
3. 注入:
窗口 = [system, [Known memories], 历史消息..., 用户刚说的话]
注入位置:system 之后、历史之前
关键:注入不落盘。 [Known memories] 这条消息不加进 AgentState。如果注入的记忆被存回 state,下次提取会从 messages 里再提取出这条 → 写回 store → 自我复制 → 记忆库被污染。注入是只读搬运,每轮重新检索、重新注入、用完即弃。
跨会话记忆本质是三问框架的完整闭环:存什么(四步写入)、怎么取(三步读取)、怎么忘(TTL + supersede)。
2.6 治理:记忆出错了怎么办(解决翻车 3)
翻车 3 的根因:光存文字不行,要能溯源/更正/过期。核心不对称:
遗忘(信息过时了):
- 损失:丢一条信息
- 代价:可接受(用户重说一遍就行)
治理(记忆出错了/被滥用了):
- 损失:错误信息被反复放大,污染每一次回答
- 代价:不可接受(信任被破坏,且很难发现错了)
忘掉一条正确记忆,损失是一条信息;留着一条错误记忆,污染的是之后每一次回答。
治理三件套:
工具 1:溯源(provenance)——这条记忆哪来的?
每条记忆带四元组:
sourceType 怎么来的
- USER_SAID 用户在对话里说的
- TOOL_RESULT 从工具执行结果提取的
- MODEL_DERIVED 模型总结/压缩/主动存的
- ADMIN_EDIT 管理员手写或修改的
actor 谁说的 / 哪个工具 / 哪个模型
runId 从哪次 run 提取的(管理员手写的为 null)
at 什么时候记录的
四个排查场景:
场景 A:"这条记忆对不对?" → 看 sourceType
场景 B:"谁说的这条?" → 看 actor
场景 C:"哪次对话产生的?" → 看 runId
场景 D:"这条是 3 个月前记的" → 看 at
provenance 是治理的地基。没有它,四个问题全部答不上来。
工具 2:supersede——错了怎么改?(关键机制)
最直觉的改法"删旧写新"为什么不行:
1. 销毁证据:3 个月后想查"为什么一直推荐错" → 旧记忆已删,查不到
2. 无法回滚:万一"不过敏"才是错的 → 旧的已删,回不去
3. 审计断裂:合规要求可追溯 → 删了就断了
正确做法 supersede(标记取代):
旧记忆:"对花生过敏" → status 从 ACTIVE 改为 SUPERSEDED(不物理删!)
新记忆:"不过敏" → status = ACTIVE, provenance 标记谁更正的
结果:
- 检索只回 ACTIVE → 模型只见"不过敏"(行为正确)
- 列出所有状态 → 管理员可见完整历史(可审计)
- 需要回滚?把旧的改回 ACTIVE、新的标 REJECTED 即可
supersede 的本质是"软删除 + 留痕"。物理删除只在 GDPR 场景才用。
工具 3:TTL——过时了怎么自动失效?
有些记忆天然有保质期。TTL 机制:写入时设 expireAt,查询时惰性过滤:
查询时:
entry.expireAt != null && now >= expireAt ?
是 → 跳过(不返回)
否 → 正常返回
为什么"惰性过滤"而不是"主动清理"?零额外组件(不需要后台线程/锁),代价是过期条目仍占内存(但不影响正确性)。TTL 的核心不是"怎么删",而是"怎么让它不再生效"——失效比删除重要。
治理本质是给"怎么忘"加上了"怎么管":supersede 留痕取代 + TTL 惰性过期 + provenance 溯源 + 管理面板审核。
2.7 多人共享一个 Agent 的记忆:scope + 待审闸
当 Agent 从个人助手变成团队 Agent,三个新问题:
问题 1:A 的个人隐私怎么保护? → 需要:个人记忆和频道记忆分开存
问题 2:A 说错了怎么办? → 需要:共享记忆写入后不能立刻生效,要先审
问题 3:B 怎么看到 A 留下的团队知识? → 需要:频道记忆对频道内所有人可见
三个问题指向同一个解:scope 机制 + 待审闸门。
scope 统一隔离与共享
scope 就是一个字符串:
agent:weather-bot Agent 自己的常识
user:u1 u1 的个人记忆(只对 u1 可见)
session:s1 会话级事实
task:r42 任务工作记忆
channel:c1 频道共享记忆(频道内所有人可见)
隔离与共享是同一个机制的两面:
隔离(user:u1):
B 的查询列表 = [user:u2, channel:c1]
store.query 绝不返回 user:u1 的条目
→ A 的个人隐私 B 看不到 ✅
共享(channel:c1):
A 的查询列表 = [user:u1, channel:c1]
B 的查询列表 = [user:u2, channel:c1]
两人都把 channel:c1 放进查询列表
→ 频道记忆两人都能看到 ✅
隔离和共享的差别,仅仅是scope 在不在你的查询列表里。不需要两套系统。
隔离的实现位置:store 强制,不靠自觉。 store.query 内部那一行 .filter(e -> query.scopes().contains(e.scope())) 就是隔离的全部。调用方传 [user:u2],store 绝不返回 user:u1 的条目。
待审闸门:为什么共享记忆要先审后用
个人 vs 共享的风险不对称:
个人记忆写错了:
- 只影响自己
- 损失:一人一次回答偏了
→ 可以宽松:写完就 ACTIVE
共享记忆写错了:
- 影响频道内所有人
- 被反复检索、反复放大
- 可能几个月才发现
→ 必须严格:先审后用
待审机制完整流程:
T1 A 说"密码每周三轮换"
→ scope=channel:c1 → status=PENDING_REVIEW
→ store: [PENDING_REVIEW] 密码每周三轮换
T2 B 检索 channel:c1
→ store.query 只回 ACTIVE
→ B 看不到这条 ✅ 防污染
T3 管理员审核:
→ 正确 → approve(id) → ACTIVE
→ 错误 → reject(id) → REJECTED
T4 审核后:
→ 正确:B 能看到了 ✅
→ 错误:B 永远看不到 ✅(错误被挡在门外)
待审闸门的本质:在"写入"和"生效"之间插入人工审核——写入可以自动,但生效必须人工确认。个人记忆可以"先写后治",共享记忆必须"先审后用"。治理时机的前移。
| 问题 | 解法 | 机制 |
|---|---|---|
| A 的隐私 | 个人记忆 scope=user:u1,B 查询列表不含它 | scope 隔离 |
| A 说错了 | 频道记忆写入默认 PENDING_REVIEW | 待审闸 + supersede |
| B 看到团队知识 | 频道记忆 scope=channel:c1,B 查询列表含它 | scope 共享 |
三个问题,一个 scope 机制全解。
三、主流框架实现方案:一张全景图 + 三问拆解
讲完设计思路,来看看主流方案各自回答了三问里的哪些、留了哪些坑。用三问去拆,就不会迷路。
3.1 主流方案一页全景图
| 方案 | 派系 | 存什么 | 怎么取 | 怎么忘 | 治理 | 设计借鉴点 |
|---|---|---|---|---|---|---|
| LangChain Memory 家族 | 上下文管理 | 不存(就是消息) | buffer/window/summary | 窗口滑出 | 无 | 反面教材:只做单会话,不解决跨会话 |
| pi compaction | 上下文管理 | 超窗归档原文 | 压缩后注入 | 压缩归档 | 归档可回溯 | 压缩+归档闭环的范本 |
| MemGPT/Letta | 操作系统 | 分层存储,模型自编辑 | 模型自检索 | 模型自删 | 弱 | self-editing 思想(给模型 memory 工具) |
| mem0 | 知识提取 | LLM 提炼 + ADD/UPDATE/DELETE/NOOP | 向量检索注入 | LLM 决策删除 | 溯源 | 提取-冲突消解管线同构 |
| ChatGPT memory | 知识提取 | 双通道(saved + 历史引用) | saved 全量注入 + 历史语义检索 | 用户设置可删 | 用户可见可删 | 双通道读取 |
| Claude memory tool | 知识提取 | 文件目录,模型显式写 | 模型显式读 | 模型/用户删 | 弱 | 显式保存豁免阈值 |
| Zep/Graphiti | 知识提取 | 时序知识图谱 | 图检索 | 双时间轴过期 | 时序审计 | v2 候选(时序+图谱) |
| Claude Tag 频道记忆 | 共享治理 | 频道共享条目 | scope 检索 | 管理员管理 | 强(核心) | scope 隔离 + 待审闸 |
3.2 用三问拆解四个代表方案
mem0(知识提取派代表):
存什么:会话结束 → LLM 提炼事实 → 与旧记忆比对 → ADD/UPDATE/DELETE/NOOP
怎么取:向量化 → 语义相似检索 → 每轮注入
怎么忘:UPDATE/DELETE 由 LLM 决策 + 用户管理
特点:写入用 LLM 做冲突消解(四选一决策),读取用向量检索。治理有溯源但偏弱。适合想要"自动提取 + 语义检索"的场景,但 LLM 决策写入有成本和不确定性。
ChatGPT memory(双通道代表):
存什么:双通道——模型自动存的 saved memories + 历史对话语义引用
怎么取:saved memories 每轮全量注入 + 历史按语义检索
怎么忘:用户在设置里逐条可见、可删
特点:saved memories 全量注入(量小但重要),历史对话按需语义检索(量大但参考性)。治理交给用户可见可删。适合"用户偏好少而精 + 历史可语义回溯"的个人助手场景。
MemGPT/Letta(操作系统派代表):
存什么:分层存储(core memory / archival memory),模型用工具自编辑
怎么取:模型用工具自检索(觉得需要就自己查)
怎么忘:模型用工具自删
特点:把记忆管理权交给模型自己(self-editing),像操作系统管内存一样管记忆。治理弱。适合"长程自主 Agent"场景,但对模型能力要求高、不可控风险大。
LangChain Memory 家族(上下文管理派代表):
存什么:不存(就是消息列表本身)
怎么取:buffer(全量)/ window(最近 K 条)/ summary(摘要)
怎么忘:窗口滑出
特点:只解决单会话内的上下文管理(翻车 2),完全不解决跨会话(翻车 1)和治理(翻车 3)。是最基础的,也是最容易用错的——很多人以为接了 LangChain Memory 就有"记忆"了,其实只是个消息裁剪器。
3.3 怎么选:按你优先解决哪个翻车
你的痛点是"聊太久变笨变贵"(翻车 2)
→ 上下文管理派:pi compaction(压缩+归档)/ LangChain window+summary
你的痛点是"跨会话记不住用户"(翻车 1)
→ 知识提取派:mem0(LLM 提取+向量检索)/ ChatGPT memory(双通道)
你的痛点是"记忆出错污染全团队"(翻车 3)
→ 共享治理派:Claude Tag(scope+待审)/ 自建治理三件套(provenance+supersede+TTL)
你的痛点是"长程自主 Agent 要自己管记忆"
→ 操作系统派:MemGPT/Letta(self-editing)
现实是组合:成熟的 Memory 系统不是复刻某一个方案,而是按三问框架从各派取最合适的部件组合——
pi 的 compaction (解决装不下)
+ mem0 的提取-冲突消解管线 (解决记什么,判定器可用规则或 LLM)
+ MemGPT 的 self-editing 思想 (给模型 memory 工具,主动存取)
+ Claude Tag 的频道治理 (scope 隔离 + 待审)
+ 治理三件套 (provenance/supersede/TTL)
3.4 向量检索:现在要不要用
第 5 层检索的硬伤是字面匹配:
硬伤 1:存了"我对花生禁忌",查"过敏" → 查不到
硬伤 2:存了"用户不喜欢坚果类",查"花生" → 查不到
向量检索(embedding)怎么解决:
步骤 1:向量化——"我对花生过敏"和"花生是我的饮食禁忌"向量很接近
步骤 2:存进向量库(每条记忆多存 vector 字段)
步骤 3:检索时查询也变成向量,找距离最近的条目
v1 要不要用向量?三个考量:
理由 1(用):语义匹配,解决关键词的字面硬伤
理由 2(不用):依赖重——需要 embedding 模型 + 向量库(FAISS/Milvus/pgvector)
理由 3(关键):检索方式别焊死——query 抽象好,v1 关键词、v2 向量、v3 图谱,调用方都不动
设计启示:检索方式是可替换的实现细节,不是架构。把
query(MemoryQuery)抽象留好,v1 用关键词保证可测,v2 换向量不动调用方。
3.5 RAG 与 Memory 的边界
容易混淆的两个概念:
RAG(Retrieval-Augmented Generation)
检索的是:外部世界知识(文档库 / 知识库 / 网页)
例子:"公司报销政策是什么?" → 检索报销制度文档
回答:"世界上存在什么"
比方:查百科全书(客观知识,谁查都一样)
Memory
检索的是:Agent / 用户的过去(之前的对话 / 提取的事实 / 偏好)
例子:"帮我订午餐" → 检索"用户对花生过敏"
回答:"我们之间发生过什么"
比方:翻日记本(私人记忆,因人而异)
| 维度 | RAG | Memory |
|---|---|---|
| 检索内容 | 文档 chunks | 记忆条目 |
| 隔离需求 | 弱 | 强(个人记忆严格隔离) |
| 治理需求 | 弱(文档错了改文档) | 强(要溯源/更正/待审) |
| 时效性 | 低(文档更新慢) | 高(用户刚说的下一轮就要用) |
| 写入方式 | 离线灌库 | 运行时实时提取 |
Memory 和未来的 RAG 文档库是两套东西,不混做。
四、设计清单:照着画你自己的 Memory
读完上面,你应该能回答下面这张清单。如果有一格答不上,说明你的设计还有洞。
4.1 三问清单
| 三问 | 你要拍的板 | 参考选项 |
|---|---|---|
| 存什么 | 原材料 or 提炼物? | 主流选提炼物(结构化条目) |
| 存什么 | 谁判断重要? | 规则 / 模型判 / 显式 |
| 存什么 | 什么时候写? | 每轮 / 会话结束 / 模型调工具 |
| 存什么 | 矛盾怎么消解? | LLM 四选一 / subject 键 + supersede |
| 怎么取 | 怎么匹配? | 关键词 / 向量 / 图谱 |
| 怎么取 | 谁发起? | 被动注入 / 主动 search 工具 / 双通道 |
| 怎么取 | 注入放哪、放多少? | system 之后历史之前;吃预算要压缩 |
| 怎么取 | 注入落不落盘? | 不落盘(防自我复制污染) |
| 怎么忘 | 过时怎么失效? | TTL 惰性过滤 / 滑动窗口 |
| 怎么忘 | 错了怎么改? | supersede 留痕取代(别物理删) |
| 怎么忘 | 谁能读/写/删? | scope 查询列表 + 管理面板 |
4.2 分层清单
| 层 | 你用什么承载 | 生命周期 | 存取机制想清楚没 |
|---|---|---|---|
| Working Memory | 消息列表 / AgentState | 一个 run | —— |
| Session Memory | 会话历史 | 一次会话 | run 开始拼回 / run 结束同步 |
| Long-term Memory | 记忆库 / Store | 跨会话持久 | 写入管道 + 读取管道 |
4.3 治理清单
| 治理项 | 你的机制 | 触发时机 |
|---|---|---|
| 溯源 | provenance 四元组(sourceType/actor/runId/at) | 写入时必须带 |
| 更正 | supersede(旧 SUPERSEDED + 新 ACTIVE) | 发现错误时 |
| 过期 | TTL 惰性过滤 | 查询时 |
| 共享审核 | channel scope 默认 PENDING_REVIEW | 写入时定状态 |
| 隔离 | store.query 强制 scope 过滤 | 查询时 |
4.4 防错清单(踩过的坑)
- 长期记忆别存原始消息,存提炼后的结构化条目
- 只提取 USER 消息,别提取 assistant 回复(可能含幻觉)
- 注入的记忆别存回 state(防自我复制污染)
- compaction 要就地改写 state,别只改请求副本(防 Checkpoint 死循环)
- 压缩失败要降级截断,别抛异常(记忆故障不拖垮主流程)
- 错误记忆用 supersede 别物理删(保审计/可回滚)
- 隔离靠 store.query 强制,别靠调用方自觉
- 共享记忆先审后用,个人记忆先写后治
- 检索方式别焊死,query 抽象留好(v1 关键词 → v2 向量)
- Memory 和 RAG 分开做,别混
五、收束:一句话记住整篇
记忆系统的本质是一句话:
模型是一条只有窗口那么大记忆的金鱼;记忆系统就是围绕这条金鱼干活的搬运工——把重要的搬出去存好,把需要的搬进来,顺手把窗口收拾得又小又准,把存的管理得能溯源、能更正、能过期。
所有主流方案本质都是这个搬运工,差别只在三件事:搬什么(存什么)、怎么搬(怎么取+怎么忘)、何时搬(写入时机+注入时机)。
你设计 Memory 要做的,就是拿着三问框架 + 分层模型 + 治理三件套,对着自己的业务场景(三个翻车里你优先解决哪个),从主流方案里取最合适的部件组合——而不是闭眼复刻某一个框架。
三个翻车,三问框架,一张全景图。这就是设计 Agent Memory 的全部。
更多推荐


所有评论(0)