用户在一次工单对话里说:
以后请用中文,先给结论再列证据。顺便看一下 T-104,系统显示已经退款。上次那张深色截图看起来还可以。
Agent 还从工具结果里读到一个邮箱,并在任务结束后收到 Reviewer 的纠正:退款前必须先检查支付回执。
这些信息都出现在同一次运行里,但不应该被同一种方式保存:
- “以后用中文”是用户明确表达、未来可能复用的偏好;
- “T-104 已退款”由工单系统负责,不应复制成 Agent 自己的长期事实;
- “喜欢深色截图”只是模型从一次行为中做出的推测;
- 邮箱和授权 Token 不应因为“以后可能有用”就落入记忆;
- “退款前检查回执”经过人工确认,可以成为 Agent 的流程经验。
如果把整段对话直接存进向量库,下一次再按相似度取回,我们并没有得到“会记忆的 Agent”,只是得到了一份会把事实、推测、秘密和过期信息一起带回来的历史仓库。
Memory Engineering 解决的不是怎样尽量多记,而是怎样让系统决定:什么能记、归谁所有、什么时候想起、冲突时信谁,以及何时必须忘记。
本文是 AI Agent 工程进阶 第 8 篇。上一篇 Agent Tracing 解决“这次运行发生了什么”;这一篇继续回答“哪些信息可以跨任务保留,以及怎样避免 Agent 被自己的旧记忆带偏”。
| 项目 | 说明 |
|---|---|
| 内容类型 | Agent Memory 入门、策略设计与可复现实验 |
| 适合读者 | 理解基本 Agent Loop,但第一次系统设计长期记忆的开发者 |
| 阅读时间 | 约 17-22 分钟 |
| 跟做时间 | 45-60 分钟 |
| 环境要求 | Python 3.10+,零第三方依赖,不需要 API Key |
| 代码检查点 | ddca6ca |
| 可带走产物 | Memory Policy、8 个边界案例、命名空间 Store、Recall Gate 与五份报告 |
| 资料核对日期 | 2026-08-04 |
| 实验边界 | 确定性策略夹具与内存存储;不测试 embedding、向量数据库性能或模型回答质量 |
先说明一个边界
本文中的 Memory 是模型外部的持久状态,不是模型权重更新,也不等于“Agent 学会了”。存储与召回可以让未来运行再次看到一条信息,但它同样可能持续放大错误、偏见和过期事实。
一分钟概览
如果只想先拿走结论,可以记住十点:
- Context 是本次调用真正进入模型的输入,Memory 是可能跨任务保留的信息。 两者不是同一个存储层。
- Checkpoint 用于恢复当前任务,Session 用于延续一段对话,Memory 用于未来任务复用。 生命周期和所有者不同。
- 订单、工单、权限和政策应回到 Source of Truth。 不要让 Memory 变成业务数据库的影子副本。
- 先设计 Write Gate,再选择向量库。 不能回答“谁有权写”,检索做得再快也不可靠。
- 显式信息优先于模型推测。 一次点击、一次措辞或一次选择,通常不足以形成长期偏好。
- 召回先检查命名空间,再计算相关性。 否则“很相似”可能把另一个租户的数据带进来。
- 每条记忆至少保留来源、有效期和状态。 没有 provenance 的句子不应该获得长期权威。
- 更正不是把两条冲突文本一起召回。 新版本应 supersede 旧版本,并保证同一 key 只有一个 active 版本。
- 删除与记住同样重要。 删除正文时,报告、缓存、索引和审计记录也要遵守同一策略。
- Memory Gate 通过只证明策略按预期运行。 它不证明模型答案更好,质量仍要用 Eval 验证。
第一次阅读建议走第 1-7 节,完成五个状态面的区分和 Memory Lab。平台映射、记忆类型与生产存储可以第二次再看。
图 1:候选信息先经过 Write Gate,合格记录进入带命名空间的 Memory Store,未来任务再经过 Recall Gate 召回
图 1:Memory 不是聊天历史的自动归档。明确偏好和已验证经验可以进入 Store;工单事实回到事实源,推测与敏感值被拒绝;未来使用前还要再过 Recall Gate。
1. Memory Engineering 到底在工程什么
很多 Memory 教程从 embedding、向量数据库或“语义记忆、情节记忆、程序性记忆”开始。这些概念有用,但第一次搭系统时,更早的问题是:
谁提出了这条候选信息?
它为什么值得跨任务保留?
它属于哪个租户、用户、Agent 或项目?
正式事实由谁维护?
下一次任务真的需要它吗?
它是否已过期、被更正或被要求删除?
如果这些问题没有答案,向量数据库只会更高效地检索未经治理的数据。
因此我会把 Memory Engineering 拆成两道门和一个生命周期:
Candidate
-> Write Gate
-> Namespaced Store
-> Recall Gate
-> Revalidate
-> Use as evidence
-> Supersede / Expire / Delete
这里有一个重要细节:写入时合格,不代表以后永远合格。 用户偏好会变化,流程会更新,租户会迁移,政策会失效。Memory 既需要写入策略,也需要读取时重新验证。
一个判断是否需要 Memory 的简单问题
先问:
如果这条信息在当前任务结束后消失,下一个独立任务是否会因此重复犯错、重复询问,或失去经过确认的个性化信息?
如果答案是否定的,它大概率只属于当前 Context、RunState 或 Session。
如果答案是肯定的,也仍然不能直接写入。还要判断它是否已有业务事实源、证据是否足够、能否安全持久化,以及是否有明确所有者。
2. 先分清五个状态面
Agent “记得某件事”可能来自完全不同的机制。把它们都叫 Memory,会让恢复、隐私和删除变得混乱。
图 2:Context、RunState 与 Checkpoint、Session、长期 Memory、Source of Truth 五个状态面的所有者和生命周期
图 2:越往下生命周期越长,但长期不等于更权威。正式业务事实始终由 Source of Truth 负责;Memory 只是经过策略筛选的跨任务辅助证据。
2.1 Context:当前一次模型调用看到什么
Context 包含系统规则、当前任务、工具定义、检索结果、必要历史和运行摘要。它的目标是让这一次调用获得足够而不过量的信息。
上一篇 Context Architecture 已经把它设计成 Context Packet:每个来源带有来源、新鲜度、权限和预算证据。
Context 可以临时包含一条长期记忆,但 Context 本身不是长期 Memory。
2.2 RunState / Checkpoint:当前任务从哪里继续
RunState 保存:
当前步骤
已完成动作
待审批动作
稳定 action_id
重试次数
外部回执状态
它服务于“这次任务中断后怎样恢复”,不是“下个月另一个任务要了解用户什么”。
把 Checkpoint 当长期记忆,会让未来任务误读旧的中间状态;把长期 Memory 当 Checkpoint,又无法证明哪些副作用已经发生。
2.3 Session:同一段对话的连续历史
Session 常用于保存同一线程里的消息或运行历史。它让第 12 轮对话能够接上前 11 轮,但不代表所有消息都值得跨 Session 保留。
例如:
“把刚才第二段再缩短一点” -> 依赖当前 Session
“以后默认先给结论” -> 可能成为长期偏好候选
第一句离开当前对话就失去指代对象,第二句才可能在未来任务复用。
2.4 Long-term Memory:跨任务复用的受控信息
长期 Memory 保存经策略允许、未来可能复用的记录,例如:
- 用户明确确认的表达偏好;
- 经人工验证的工作流经验;
- 某项目稳定存在、且没有更正式所有者的约定;
- 经过审查的失败模式与处理办法。
它需要命名空间、来源、状态、有效期、更正和删除。
2.5 Source of Truth:正式业务事实
订单状态、工单结果、权限、付款、库存和生效政策应由业务系统负责。
Memory 可以记住“查询退款状态前调用哪个工具”,但不应该长期记住“订单 104 已退款”并在未来绕过订单系统。后者会过期,也缺少正式更新路径。
| 信息 | 正确状态面 | 原因 |
|---|---|---|
| 当前问题和刚检索到的政策段落 | Context | 只服务当前调用 |
| 工具已提交但响应丢失 | RunState + 业务回执 | 用于恢复和对账 |
| “把上一段再缩短” | Session | 依赖当前对话指代 |
| “以后默认用中文” | Long-term Memory 候选 | 明确且可能跨任务复用 |
| T-104 的退款状态 | Source of Truth | 会变化,由工单系统维护 |
3. 一条 Memory Record 至少要有什么
纯文本 "用户喜欢先看结论" 不足以成为可治理的记忆。
Memory Lab 使用七个主字段:
{
"memory_id": "mem_ac3d1a3fb0bf26b5",
"namespace": ["tenant_acme", "user_u17"],
"kind": "preference",
"statement": "Put the conclusion before the evidence.",
"provenance": {
"source": "explicit_user",
"source_run_id": "run_104",
"source_ref": "message_7"
},
"valid_until": null,
"status": "active"
}
3.1 memory_id:稳定引用,不把正文当身份
更正、删除和审计都需要稳定 ID。不要用整段正文充当主键,否则句子稍微改写就会生成无法关联的另一条记忆。
3.2 namespace:先确定“是谁的”
本文示例使用:
[tenant, user] 用户偏好
[tenant, agent] Agent 流程经验
[tenant, project] 项目约定
生产系统还可能加入环境、区域或数据域。关键不是层数越多越好,而是召回前能用确定性规则回答“这个运行是否有权看”。
3.3 kind:不同类型使用不同策略
偏好、流程经验、任务事件和业务事实不能共用同一套有效期与权限。kind 让 Policy 可以分别处理。
3.4 statement:可使用的最小内容
不要默认保存整轮对话。只保留未来任务真正需要、并已通过策略的最小陈述。必要时同时保存引用,让 Reviewer 可以回到来源,而不是把上下文全部复制进来。
3.5 provenance:为什么应该相信它
来源至少回答:
谁说的或谁确认的
出现在哪次运行
可以回查到哪个消息、Review 或业务回执
“模型推测”与“用户明确确认”即使文本一样,也不应拥有同等权威。
3.6 valid_until:什么时候必须重新确认
并非所有记忆都要永久保存。迁移期流程、临时项目约定和阶段性偏好可以设置 TTL。到期后应停止召回,而不是等某次事故才发现它已过时。
3.7 status:当前能否被使用
最小状态包括:
active 可以进入 Recall Gate
superseded 已被更高版本替代
deleted 正文已清除,不可召回
实现里还保存 key、version、创建日期和内容摘要。它们不是为了让 JSON 看起来完整,而是服务于冲突、追溯和删除。
4. Write Gate:什么值得记
Write Gate 是整个系统最重要的一层。一次对话结束后,不应该由模型自由决定“我觉得以后可能有用”。
图 3:候选信息依次经过复用价值、证据、事实源、命名空间、敏感性和生命周期六个问题
图 3:六个问题会把候选信息路由到 Store、Source of Truth 或 Reject。拒绝写入不是能力缺失,而是 Memory Policy 正常工作。
4.1 六个问题
我会按下面顺序检查一条候选信息:
- 未来任务会复用吗? 只对当前步骤有用的内容留在 Context 或 RunState。
- 证据是什么? 用户明确表达、人工确认,还是模型推测?
- 业务系统是否已经拥有它? 工单、订单和权限回到 Source of Truth。
- 命名空间属于谁? 没有明确租户与主体,不允许写入。
- 是否含敏感或禁止持久化的数据? 密钥、邮箱和受限正文默认拒绝或最小化。
- 怎样过期、更正和删除? 没有退出路径的记忆不应轻易进入长期层。
4.2 工单案例的五种决策
| 候选信息 | 证据 | 决策 | 原因 |
|---|---|---|---|
| 以后先给结论,再列证据 | 用户明确表达 | store | 可复用、有所有者 |
| T-104 已退款 | 工单 API 回执 | route_to_source | 正式事实由工单系统维护 |
| 用户喜欢深色截图 | 模型从一次选择推测 | reject | 证据不足 |
| 退款前必须查支付回执 | Reviewer 已确认 | store | 可复用的流程经验 |
| 邮箱与模拟 Bearer Token | 工具输出 | reject | 敏感或禁止持久化 |
注意,route_to_source 不是把信息丢掉。它表示未来需要这条事实时,重新调用正式系统,而不是相信 Agent 的旧副本。
4.3 模型推测怎样才能升级为记忆
推测可以成为待确认候选,但不能直接成为 active Memory。例如:
Agent 观察:用户连续三次要求短答案
系统动作:不写入长期层
下一步:在合适时机询问“是否以后默认先给简短版本?”
用户确认:写入 explicit_user preference
这比静默建立画像更准确,也给用户留下知情和更正的机会。
还有一条容易漏掉的信任边界:evidence_type 必须由可信运行时根据真实事件写入,不能让模型自己把候选标成 explicit_user 或 verified_reviewer。模型可以提出 statement,运行时要用当前消息、Reviewer 身份或业务回执确认来源,再生成不可由模型覆盖的 provenance。
5. Recall Gate:什么此刻该想起
写入合格只是第一道门。未来任务召回时,还要重新判断这条记忆是否属于当前运行、是否相关、是否仍有效。
图 4:Recall Gate 先做命名空间隔离,再检查相关性、状态与有效期、冲突权威,最后作为可覆盖的上下文证据使用
图 4:相关性不是第一道门。当前明确指令与业务事实源是上方锚点,Memory 只能补充上下文,不能覆盖更高权威。
5.1 顺序一:先按命名空间隔离
假设 tenant_acme / user_u17 有一条高度相关的表达偏好,而当前查询来自 tenant_beta / user_u17。即使用户名相同、语义分数接近 1,也必须返回空。
因此正确顺序是:
namespace / permission filter
-> kind filter
-> active + TTL filter
-> relevance ranking
而不是先在全库做语义搜索,再希望最后一步记得过滤租户。
5.2 顺序二:只取当前任务需要的类型
写技术报告时,可能需要表达偏好和项目约定,不需要把购物偏好、历史工单和所有流程经验一起放进 Context。
召回查询至少应声明 kinds 与任务意图。Memory 的目标是减少重复信息,而不是建立另一种 Context dump。
5.3 顺序三:检查状态、有效期和冲突
只召回 active 且未过期的记录。superseded 保留用于审计,不能继续影响模型;deleted 不保留正文,也不进入检索索引。
5.4 顺序四:重新对照当前锚点
召回结果进入模型前,再与两类更高权威信息对照:
- 当前用户明确指令;
- 当前业务 Source of Truth。
例如长期记忆写着“先给结论”,但当前消息明确要求“请先列证据”,当前指令优先。Memory 是默认值,不是不可覆盖的系统规则。
5.5 预算也是 Recall Gate 的一部分
通过 Policy 的记录仍然可能太多。可以按相关性、权威、新鲜度和 token 预算排序,只把最少必要记录放进 Context Packet,并在 Trace 中保存被选中的 memory_id。
6. 更正、过期与删除
长期 Memory 最容易被忽略的不是写入,而是变化。
图 5:同一个 response_order key 从 v1 active 到 v1 superseded、v2 active,再到过期或删除 tombstone 的生命周期
图 5:更正创建新版本并停用旧版本;删除会清除同一 key 的历史正文与内容摘要,只保留不含原值的审计证据。
6.1 更正:新版本替代旧版本
用户后来明确说:
以后先列证据,最后再给结论。
系统不应该把两句话都标成 active,再让模型自己判断。Memory Lab 会:
response_order v1 active
-> status = superseded
response_order v2 active
statement = evidence before conclusion
同一个 namespace + kind + key 最多只有一个 active 版本。
如果新信息权威更低,例如模型推测与用户明确偏好冲突,应拒绝低权威版本,而不是覆盖用户选择。
6.2 过期:停止召回,不必假装删除
流程经验可以设置 valid_until。过期后它不再进入 Context,但仍可保留受控审计记录,供维护者判断是否续期或替换。
删除和过期目的不同:
expire 这条信息不再有效
delete 这条内容不应继续被保存
6.3 删除:正文、摘要和索引一起处理
只在主表把 status 改为 deleted,却让向量索引、离线报告和缓存继续保存正文,不算完成删除。
本文实验在删除 response_order 时会:
- 清空 v1 和 v2 的
statement; - 将内容哈希改为固定
purged,避免低熵文本被摘要反查; - 将状态设为
deleted; - 把来源替换为不含原值的删除请求证据;
- 保留
memory_id,证明删除动作作用于哪些记录。
生产环境还要同步处理数据库副本、搜索索引、对象存储、缓存、日志、备份和保留政策。本文的内存实验不声称覆盖法律意义上的彻底擦除。
7. Memory Lab 0.8.0 怎样实现这些规则
完整代码位于 RalfNick/ai-agent-learn,本篇对应不可变检查点 ddca6ca。
Lab 使用 Python 标准库,不连接模型或数据库。这样可以先验证 Policy 行为,再把同一接口替换成真实存储。
7.1 文件增量
agent-reliability-lab/
├── agent_lab/
│ ├── memory.py # Candidate、Store、Recall 与 Review Gate
│ └── memory_reporting.py # JSON、Markdown 与 JSONL 报告
├── datasets/
│ └── memory-cases.jsonl # 8 个确定性案例
├── reports/
│ ├── memory-review.md
│ ├── memory-decisions.jsonl
│ ├── memory-store.jsonl
│ └── memory-recall.md
└── tests/
└── test_memory.py
7.2 Candidate Policy
实际实现的判断顺序保持刻意简单:
if candidate.sensitivity == "secret" or contains_sensitive(statement):
return reject("sensitive_or_prohibited")
if candidate.evidence_type == "business_record" or kind == "case_fact":
return route_to_source("business_source_of_truth")
if not candidate.reusable:
return reject("not_reusable_across_tasks")
if candidate.evidence_type == "model_inference":
return reject("inference_requires_confirmation")
if namespace_is_invalid(candidate.namespace):
return reject("invalid_namespace")
return store_or_supersede(candidate)
这里没有让模型给“值得记忆程度”打分。不是因为评分永远没用,而是硬边界应先由确定性策略处理:秘密不能因为模型给了高分就落盘,跨租户数据也不能因为很相关就被召回。
实验中的 JSONL 是可信测试夹具,因此可以直接声明 evidence_type。生产入口不能把这个字段放进模型可自由生成的参数里;它应由 Harness 根据已认证的用户消息、Reviewer 事件或工具回执填充。
7.3 Namespace-first Recall
实验先按精确命名空间过滤,再进入状态、TTL 和相关性:
candidates = (
record
for record in records
if record.namespace == query.namespace
)
for record in candidates:
if record.kind not in query.kinds:
continue
if record.status != "active":
continue
if record.valid_until and record.valid_until < query.as_of:
continue
rank_by_relevance(record)
本地实验用 token overlap 保持结果可预测。生产系统可以替换成倒排、向量或混合检索,但命名空间和权限过滤不能因为更换检索器而消失。
7.4 Review Gate 检查什么
Review Gate 不只统计 8 条 case 是否返回预期 action,还检查:
expected_decisions_matched
required_fields_present
sensitive_values_absent
namespace_isolation_enforced
one_active_version_per_key
deleted_content_purged
这些检查分别对应文章前面的 Write Gate、Recall Gate 与生命周期,不是为了凑一张 PASS 截图。
8. 跟着运行 Memory Lab
git clone --branch agent-engineering-series https://github.com/RalfNick/ai-agent-learn.git
cd ai-agent-learn/phase-7-agent-engineering/agent-reliability-lab
python run_lab.py memory-review --output reports/local
python -m unittest discover -s tests -v
当前检查点的输出是:
Memory cases 8
Expected decisions matched 8 / 8
store 2
route_to_source 1
reject 3
supersede 1
delete 1
Review gate PASS
Full unit tests 74 passed
图 6:Memory Lab 的八个案例、五类决策、六项 Review Gate 与最终存储状态
图 6:两个 store 分别是用户明确偏好和 Reviewer 验证经验;三个 reject 包含模型推测、敏感值和跨租户召回。数字只描述本文夹具,不是 Memory 效果指标。
命令生成五份文件:
| 文件 | 先看什么 |
|---|---|
memory-review.json | 完整 Gate、案例和最终 Store |
memory-review.md | 决策汇总与六项检查 |
memory-decisions.jsonl | 每个候选为什么 store、reject 或 route |
memory-store.jsonl | 删除后 Store 是否只剩合格记录和无正文 tombstone |
memory-recall.md | 跨租户查询为什么返回 none |
一个容易误读的地方是:cross-tenant-recall 的实际 action 为 reject,但总 Gate 仍通过。因为这个案例的合同就是“即使其他租户有高度相关记录,也必须拒绝召回”。
8.1 练习一:把推测升级成用户确认
打开 datasets/memory-cases.jsonl,找到 inferred-preference:
"evidence_type": "model_inference",
"source_ref": "model_observation",
"expected_action": "reject"
把 evidence_type 改为 explicit_user,同时把 source_ref 改成可回查的 message_confirmation_12,重新运行。Review Gate 应失败,因为实现返回 store,但夹具仍期待 reject。
确认行为变化后,再把 expected_action 改成 store,Gate 恢复 PASS。
这个练习说明:数据集声明的是产品策略,不是实现碰巧得到的结果。 策略变化必须显式修改测试合同。
8.2 练习二:让删除选择器指向错误 key
找到 forget-preference,把:
"key": "response_order"
临时改成:
"key": "unknown_preference"
命令应以非零状态退出:系统找不到目标,返回 reject;expected_decisions_matched 与 deleted_content_purged 都会失败,因为没有任何目标记录被清除。
恢复正确 key 后再运行,观察 memory-store.jsonl 中两个版本的:
{"statement": null, "status": "deleted", "content_hash": "purged"}
这一步比只测试“召回不到”更严格,因为后者可能只是索引漏查,正文仍然存在。
9. Session、RAG、Compaction 与 Memory 不要混在一起
9.1 Session 不是长期 Memory
Session 解决对话连续性。它可以在每次运行前取回历史消息,并在运行后追加新消息。长期 Memory 则需要从历史中筛选未来可复用的信息,并应用不同的所有权与保留策略。
把整个 Session 永久保留并不自动得到 Memory Policy。
9.2 RAG 不等于 Memory
RAG 常用于从外部知识库检索文档。Memory 常用于保存交互过程中形成、并经策略允许的跨任务状态。
它们可以共用向量检索基础设施,但数据治理不同:
| 问题 | RAG 文档 | Agent Memory |
|---|---|---|
| 谁创建 | 内容系统、文档作者 | 用户、Agent、Reviewer 或运行时 |
| 谁更新 | 文档发布流程 | Memory Policy 与更正流程 |
| 权威来源 | 文档版本 | provenance + 当前事实源 |
| 常见生命周期 | 版本发布与下线 | active、superseded、expired、deleted |
9.3 Compaction 不是长期 Memory
Compaction 用摘要或重写方式让一段长运行继续装进有限 Context。它主要解决“同一次长任务怎样继续”。长期 Memory 解决“未来独立运行需要复用什么”。
压缩摘要可以成为候选来源,但不能未经 Write Gate 自动获得长期权威。
9.4 Prompt cache 也不是 Memory
缓存减少重复输入的传输或计算成本,不负责信息是否可信、属于谁、何时过期或怎样删除。缓存命中不代表 Agent 形成了长期记忆。
9.5 Memory 不等于模型学习
模型下次看到一条召回记录,只是在当前 Context 中重新读取它。外部 Store 没有改变基础模型权重。若记忆被删除,模型也不会因为曾经看过就自动“反训练”。
10. 记忆类型何时有用
当状态面已经分清,再看常见分类会更容易。
| 类型 | Agent 示例 | 更适合的治理 |
|---|---|---|
| Semantic | 用户明确偏好、项目稳定约定 | 按主体命名空间、来源和 TTL |
| Episodic | 某类失败曾怎样发生和解决 | 关联 Trace、Reviewer 与任务结果 |
| Procedural | 退款前先查回执的已验证步骤 | Agent / 项目命名空间、版本化 |
这个分类帮助选择检索和更新方式,但不能替代 Write Gate。一个“情节”仍可能包含个人信息,一个“程序”仍可能已经过期。
11. 映射到 OpenAI、Claude 与 LangGraph
下面只映射当前官方能力,不把框架名当成统一标准。
11.1 OpenAI:Session、Compaction 与 Sandbox Memory 是不同层
OpenAI Agents SDK Sessions 当前用于保存特定 Session 的运行历史:运行前读取历史并加入输入,运行后追加新项。官方文档也明确提醒,同一次 run 不应同时把 Session 与 conversation_id、previous_response_id 或自动 previous response 方式混用,因为它们是不同的状态延续机制。
OpenAI Cookbook 的可靠 Agent Memory / Compaction 示例 将两者区分为:Compaction 帮助同一长运行继续,Memory 帮助未来 sandbox-agent 运行复用已审查的工作流经验;最终经 Review 的产物仍是事实依据。
OpenAI Sandbox Memory 指南 也将 Sandbox Memory 与 SDK Session 分开:Session 保留消息历史,Sandbox Memory 跨运行提炼可复用经验,并通过 memory_summary.md、MEMORY.md 和 rollout summary 做渐进式读取。
这些实现与本文的对应关系是:
Agents SDK Session -> Session 状态面
Compaction -> 当前长运行的 Context 管理
Sandbox Memory -> 跨运行经验候选与按需召回
最终业务产物 / API -> Source of Truth
11.2 Claude:客户端存储与按需读取
Claude Memory Tool 提供查看、创建、替换、插入、删除和重命名等操作,但存储实现由客户端应用负责。官方示例把记忆限制在 /memories 路径,并建议按需读取,而不是把全部文件预先塞入 Context。
这对应本文的两点:
- 工具能力不等于存储治理,应用仍要负责路径隔离、权限与删除;
- Memory Store 不等于 Context,召回应遵循渐进披露。
Claude Context Editing 处理的是有限上下文中的压缩和选择性清理,仍应与跨任务持久 Memory 区分。
11.3 LangGraph:线程状态与跨线程 Store
LangGraph Memory 概念 将短期记忆放在 Agent State / Checkpoint 中,并按 thread 组织;长期记忆则保存在跨 Session 的自定义 namespace / store 中。官方还使用 semantic、episodic、procedural 三类记忆,并区分 hot path 写入与后台写入。
对应本文:
thread state + checkpointer -> RunState / Session
store + namespace -> Long-term Memory
hot path / background write -> Write Gate 在何时执行
无论使用哪一种模式,租户隔离、来源、TTL、冲突与删除仍需要项目自己定义。
11.4 Mem0、Letta 与 LangMem:实现参考,不是收益证明
Mem0、Letta 和 LangMem 提供了不同的持久记忆、Agent 状态和提炼方式,适合继续研究具体实现。
但开源项目首页中的 benchmark、成本或提升数字只适用于其声明的数据集与配置,不能直接证明“给任何 Agent 加 Memory 都会更好”。更稳妥的顺序仍然是:
定义 Policy
-> 建立无 Memory 基线
-> 加入受控 Memory
-> 用同一任务集重复评测
-> 检查收益、误召回、隐私与成本
12. 一份可复制的 Memory Policy
下面这份最小模板可以放进项目文档,再逐项落到代码和测试。
memory_policy:
owners:
- tenant
- subject
allowed_kinds:
preference:
evidence: [explicit_user]
ttl: optional
procedure:
evidence: [verified_reviewer]
ttl: required
route_to_source_of_truth:
- order_status
- ticket_status
- permission
- payment
- active_policy
prohibited:
- secret
- access_token
- raw_email
- unconfirmed_model_inference
recall_order:
- namespace_and_permission
- kind
- active_and_unexpired
- relevance
- authority_and_version
precedence:
- current_explicit_instruction
- current_source_of_truth
- explicit_user_memory
- verified_procedure_memory
lifecycle:
one_active_version_per_key: true
support_expiry: true
support_correction: true
support_delete: true
purge_indexes_and_reports: true
Policy 写完后,不要只做文档 Review。至少为每一条允许、拒绝、冲突和删除路径准备一个可重复测试。
13. 收藏清单:第一次给 Agent 加长期记忆
第 1 步:列出五个状态面
- 当前 Context 存在哪里
- RunState / Checkpoint 存在哪里
- Session 保存什么历史
- Long-term Memory 允许哪些 kind
- 哪些业务事实必须实时查询 Source of Truth
第 2 步:只开放一类低风险记忆
先从用户明确偏好或经 Reviewer 验证的流程经验开始,不要第一版就自动提炼全部对话。
第 3 步:给记录补齐治理字段
- namespace
- kind 与 key
- provenance
- valid_until
- status 与 version
- correction / delete owner
第 4 步:让 Recall namespace-first
先做租户、用户、项目和权限过滤,再做向量或关键词相关性。为跨租户和跨用户准备负向测试。
第 5 步:建立冲突与删除实验
至少验证:
- 新显式偏好会 supersede 旧版本
- 低权威推测不能覆盖显式偏好
- 过期记录不召回
- deleted 记录正文和内容摘要已清除
- 缓存、索引与报告遵守同一删除路径
第 6 步:再做质量对照
在相同任务、模型、预算和重复次数下比较:
无长期 Memory
受控 Memory
故意注入过期或错误 Memory
不仅看答案是否更个性化,也要看误召回、越权、冲突、Token 成本和人工纠正率。
结语:可靠的记忆从“允许忘记”开始
Agent Memory 最吸引人的演示,是它隔了几天还能说出用户偏好。但工程上更重要的问题是:
它为什么有权保存?
这条信息现在还有效吗?
它是否属于当前用户和任务?
有没有更权威的当前事实?
用户更正或删除后,旧版本是否真的停止影响系统?
如果只记住一句:
Memory 不是长期 Context,而是一组带所有权、来源、有效期和退出路径的可召回记录。
下一篇进入 Graph Engineering。Memory 解决单个运行怎样复用受控状态;当任务包含真实依赖、并行研究、独立验证和人工闸门时,我们再判断是否需要把多个局部 Loop 组织成工作图。
参考资料
OpenAI 官方
- OpenAI Agents SDK:Sessions
- OpenAI Cookbook:Building Reliable Agents with Memory and Compaction
- OpenAI Sandbox:Persist memory across runs
- OpenAI Agents SDK:Sandbox memory example