2026年8月31日 约 17 分钟 Hyr1sky

Agent Memory:从生命周期到工程实践

从记忆分类与生命周期出发,梳理 Agent Memory 的写入、合并、检索与遗忘机制,并比较 Mem0、Zep、Letta、MemOS 与 LangGraph 等主流方案。

Agent Memory 不只是一个记忆数据库,而是一套围绕记忆写入、合并、检索和遗忘建立的生命周期系统。数据库只是其中的存储载体,真正困难的是决定什么值得记、如何处理冲突,以及当前任务应该取回什么。它的作用范围比 Context Engineering 更长,会跨会话、跨周期运行;Context Engineering 则负责决定本轮最终把哪些记忆放进 prompt。

1. Agent Memory 的设计

1.1 记忆的分类

简单划分:

  • 短期记忆: 当前会话(session)级
  • 长期记忆: 跨 session 级

职责划分:

  • Episodic Memory(情景记忆): 背景、事件
  • Semantic Memory(语义记忆): 偏好、经验、持久事实
  • Procedural Memory(程序记忆): 程序性流程、规则

还有一些其他的划分,比如按照存储划分为 token 级记忆、参数化记忆、潜在记忆等,但我认为意义不大,暂时不归纳进来。

1.2 记忆操作的生命周期

编码(Encode) → 存储(Storage) → 提取(Retrieval) → 巩固(Consolidation) → 反思(Reflection) → 遗忘(Forgetting)

操作说明工程实现
编码将原始交互转化为可存储的结构化信息LLM 提取事实三元组、生成摘要
存储将编码后的信息持久化写入向量库 / 图数据库 / 参数
提取根据上下文检索相关记忆向量检索 + BM25 + 图遍历
巩固将短期记忆转化为长期记忆异步任务:对话摘要 → 实体库
反思主动回顾评估记忆内容,优化决策任务完成后提取 Meta-Knowledge
遗忘淘汰低价值或过时记忆权重衰减 + 冲突标记废弃

这几个操作并不是严格执行一次的线性流水线。一次对话中可能多次发生 Retrieval;Consolidation、Reflection 和 Forgetting 通常由后台任务周期性执行;新证据也可能重新激活或覆盖旧记忆。更合理的理解是:它们共同组成一个不断循环的 Memory Lifecycle。

1.3 短期记忆与长期记忆

1.3.1 短期记忆

短期记忆是 Agent 中当前 session 的状态,包含对话文本、工具结果、执行计划和等待补全的参数等,是当前任务状态的主要载体。它们不一定全部进入当轮 prompt,而是由 Context Engineering 根据相关性和 token budget 选择、压缩后注入。

短期记忆主要依托于当前模型的上下文能力。如果上下文窗口较小,短期记忆膨胀会导致 Agent 能力大幅下滑,并出现 Context Engineering 中常见的“上下文失效”问题。

1.3.2 长期记忆

长期记忆是跨会话、跨周期的,依赖于外部存储,通常包含偏好、事实和决策等。整个长期记忆我们可以大致分为 record 和 retrieve 两个阶段,对应数据库的增删改和查。

Record:记忆写入

Record 不是简单地把本轮对话写入数据库,而是一个从原始对话中抽取高价值信息,并与已有记忆进行合并的过程。完整链路可以概括为:

Conversation Event → Consolidation Trigger → Memory Candidate → Reconciliation → Memory Commit

首先是 Consolidation Trigger,也可以理解为一个 gate,用于判断当前是否需要启动记忆巩固。触发条件可以是会话结束、静默达到一定时间、累计达到一定的 turn/token 数、上下文即将压缩,或者用户明确说出“请记住”。这个 gate 只决定是否启动任务,不代表抽取到的信息一定会写入长期记忆。

任务启动后,框架会固定本次处理的源消息范围,再通过 LLM 对短期记忆进行语义提纯,过滤重复对话和无价值噪声,生成结构化的 Memory Candidate。例如从“以后写接口示例都用 FastAPI”中抽取出用户的技术栈偏好,同时记录来源消息、作用域、时间和置信度。

候选记忆不能直接写库,还需要与已有记忆进行 Reconciliation:不存在的事实执行 INSERT,相同事实执行 REINFORCE,新事实可以 SUPERSEDE 旧事实,不同项目或时间范围内的事实可以 KEEP BOTH,用户明确纠正的内容则执行 RETRACT。工程上最好保留不可变的 Memory Revision,而不是直接覆盖旧值,否则很难处理事实变化、用户纠错和后续审计。

Record 阶段需要重点考虑以下问题:

  • 幂等: 后台任务通常是 At-Least-Once 投递,同一任务可能被执行多次。幂等 Key 应由租户、会话、固定的源消息范围和抽取器版本组成,而不是根据 LLM 抽取结果文本生成。随机生成的 batch ID 如果在每次重试时发生变化,也无法保证幂等。
  • 语义去重: 两个不同会话可能抽取出语义相同的事实,因此除了任务幂等,还需要基于 subject + predicate + scope 形成稳定的 Memory Identity,用来判断两条记忆是否描述同一个对象和属性。
  • 并发更新: 多端会话可能同时更新同一条记忆,不能简单使用 Last-Write-Wins。可以在 Memory Identity 粒度上使用乐观锁或 MVCC;如果提交时发现版本已经变化,就重新读取最新版本并再次执行语义合并。
  • 一致性: 普通对话中隐式抽取的偏好可以采用异步 Best-Effort,不应阻塞本轮回答。但用户明确发出的“记住”或“忘记”指令需要更强的一致性、状态确认和审计能力。
  • 索引更新: 关系数据库可以作为正式记忆的 Source of Truth,向量库作为可重建的检索索引。两者之间可以通过 Transactional Outbox 异步同步,避免数据库写入成功但向量索引更新失败造成双写不一致。
Retrieve:记忆检索

Retrieve 是根据当前用户输入、任务目标和运行状态,从长期记忆中召回候选信息的过程。它不是简单地把用户的全部记忆取出来,而是需要经过作用域过滤、相关性召回、重排和预算裁剪:

Query Construction → Scope Filtering → Hybrid Retrieval → Rerank → Validation → Context Injection

首先根据当前问题和 Agent 状态构造检索 Query,然后按 tenant、user、project、task 等作用域做权限过滤,避免跨用户或跨项目读取记忆。召回阶段可以组合结构化查询、向量检索、BM25 和图遍历:结构化字段适合查找明确的偏好和事实,向量检索适合查找语义相关的情景记忆,图遍历则适合处理人物、项目和事件之间的关系。

初步召回的记忆还需要进行 Rerank,除了语义相似度,还要综合考虑来源可信度、记忆新鲜度、作用域匹配度、历史使用效果和当前任务价值。已经被新版本替代、明确撤销或者与当前输入冲突的记忆,不应该直接注入上下文。

最后,检索系统根据 token budget 返回一组候选记忆,由 Context Engineering 决定哪些内容真正进入当前 prompt、以什么顺序和格式进入。也就是说,Memory Retrieve 负责“当前可能需要回忆什么”,Context Engineering 负责“本轮最终让模型看到什么”。

Retrieve 阶段需要重点关注:

  • 权限与隔离: 检索必须先做租户、用户和作用域过滤,再进行语义召回,不能只依赖向量相似度。
  • 冲突处理: 当前用户输入的优先级通常高于历史记忆;当新输入与旧记忆冲突时,应优先采用当前明确陈述,并将冲突反馈给后续 Record 阶段。
  • 时效性: 偏好、职位、项目状态等记忆可能随时间变化,应优先返回当前有效版本,而不是简单返回相似度最高的历史记录。
  • 可解释性: 每条召回记忆应保留 provenance,必要时能够解释它来自哪次对话、何时形成、当前置信度是多少。
  • 效果评估: 不能只观察 Recall@K,还需要评估记忆是否真正提升任务成功率,以及错误记忆注入率、过期记忆命中率和无效 token 占用。

简单来说,Record 管理的是“哪些经历值得长期保存,以及如何安全地更新”,Retrieve 管理的是“当前任务应该回忆哪些内容”。Memory System 负责提供候选记忆,最终是否把这些记忆放进本轮上下文,仍然属于 Context Engineering 的职责。

2. 主流的记忆框架

这里首先要注意,这些框架并不完全处于同一个抽象层:有些是可以嵌入现有 Agent 的 Memory Service,有些是完整的 Agent Runtime,还有一些更接近研究型的 Memory Operating System。因此选择时不应该只比较功能数量,而应该先判断自己需要的是“给已有 Agent 加记忆”,还是“直接采用一套有状态 Agent 架构”。

框架核心定位更适合的场景主要注意点
Mem0可插拔的长期记忆层,提供 add/search/update/delete 等高层接口给现有聊天应用或 Agent 快速增加用户偏好和跨会话事实使用门槛低,但抽取策略和 OSS/Platform 能力变化较快,需要固定版本并自己做效果评估
Zep托管的 Temporal Context Graph 与 Context 组装服务;Graphiti 是其开源图引擎人物关系、业务状态和事实会随时间变化的应用图结构和时间语义更强,但写入通常是异步的;自托管 Graphiti 不等于获得完整 Zep Cloud
Letta以 Git-backed MemFS 为核心的 Stateful Agent Runtime长期运行的个人助手、AI Coworker 和自主 Agent不只是一个 Memory SDK;Agent 可以主动修改和版本化记忆,但需要控制常驻 Context 和自主写入范围
MemOS将文本、Activation/KV、参数化记忆统一管理的 Memory OS研究多形态记忆、跨 Agent 共享和完整生命周期治理Simple 模式可以快速试验,完整多 Cube/图结构部署则明显更重
LangGraph PersistenceCheckpointer + Store 组成的状态持久化基础设施已经使用 LangGraph,希望自己掌控写入和检索策略透明、可组合,但它主要提供基础设施,Memory Candidate、Consolidation 和冲突策略仍需自己实现

2.1 Mem0

Mem0 更像一个可以插入现有应用的 Memory Service。应用通过 add 提交对话或事实,Mem0 负责抽取可记忆信息并写入后端;运行时再通过 searchuser_idagent_idrun_id 等作用域召回相关记忆。它的价值是把 Record 和 Retrieve 封装成少量接口,比较适合快速验证“长期记忆是否真的提升产品体验”。

从实操角度看,Mem0 最容易上手,但也最容易让人误以为接入 SDK 就等于完成 Memory System。实际使用时仍然需要关注:

  • user_id / agent_id / run_id 的作用域设计,否则很容易串记忆或者查不到记忆;
  • LLM 自动抽取是否把假设、引用和临时需求错误固化成事实;
  • 写入后的最终一致性、重复记忆和删除传播;
  • OSS 与托管 Platform 的特性并不总是完全一致,升级时应关注抽取算法、Graph 能力和 API 的变化;
  • 线上至少记录 Memory 写入接受率、重复率、检索命中率和错误记忆注入率。

如果是第一次实践 Agent Memory,我认为 Mem0 比较适合做 POC,因为能快速看到 add/search 的完整闭环;但正式上线前,最好在它外面再包一层自己的 Memory Service,用来统一作用域、权限、幂等和审计策略。

2.2 Zep / Graphiti

Zep 的重点不是把每条对话单独存成向量,而是从聊天、业务数据和文档中抽取实体、关系和带时间范围的事实,形成 Temporal Knowledge Graph。原始输入作为 Episode 保留,实体成为 Node,事实和关系成为 Edge;当事实发生变化时,可以让旧事实失效但保留历史,而不是直接覆盖。

这套思路适合下面的问题:

用户现在向谁汇报?
这个项目之前使用过哪些技术方案?
某个事实是在什么时候发生变化的?
人物、公司、项目之间有什么关系?

Zep Cloud 提供较高层的 Memory、Graph 和 Context API;Graphiti 则是其时间知识图谱方向的开源基础组件。两者共享时间图谱思想,但并不是同一个可替换部署包:Graphiti 不包含 Zep Cloud 的完整 Context Block、治理和规模化服务,旧的 Zep Community Edition 也已经不适合作为新项目的自托管方案。

实操时需要注意,图记忆的成本主要不在查询语句,而在实体解析和关系治理。同一个人可能被写成“老王”“王经理”“王强”,如果实体消歧不稳定,图会快速膨胀。写入通常也是异步处理,因此最近几轮消息仍应作为 Short-term Memory 直接保留,不能假设刚写入的事实一定已经能够从图中召回。

2.3 Letta

Letta 源自 MemGPT 的思路,它不是单纯的外部记忆库,而是一套 Stateful Agent Runtime。需要特别注意版本语境:旧资料经常使用 Core Memory、Recall Memory 和 Archival Memory 描述 Letta V1;当前主线则以 MemFS 为核心,把长期记忆组织成一个由 Agent 自己维护的 Git-backed Markdown 仓库。

  • **system/ 记忆:**每轮直接进入 System Prompt,适合身份、关键偏好和高频规则;
  • **其他记忆文件:**只把目录树和描述暴露给 Agent,需要时再读取全文;
  • **skills/:**保存经过验证、可以复用的程序性流程;
  • **Git History:**提供 commit、diff、回滚和并发整理的版本边界。

Letta 的特点是 Agent 可以通过普通文件工具主动整理自己的记忆;Dreaming 机制还可以使用后台 Agent 回顾近期对话并做 Consolidation。默认 MemFS 主要使用文件搜索,并不等于已经配置了向量检索;如果需要 Semantic/Hybrid Search,还要额外安装并建立索引。

这种文件化设计很直观:人可以直接阅读 diff,也可以把错误记忆回滚。但 system/ 内容会持续占用 token,不能把日志和长文全部放进去;记忆修改只有 commit 后才算进入正式版本,Cloud 场景还要考虑 push 和跨环境同步。它更适合希望采用整套有状态 Agent 架构的项目,而不是只想给现有接口增加一次向量检索的场景。

2.4 MemOS

MemOS 尝试把 Memory 提升为一等系统资源。它使用 MemCube 统一封装多种记忆:

  • Plaintext Memory:文本、文档、向量和图结构等可解释记忆;
  • Activation Memory:KV Cache、Hidden State 等推理中间状态;
  • Parametric Memory:被蒸馏进模型权重、Adapter 或 LoRA 的稳定能力。

在此基础上,通过 MemScheduler、MemLifecycle 和治理模块处理记忆的调度、转换、版本、归档和权限。它与普通“向量库 + 摘要”的差别在于,目标不是只管理文本事实,而是统一不同成本、生命周期和可迁移性的记忆载体。

从实践角度看,MemOS 的概念很适合帮助理解未来 Memory System 的完整形态,但如果当前目标只是为业务 Agent 保存用户偏好,直接采用这套抽象可能过重。更合理的学习顺序是先实现 Plaintext Memory 的 Record/Retrieve 闭环,再研究 Activation Memory 和 Parametric Memory 是否真的有业务价值。

2.5 LangGraph Persistence

LangGraph 把短期记忆和长期记忆拆得比较清楚:Checkpointer 按 thread_id 保存 Graph State,使会话可以恢复;Store 则按照 namespace 和 key 保存跨 thread 的 JSON 文档,并可以建立语义索引。

它的优点是基础设施透明,特别适合已经使用 LangGraph 的项目。但 Checkpointer 解决的是状态持久化,不等于长期记忆;Store 解决的是存储和检索,也不会自动替你判断什么值得记。因此 LLM 抽取、Memory Candidate、Consolidation、冲突合并和 Forgetting 仍然需要业务自己实现。

2.6 一套最小可运行的实践方式

如果是为了学习原理,我认为没有必要一开始就同时引入向量库、图数据库和复杂 Memory Framework。可以先实现下面这条最小闭环:

用户输入
→ 按 user_id + project_id 检索 Top-K 记忆
→ 过滤过期和冲突内容
→ 注入 Context
→ 调用 LLM
→ 异步抽取本轮 Memory Candidate
→ 人工规则校验后写入 PostgreSQL + pgvector

一个最小 Memory Record 至少包含:

{
  "memory_id": "...",
  "subject_id": "user-123",
  "type": "preference",
  "content": "用户偏好使用 Python 示例",
  "scope": "project:agent-blog",
  "source_event_ids": ["event-101"],
  "confidence": 0.92,
  "valid_from": "2026-08-30T10:00:00Z",
  "status": "active",
  "version": 1
}

实操时可以先从少量高价值类型开始,例如全局偏好、项目约束和已确认决策。不要一开始就记录全部聊天摘要;Memory 系统最常见的问题不是“记不住”,而是记得太多、记错了以后又没有失效机制。

2.7 选型建议

  • 已有 Agent,只想快速增加长期用户记忆:先试 Mem0;
  • 事实关系复杂,并且需要回答“什么时候发生变化”:考虑 Zep / Graphiti;
  • 想构建长期自治、可以主动管理记忆的 Agent:考虑 Letta;
  • 已使用 LangGraph,想完全掌控策略:使用 Checkpointer + Store 自己实现;
  • 研究统一文本、KV Cache 和参数化记忆:再进一步了解 MemOS。

3. 记忆的高级演化机制

3.1 记忆反思与合成

自我反思(Self-Reflection):任务完成后回顾过程,从成功和失败中提取可复用经验,例如“调用工具前应先确认时区”或“该用户更容易接受对比表格”。反思结果不应该直接覆盖事实记忆,而应该作为低置信度的 Procedural Memory Candidate,经过多次任务验证后再提升权重。

细粒度反思闭环(Reflect Loop):在长任务的重要节点执行“观察结果 → 判断偏差 → 调整计划”的小循环。它能提升复杂任务稳定性,但需要限制反思次数和 token budget,否则容易产生无效自省,甚至让错误假设被反复强化。

记忆聚类与合并(Clustering & Consolidation):将大量相似的 Episodic Memory 聚类,再提炼成更稳定的 Semantic 或 Procedural Memory。例如多次观察到用户要求 Python 示例,可以逐渐巩固成稳定偏好;原始事件仍应保留 provenance,以便后续纠错。

3.2 记忆清理与遗忘

我认为这是最重要的一个环节。如果说目前业界的记忆架构很多是出于对人脑记忆习惯的模仿,那么在存储方面我们已经做得还不错,但遗忘机制仍不成熟。之前有一段时间很火的 dream 概念,就是模拟人脑在睡眠时对记忆进行归纳,包含浓缩、合并与丢弃。但目前的 Agent Memory 系统仍然倾向于多记,这会导致存储和候选集合持续膨胀,给检索、冲突处理与上下文注入带来更多复杂度。

目前主流的清理与遗忘手段仍然是根据时间戳以及权重衰减公式,例如:

score = relevance × importance × decay(t)

这套机制来自《Generative Agents》提出的三维检索模型。实际工程里,不建议每次在向量库里对全量记忆计算时间衰减,更稳的做法是向量库先做静态语义召回,再在 Reranker 阶段实时应用动态调整。

除了时间衰减,实际工程里还可以组合以下策略:

  • **TTL:**临时任务状态、短期计划到期后自动失效;
  • **Supersede:**新事实替代旧事实,但保留 Revision 和 provenance;
  • **Usage Decay:**长期没有被检索或检索后没有产生价值的记忆逐渐降权;
  • **Quota:**限制每个用户、项目和记忆类型的容量,超过后优先合并低价值内容;
  • **Episode Compression:**把大量相似事件压缩成摘要或稳定规律;
  • **Hard Delete:**隐私删除和用户“忘记”指令必须从主存储、索引和缓存中真正删除,不能只降低检索权重。

遗忘并不一定意味着物理删除。多数情况下可以先标记为 inactive、expired 或 superseded,使其退出默认检索;只有隐私、合规和用户明确删除的场景才执行不可恢复的 Hard Delete。

4. 长期记忆的检索

一套比较实用的检索顺序是:

Hard Filter → 多路召回 → RRF/加权融合 → Rerank → 去重与冲突处理 → Token Budget 裁剪 → Context Injection

Hard Filter 应该始终发生在语义召回之前,先限定 tenant、user、project、权限和有效时间,避免把不应该访问的记忆召回后再过滤。

4.1.1 Sparse Retrieval:稀疏检索

以 BM25 为代表的稀疏检索依赖关键词匹配,适合处理专有名词、错误码、实体名称和用户明确使用的表达。它不需要向量模型,结果也比较容易解释,但对同义改写和隐含语义的召回能力有限。

4.1.2 Dense Retrieval:稠密检索

稠密检索将 Query 与 Memory 编码为向量,通过语义相似度召回候选,适合发现措辞不同但含义接近的记忆。它能够补足关键词检索的语义盲区,但也更容易召回“主题相近、事实并不适用”的内容,因此必须结合 scope、status 和时间等元数据过滤。

4.1.3 混合策略

多路召回的常见融合方式:

  • RRF(Reciprocal Rank Fusion):几乎不用调参,适合冷启动,按排名倒数加权融合。
  • Linear weighted(α·dense + (1-α)·sparse):可调,但需要标注数据校准权重。
  • Cross-encoder Reranker:召回阶段取并集,精排阶段统一打分,对长尾 query 更有帮助。

4.2 元数据过滤 Hard Filters

标签硬过滤,如 userID、组织ID、项目、时间范围、记忆状态和业务标签等。作用域设计应该尽量在 Record 阶段完成,否则 Retrieve 阶段很难判断某条偏好是全局偏好,还是只适用于某个项目。

4.3 Rerank 与 Context Injection

召回结果不能直接全部放进 prompt。Reranker 除了语义相关性,还应该考虑 recency、importance、confidence、scope match 和历史使用效果。已经被覆盖、撤销或与当前用户陈述冲突的记忆应当降权或移除。

最终注入时,最好将记忆标记为“历史参考信息”,同时保留来源和时间,不要把检索结果伪装成 System Instruction。当前用户的明确输入通常应该高于历史记忆;如果发生冲突,应优先相信当前输入,并把冲突交给下一次 Record 阶段处理。

4.4 实践中的评估指标

Memory 系统不能只看向量检索的 Recall@K,还需要看端到端效果:

  • Memory Write Acceptance Rate:抽取出的候选中有多少真正被接受;
  • Duplicate / Conflict Rate:重复和冲突记忆比例;
  • Useful Recall Rate:召回记忆是否实际帮助了当前回答;
  • Wrong Memory Injection Rate:错误、过期或越权记忆被注入的比例;
  • Memory Token Overhead:记忆占用了多少 Context Token;
  • Task Success Lift:启用 Memory 后任务成功率相对无 Memory 基线提升多少。

我认为最重要的不是“召回了多少记忆”,而是“召回的记忆是否让 Agent 做出了更好的决策”。