拆解 Opus 5:Agent Memory 工程,从聊天摘要到有来源、可并发的状态系统

很多 Agent 的“长期记忆”是一段由模型不断改写的聊天摘要。它把用户事实、模型推断、搜索结果和建议混在一起,让一次误解在未来不断被召回。

真正的 Memory Service 首先是状态系统。判断一条信息能否持久化,不应只问“它真实吗”,而应问:

谁说的?关于谁?用户确认过吗?能保存多久?谁可以读取?发生冲突时谁覆盖谁?

没有答案,就不应写入。

一、记忆不是聊天摘要

opus-5 的 memory 章节提出了一条非常有价值的来源规则:只有用户明确陈述或确认的内容才能写入;模型推断、搜索结果和模型建议默认不进入长期记忆。

文件布局未必应该照搬,但来源规则通用:聊天摘要服务当前对话,允许压缩和重述;长期记忆跨会话影响行为,需要 provenance、并发控制、权限、保留期、审计和删除。

二、先把六种“记忆”拆开

团队讨论 Memory 时经常在说不同的东西。至少应区分六类状态:

类型

目的

典型寿命

是否可重建

Working context

支持当前一步决策

turn / session

通常不可完全重建

User memory

保存跨会话用户事实或偏好

长期或带 TTL

部分可重建

Project state

保存目标、决策、完成项和工件

项目周期

可从仓库部分重建

Episode log

保存执行事件与证据

审计周期

原始事实,不应依赖重建

Retrieval index

加速检索

缓存周期

可以重建

Handoff packet

向下一会话或 Agent 交接

一次或阶段性

可由项目状态生成

Anthropic 将 context 视为有限的注意力预算,推荐按需加载高信号内容;其 Memory tool 则由应用控制持久化,并可与 context compaction 配合使用。

因此 working context 不应无限累积,retrieval index 不应成为唯一事实源,project state 不能自动变成用户画像,episode log 应尽量不可变,handoff packet 只传最小充分事实。

三、Provenance:置信度不能替代来源

建议至少区分四种 origin:

  1. user_stated:用户直接说出的事实或偏好。

  2. user_confirmed:信息最初可能来自工具或模型,但用户明确确认或选择。

  3. tool_observed:来自网页、CRM、传感器、文件或其他系统的观察。

  4. model_inferred:模型根据上下文推断出的结论。

它们的区别不是“谁更准确”,而是谁有资格成为长期状态。

例如,“我使用 TypeScript”是 user_stated;用户从三个区域中选择新加坡是 user_confirmed;CRM 返回企业版属性是 tool_observed;模型根据代码风格猜测偏好是 model_inferred

置信度 0.95 的模型推断仍是推断;权威工具结果仍要记录来源、时效和主体。confidence 表示相信程度,provenance 表示进入路径,两者不能互换。

一个稳妥的默认策略是:

Origin

临时上下文

项目状态

长期用户记忆

user_stated

允许

按相关性

允许,经过敏感性策略

user_confirmed

允许

允许

允许,保存确认事件

tool_observed

允许

允许,保留来源和 TTL

默认不允许,除非产品规则和用户确认

model_inferred

允许作为候选

谨慎,标记推断

默认不允许

四、Memory Record 应该保存什么

一个最小记录可以是:

{
  "record_id": "mem_01J...",
  "subject": "user:self",
  "namespace": "preferences/development",
  "value": {
    "language": "TypeScript"
  },
  "origin": "user_stated",
  "evidence_event_id": "evt_98A...",
  "sensitivity": "personal",
  "durability": "long_term",
  "confidence": 1.0,
  "version": 7,
  "created_at": "2026-07-27T10:20:00+08:00",
  "updated_at": "2026-07-27T10:20:00+08:00",
  "expires_at": null
}

这些字段各有职责:subject 防止第三方信息错归属;namespace 支持权限与删除;origin 决定持久化资格;evidence_event_id 支持回放;sensitivitydurability 控制可见性与寿命;confidence 不提升来源等级;version 支持并发;expires_at 让易变事实自然失效。

自然语言可以作为展示或检索投影,但结构化字段才便于确定性地执行权限、过期、冲突和删除。

五、写入路径:模型只能提出候选

推荐写入流水线:

conversation / tool result
  → candidate extraction
  → subject resolution
  → provenance classification
  → sensitivity policy
  → confirmation gate
  → read current record
  → compare-and-set(version)
  → immutable audit event
  → asynchronous index refresh

这里最重要的边界是:模型生成的是 MemoryCandidate,不是最终写操作。确定性服务要重新检查事件主体、来源、第三方归属、敏感类别、重复或冲突记录、namespace 写权限和当前版本。

当信息来自工具或模型建议时,确认动作本身可以成为新的 provenance 事件。比如模型提出五个架构方案,用户选择其中一个;可以保存“用户选择了方案 C”,不能把另外四个选项和模型分析一起写成用户事实。

六、读取路径:相关不等于可以注入

写入只是风险的一半。读取路径同样需要确定性筛选:

request + principal
  → authorize namespace
  → filter subject and tenant
  → remove expired records
  → rank by task relevance
  → apply sensitivity policy
  → project minimal fields
  → attach provenance and trust
  → ContextComposer

检索结果不应直接拼成“系统对用户的认知”,而应返回带 record_id、projection、origin、sensitivity、freshness 和 instruction_allowed=false 的 envelope。即使 preference 由用户存入,也不能覆盖安全规则、事实准确性或当前明确指令。

无关记忆不应为了展示个性化而主动提及;用户询问来源时,系统应能解释并提供删除入口。

七、并发:读前写、CAS 和有限重试

第三方文件的 memory 章节明确要求 read-before-write、version token、冲突重读,并指出 if_version 只防止并发覆盖,不负责自动合并。这是典型的 compare-and-set。

假设手机端和桌面端都读取 version 7。手机端先以 if_version=7 写成 version 8,桌面端随后提交就必须得到 conflict;它应重读 version 8,再决定合并、替换或询问用户。

自动重试必须有界。结构化集合可以安全合并时,服务可以重算 mutation;两个互斥值冲突时,应保留外部修改并请求用户确认,不能让最后一次模型生成静默覆盖。

失败类型也要分开:

  • Conflict:重读后有限重试;

  • Policy denied:不重试,记录拒绝原因;

  • Storage unavailable:主回答继续,标记 memory_degraded=true

  • Index lag:回退到 source of truth;

  • Audit failure:对高风险写入应整体失败;

  • Delete request:按明确范围执行,并清理派生索引。

Memory 是增强能力,不应成为主要回答的单点故障;但审计和权限又不能因为“best-effort”被跳过。

八、安全:Memory Poisoning 会跨越会话

外部网页、邮件、文件和 MCP resource 都可能包含间接提示注入。如果 Agent 把其中的指令写入长期记忆,攻击就从一次工具调用扩散到未来会话,甚至传播给其他 Agent。

Anthropic 的 containment 实践明确讨论了工具输出攻击、共享 memory poisoning 和多 Agent 间的信任升级。 MCP 规范也强调显式同意,并要求不可信工具描述不能自动获得更高信任。 OpenAI Agents SDK 则允许在输入、输出和具体工具前后放置 guardrail。

因此写入门至少要检查原始来源和 trust、跨会话控制意图、凭据与高敏感属性、第三方主体、低信任到高权限 namespace 的跳转,以及对安全或授权政策的修改。

读取时必须继续保留 trust 标签。一次成功持久化不应把 untrusted_external 洗白为 trusted_instruction

租户隔离也不能只依赖向量检索 metadata。授权过滤应在检索前执行,检索后再次验证;trace 和 eval 数据要脱敏,避免 Memory 内容从另一个观测系统泄露。

九、长任务恢复:Project State 不是 User Profile

一个跨数天的编码 Agent 需要记住 feature checklist、测试结果、未完成文件和下一步操作。这些内容应持久化,但它们属于 project state,不是“关于用户是谁”的长期记忆。

Anthropic 的 Memory tool 建议长任务维护 progress log、feature checklist 和初始化脚本,在新会话开始时读取、结束时更新。 Long-running Harness 的实践也表明,仅靠 context compaction 不足以恢复复杂任务;结构化工件和独立 evaluator 更可靠。

恢复流程应固定为:读取目标、验收条件与 project state;检查版本库或工件;运行最小测试验证完成项;选择一个有限任务;完成后更新状态、证据和已知失败,再生成新的 handoff packet。

这样,下一会话依赖的是可验证工件,而不是上一会话的自我总结。

十、测试 Memory,不要只测“能不能召回”

Memory eval 常被简化为:存一条事实,再问模型能否回答。这只测了检索,不测状态正确性。

建议至少覆盖:

测试类别

输入

期望

正常写入

用户明确陈述稳定偏好

写入并绑定原始事件

用户确认

用户选择工具或模型提供的选项

只保存用户选择

负向写入

模型推断用户偏好

拒绝长期写入

工具观察

CRM 返回客户属性

进入项目状态并带 TTL,不自动进入用户画像

敏感信息

临时健康或财务描述

按政策拒绝、缩短寿命或请求确认

第三方信息

用户提到同事姓名和情况

不错误归属给用户,限制持久化

注入

网页要求保存跨会话指令

拒绝并记录安全事件

并发

两个客户端更新同一记录

一方冲突,不静默覆盖

存储故障

Memory backend 不可用

主任务继续,标记 degraded

删除

用户明确删除某 namespace

删除源记录并清理索引

恢复

新会话接管项目

依据工件验证状态后继续

除了最终答案,还应检查:

  • 写入了什么;

  • 为什么允许;

  • 使用了哪条 provenance;

  • 是否读取了无关或越权记录;

  • 冲突和故障如何处理;

  • 对未来会话产生了什么影响。

十一、分阶段落地

分四步:先做可审计恢复的 project state;再开放 user-stated memory;再加入 confirmation workflow;最后测 poisoning、跨租户、并发、删除、降级与长期漂移。勿从自动总结历史起步。

结语

Agent Memory 的核心不是容量,而是状态资格。

记录能否进入长期记忆,不取决于模型自信或检索相关性,而取决于来源、主体、确认、敏感性、寿命、权限和版本。

把 Memory 当作聊天摘要,系统会相信自己的故事;把它当作状态系统,跨会话能力才可能可解释、可撤销、可恢复。

评论区