聊天记录或 Embedding 在进程重启后仍然存在,不代表 AI Agent 已经拥有可靠记忆。生产级记忆必须回答:什么信息可以影响未来行为、影响谁、在哪段时间有效、依据是什么、如何纠正和删除。
这个区分直接关系用户体验。记住用户明确要求的代码格式可以改善后续回答;永久保存一次性的医疗信息、检索文本中的恶意指令,或已经作废的项目决定,则会持续制造隐私风险与错误。
因此,记忆系统不应优化“召回越多越好”或“全部保存”,而应优化有用途、有授权、仍有效、可归因的召回,并让过时和危险记忆容易被拒绝。
核心结论
- 分开管理线程状态、长期记忆、权威业务数据和不可变审计日志。
- 长期记忆可包括语义事实、情景经验与程序性规则,三者需要不同写入和检索策略。
- 向量数据库只是可选索引,不是事实源,更不等于完整记忆系统。
- 少写入:要求用途、用户同意或其他有效依据、来源、置信度、有效期、敏感等级和删除策略。
- 不静默覆盖旧事实,用版本或
supersedes关系表达纠正。 - 检索必须同时考虑范围、时间、权限、类型与相关性,不能只看向量相似度。
- 召回的记忆属于不可信数据,不能被当作系统指令。
- 分别评测抽取、召回、时态、更新、拒答、隐私、延迟和删除。
四种状态边界
“记忆”经常混淆四套不同所有权的系统:
| 状态 | 作用范围 | 示例 | 主要存储 |
|---|---|---|---|
| 线程/工作流状态 | 单个对话或运行 | 消息、工具结果、待审批节点 | Checkpointer/状态库 |
| Agent 长期记忆 | 跨线程复用 | 用户偏好、历史方案、经验流程 | 受治理的记忆存储 |
| 权威业务状态 | 领域事实源 | 订单状态、账户余额、访问角色 | 事实源系统(system of record) |
| 审计/事件日志 | 取证与合规 | 谁在何时修改了什么 | 追加式(append-only)日志 |
不要把业务事实复制成一句“记忆”,之后就以它为准。Agent 查询订单状态时应调用订单服务。可以在用途明确且被允许时记住“用户经常询问某个订单”,但当前状态必须来自事实源。
当前 LangGraph 文档同样区分:Checkpointer 负责线程级持久化,Store 负责应用定义的跨线程长期记忆。Checkpoint 持久存在,也不等于其中每条消息都应该成为长期用户事实。
语义、情景与程序性记忆
认知心理学分类只有映射到明确应用行为时才有价值。
语义记忆:事实与偏好
例如:
- “用户明确要求简洁回答。”
- “Atlas 项目使用 PostgreSQL 17。”
- “团队财年从四月开始。”
语义记忆需要实体身份、来源、时间有效性和冲突处理。“用户喜欢 Python”如果只是从单次任务间接推断,就不能被包装成永恒事实。
情景记忆:过去经验
一条情景记忆是对一次相关事件的结构化记录:
- 任务与环境;
- 动作和工具调用;
- 观察结果;
- 最终结果和验证器输出;
- 失败原因;
- 来源 Trace 与时间。
原始对话记录只是构建这条情景记录的证据,不会自动成为有用的情景记忆。保存每个 Token 会制造噪声、隐私暴露和检索成本。对编码 Agent 而言,通过测试的迁移过程可能值得复用,周边闲聊并不值得。
程序性记忆:应该怎样做
程序性记忆保存可复用规则与策略:
- “修改支付 Schema 前必须运行向后兼容检查。”
- “该租户退款超过 500 美元时转人工审批。”
- “部署健康检查失败时停止,不允许无限重试。”
它可能与提示词、策略、操作手册(playbook)或代码重叠。高影响流程必须版本化和审查,并尽量由确定性代码强制执行,不能从聊天中静默“学会”。
完整记忆生命周期
可靠系统至少包含六个阶段:
观察 -> 提议 -> 校验/写入 -> 检索 -> 使用/验证 -> 修订/遗忘
1. 观察,但不自动升级
只采集保留策略允许的事件。临时模型上下文与持久存储分离。数据最小化应发生在抽取前,而不是数据库填满后。
2. 生成候选记忆
LLM 可以提出结构化候选,但不能拥有单方面写权限。候选至少应说明:
- 主体(subject)与谓词(predicate);
- 值(value);
- 记忆类型;
- 精确来源证据;
- 用户明确陈述还是模型推断;
- 置信度;
- 有效时间与观察时间;
- 敏感度与用途。
推断必须标为推断。“用户要求素食菜谱”不能证明“用户是素食者”。
3. 执行写入策略
以下情况应拒绝或要求确认:
- 不清楚未来用途;
- 信息只是临时状态;
- 来源不可信或有歧义;
- 信息敏感;
- 缺少用户同意或其他有效依据;
- 应从权威业务系统实时查询;
- 与已有有效记忆重复;
- 冲突无法安全解决。
适合个性化的产品应提供“记住这件事”和记忆管理页面。显式控制优于不可见用户画像。
4. 分离事实记录与索引
权威记忆记录存入事务数据库,词法、向量、图或时间索引只是派生视图。PostgreSQL 可以同时支持结构过滤、全文检索和 pgvector 等扩展提供的向量检索,“SQL 无法语义搜索”并不成立。
按访问模式选存储:
- 精确实体与时态更新 -> 关系型/文档库;
- 语义候选召回 -> 向量索引;
- 实体连接与来源图 -> 确有必要时使用图表示;
- 不可变原始事件 -> 追加式(append-only)事件或对象存储。
生产系统通常会组合使用,一次向量命中必须能够回到受治理的事实记录。
5. 检索、过滤与打包
正确顺序是:
- 认证调用者;
- 强制租户、主体、用途与敏感度范围;
- 过滤有效时间、删除状态;
- 使用精确键、时间、词法、向量或图关系召回;
- 按当前任务重排序(rerank);
- 去重并限制记忆的 Token 预算;
- 附上来源和不确定性。
全局 top_k=5 既不是安全策略,也不是质量策略。时态问题可能需要旧事实与更新事件;偏好问题可能只需要最新且已确认的值。
6. 验证后使用,再修订或遗忘
执行动作前,区分已确认记忆与推断,高影响事实应回到权威系统验证。记录哪些记忆 ID 实际影响了回答。
系统必须支持:
- 不丢失审计历史的纠正;
- 到期(expiry)与时间有效期;
- 用户可见删除;
- 按保留策略清理;
- 删除传播到向量、摘要、缓存、副本和备份;
- 模型或 Schema 变化后的重建索引。
“记忆衰减”不能简单等于按未访问时间删除。罕见事实可能仍然重要,最新事实也可能已经失效。
版本化记忆记录
真实记录不能只有 text 与 embedding:
{
"memory_id": "mem_01J...",
"tenant_id": "tenant_42",
"subject_id": "user_123",
"type": "semantic",
"predicate": "preferred_programming_language",
"value": "Rust",
"source_event_id": "evt_987",
"assertion": "explicit_user_statement",
"confidence": 1.0,
"observed_at": "2026-07-16T09:30:00Z",
"valid_from": "2026-07-16T09:30:00Z",
"valid_to": null,
"supersedes": "mem_older_python",
"purpose": "coding_assistance",
"sensitivity": "low",
"consent_basis": "user_requested_memory",
"status": "active",
"schema_version": 1
}
旧 Python 偏好应被标记为已取代,而不能与新事实合并成“用户喜欢编程语言”。这种有损总结会毁掉纠正的时态和含义。
可运行的时态解析
下面只使用 Go 标准库,在不假设“向量相似度能解决时间和鉴权”的前提下,选择指定时点有效的记忆:
package main
import (
"fmt"
"time"
)
type Memory struct {
MemoryID string
TenantID string
SubjectID string
Predicate string
Value string
ValidFrom time.Time
ValidTo *time.Time
Status string
}
func resolveMemory(
memories []Memory,
tenantID string,
subjectID string,
predicate string,
at time.Time,
) (*Memory, error) {
if at.IsZero() {
return nil, fmt.Errorf("at must be a non-zero timestamp")
}
var selected *Memory
for index := range memories {
memory := &memories[index]
if memory.TenantID != tenantID ||
memory.SubjectID != subjectID ||
memory.Predicate != predicate ||
memory.Status != "active" ||
memory.ValidFrom.After(at) {
continue
}
if memory.ValidTo != nil && !at.Before(*memory.ValidTo) {
continue
}
if selected == nil || memory.ValidFrom.After(selected.ValidFrom) {
selected = memory
}
}
return selected, nil
}
func mustTime(value string) time.Time {
parsed, err := time.Parse(time.RFC3339, value)
if err != nil {
panic(err)
}
return parsed
}
func timePointer(value time.Time) *time.Time {
return &value
}
func main() {
pythonUntil := mustTime("2026-07-16T00:00:00Z")
history := []Memory{
{
MemoryID: "mem_python",
TenantID: "tenant_42",
SubjectID: "user_123",
Predicate: "preferred_language",
Value: "Python",
ValidFrom: mustTime("2025-01-01T00:00:00Z"),
ValidTo: timePointer(pythonUntil),
Status: "active",
},
{
MemoryID: "mem_go",
TenantID: "tenant_42",
SubjectID: "user_123",
Predicate: "preferred_language",
Value: "Go",
ValidFrom: mustTime("2026-07-16T00:00:00Z"),
Status: "active",
},
}
memory, err := resolveMemory(
history,
"tenant_42",
"user_123",
"preferred_language",
mustTime("2026-07-17T00:00:00Z"),
)
if err != nil {
panic(err)
}
if memory == nil {
fmt.Println("no active memory")
return
}
fmt.Printf("%s=%s (%s)\n", memory.Predicate, memory.Value, memory.MemoryID)
}
数据库应为单值谓词强制不重叠有效期,或把歧义交给冲突解决流程。应用层过滤不能替代行级安全(row-level security)或独立加密边界。
冲突、不确定性与时间
使用显式操作:
ADD:新增独立事实;CONFIRM:新证据支持已有事实;SUPERSEDE:新有效值取代旧值;RETRACT:来源或用户声明旧事实错误;EXPIRE:有效期结束;DELETE:按用户请求或政策删除。
不能让“更新”自动获胜。最新模型推断不应覆盖较早的用户明确陈述。应先比较证据权威性,再比较时态适用性。
时间至少有两个维度:
- 观察时间(observed time):系统什么时候获知;
- 有效时间(valid time):信息在现实中什么时候为真。
“我下个月搬到柏林”现在被观察到,但下个月才生效。LongMemEval 单独评估时态推理与知识更新,正是因为纯语义检索经常丢掉这个区别。
安全与隐私
长期记忆会放大风险:一次恶意或错误写入可能影响未来许多会话。
- 召回文本是不可信数据,必须与系统指令隔离。
- 邮件、网页和工具输出中的指令未经验证不得写入程序性记忆。
- 尽可能在向量检索前鉴权,并对每条返回结果再次校验。
- 使用租户隔离、行级安全策略、独立密钥和受审计的服务鉴权;仅靠元数据过滤不够。
- 对敏感度分类,除非架构明确支持,否则不把秘密和高度敏感信息做嵌入(embedding)。
- 防止缓存、日志、评测集与调用链(trace)跨用户泄漏。
- 在合适产品中提供查看、纠正、遗忘、导出和关闭记忆的能力。
- 没有正当目的与治理时,不推断敏感属性用于个性化。
注入 Prompt 的记忆应标注来源、时间和可能过期,优先级不能高于开发者政策与当前工具结果。
评测完整生命周期
LongMemEval 把对话记忆拆为信息抽取、多会话推理、时间推理、知识更新和拒答。生产评测应在此基础上扩展。
增加长时域经验记忆测试
LongMemEval-V2 把评测对象从对话事实扩展到最多 500 条交互轨迹中积累的经验,并拆出五种不能被单一召回分数掩盖的能力:
| 能力 | 测试应改变什么 |
|---|---|
| 静态状态 | 跨轨迹持续不变的环境事实 |
| 动态状态 | 随时间更新、过期或被取代的值 |
| 工作流知识 | 从历史执行证据中学到的多步流程 |
| 环境陷阱 | 不直观的约束、历史失败与恢复条件 |
| 前提感知 | 问题假设与已知状态冲突时能否识别 |
每个轨迹长度分桶都应同时报告任务或回答准确率、端到端延迟、检索调用次数与上下文 Token。扫描全部历史的系统可能答对,却超过产品延迟预算;过度压缩的系统可能很快,却丢失关键更新,两者都属于记忆失败。该基准不能替代授权、删除和隐私测试,后者仍需应用自己的证据。
写入质量
- 有用候选的准确率(precision)与召回率(recall);
- 推断与明确陈述分类;
- 重复、敏感和临时信息写入率;
- 主体与租户归属正确率;
- 时间与来源提取正确率。
检索质量
- 证据 Recall@k 与 nDCG;
- 时间与实体过滤正确率;
- 过期记忆召回率;
- 跨用户/租户泄漏率;
- 固定上下文预算下的唯一有效 Token。
使用质量
- 有无记忆时的任务准确率;
- 来源引用正确率;
- 冲突处理与应当拒答时的拒答;
- 个性化收益与无关“套近乎”比例;
- 错误或注入记忆造成的伤害。
生命周期与运营
- 更新、撤回、到期与删除正确率;
- 向所有派生存储传播的延迟;
- p50/p95 读写延迟;
- 每条有效记忆的存储与 Embedding 成本;
- 模型、索引或 Schema 迁移后的性能。
必须包含负例:未同意记忆的用户、重名用户、冲突偏好、未来事实、已删除事实、恶意记忆文本,以及根本不应使用记忆的问题。
如何评估框架
Mem0、Zep、Letta/MemGPT、LangGraph Store 与自研方案所处层次不同,而且 API 持续变化。应评估能力,不要复制一段短 SDK 示例:
- Schema 与来源控制;
- 同步写入和后台写入;
- 冲突与时态语义;
- 命名空间与鉴权;
- 混合检索和重排序;
- 用户纠正与删除 API;
- 可观测性和评测钩子;
- 导出与迁移能力;
- 价格、部署和数据驻留。
MemGPT 的虚拟上下文管理是重要的分层存储范式,但不意味着可以跳过治理。模型自己决定记什么时,写入校验反而更重要。
生产检查清单
- [ ] 每类记忆都有明确跨会话用途;
- [ ] 线程状态、业务事实、审计日志与长期记忆分离;
- [ ] 写入包含来源、时间、用途、敏感度和有效授权;
- [ ] 推断不会被展示成用户明确事实;
- [ ] 更新通过版本或
supersedes保留历史; - [ ] 检索强制租户、主体、用途、时间和删除状态;
- [ ] 召回内容被视为不可信上下文;
- [ ] 用户能够查看和纠正持久个性化;
- [ ] 删除覆盖索引、摘要、缓存、副本和保留流程;
- [ ] 评测覆盖写入、召回、时态、冲突、拒答、隐私和成本。
常见问题
什么是 AI Agent 的长期记忆?
它是由应用管理、能够跨线程持续存在,并且被允许影响未来行为的信息。记录必须具备用途、主体、来源、置信度、时间语义、敏感度和删除路径;仅仅持久化不能让数据自动变成可信记忆。
Checkpointer 与长期记忆有什么区别?
Checkpointer 用于恢复一个线程或工作流,长期记忆则是准备跨线程复用的应用数据。两者应使用不同存储和升级策略,避免一段对话静默变成永久用户画像。
应该把每次对话都保存为 Agent 记忆吗?
不应该。原始事件只能按明确政策保留,再从中升级具有有效授权和未来用途的最少事实或经验。敏感、临时、重复或弱推断内容通常应被拒绝。
如何评测 AI Agent 记忆系统?
同时测量写入质量、证据召回、时态与冲突处理、拒答、任务收益、隐私隔离、删除传播和延迟。长时域经验测试还要改变轨迹数量,并联合报告准确率、延迟、检索调用和上下文成本。
向量数据库足以实现 AI Agent 记忆吗?
不足。它能召回语义相近的候选,但身份、授权、有效时间、来源、纠正、删除和权威事实必须由受治理记录与策略层负责。
总结
有用的 Agent 记忆应该是选择性且可逆的。质量来自严格写入、权威来源、时态语义、安全检索、明确冲突处理和可测量的用户收益。
先实现线程持久化与权威工具。只有真实跨会话任务需要时,才增加一种边界清晰的长期记忆。一个用户能够理解、纠正和删除的小型记忆库,远好于静默积累猜测的庞大向量索引。