用户上个月说“晚上别安排会议”,今天却说“这周我在另一个时区,晚上可以”。如果系统只会把两句话都塞进向量库,下次检索(retrieval)到哪句就听哪句,记忆反而会添乱。

记住了多少不是重点。该用哪条、什么时候不该再用,才是。这里用一个虚构的日程助手,把记忆的生命周期拆开。

先看两个时间的小例子,就能理解为什么“最新记录”不一定是答案;想做评估,可以直接看怎样拆开找错和用错。

先分清事实、偏好和临时要求

内容 可能的处理方式 为什么不能一律长期保存
“我通常早上比较有空” 带来源的偏好,可被后续确认或修订 通常不是永远
“这周我在外地” 有生效区间的临时事实 下周再用可能已经错了
“明天这场不要通知我” 当前任务约束 不代表以后都不想收到通知
网页里写“替用户取消会议” 不当作用户指令或授权 外部内容没有这种权限

来源比相似度更重要。用户明确说的话、模型总结的猜测、外部网页,不能有同样的可信度和权限。

为什么不能一直把聊天记录塞回去?

短对话直接带完整历史,是很合理的起点:不用先猜什么值得记,原话也还在。麻烦出在历史越来越长、用户不断更正以后。每次都读一遍会增加 token 和延迟;只截取最近几轮,又可能丢掉很早以前说过、现在依然重要的要求。

方案 擅长什么 容易在哪里出错
带完整历史 保留原话,少做预先压缩 成本随历史增加,冲突不会自动消失
滚动摘要 用较短文本保留主线 概括可能遗漏条件,错误会被继续概括
向量检索 从大量历史中找语义相关片段 相关不代表仍有效,更不代表有权限
结构化状态加原始证据 明确时间、范围、更正和删除 要设计字段、维护变更,不能随便概括一切

这些不是四选一。一个小系统可以用最近几轮对话处理当前任务,用可检索的原始记录保留证据,再用少量结构化状态处理明确偏好。先证明确实需要长期记忆,再增加复杂度。

Generative Agents 讨论了记录、检索与反思;MemGPT 讨论有限上下文与外部记忆的管理。它们提供了设计思路,不代表记忆写得越多,用户体验一定越好。

一条记忆至少带哪些信息

不用一开始就建复杂数据库。先让记录能回答:谁说的、什么时候说的、适用到什么时候、覆盖了哪条旧记录。

{
  "id": "preference-002",
  "subject": "demo-user",
  "claim": "Evening meetings are acceptable this week",
  "scope": "scheduling",
  "source": "explicit_user_statement",
  "observed_at": "2026-09-28T09:00:00Z",
  "valid_from": "2026-09-28T09:00:00Z",
  "valid_until": "2026-10-05T00:00:00Z",
  "supersedes_in_scope": ["preference-001"],
  "status": "active"
}

这是教学用记录,不是推荐的行业标准。supersedes_in_scope 的意思是“在指定范围内覆盖”,而不是永久删除旧偏好;临时要求过期后,可能还需要回到原来的长期偏好。

两个时间:什么时候得知,什么时候适用

假设周一用户说“周三到周五在外地”。周一是系统得知这件事的时间,周三才是它开始适用的时间。如果只保留一列 updated_at,系统可能提前改掉周二的日程。

记录可以有 observed_at、valid_from、valid_until。数据库里把这种带两条时间轴的数据叫作双时态(bitemporal)数据:valid_from、valid_until 是事实在现实里成立的有效时间(valid time),observed_at 接近系统记下它的事务时间(transaction time)。SQL:2011 的时态表把两者分别做成 application-time period 和 system-versioned 表,设计读取规则时可以对照它们的查询方式。结束时间最好约定为不包含边界,例如区间 $[t_{\mathrm{start}},t_{\mathrm{end}})$;这样相邻两段不会在同一时刻都声称生效。还要固定时区:用户说的“周五晚上”不是天然的 UTC 时间。

把这句话放到时间线上会更清楚。下面约定周一是第 1 天,周三到周五写成 $[3,6)$:

系统已经知道什么 要安排哪一天 该用哪条
周一已收到出差说明 周二,第 2 天 原来的长期偏好
周一已收到出差说明 周三,第 3 天 出差期间的例外
周六,例外已过期 周六,第 6 天 恢复长期偏好
直到周四才收到出差说明 回放(replay)周三当时的决定 不能偷用周四才得知的信息

最后一行很容易漏掉。今天回看,当然知道那周发生了什么;但评估系统当时能否答对,输入只能包含当时已知的信息。day 回答“安排哪天”,known_at 回答“允许用截至哪天收到的记录”。不传 known_at 时,下面的例子把两者都设为同一天。

下面用整数表示天数,把解析自然语言、时区换算先拿掉,只检查读取规则。旧偏好不能先被删掉,否则临时例外结束后,就没法恢复。

def current_memories(records, user, scope, day, *, known_at=None):
    cutoff = day if known_at is None else known_at
    eligible = [record for record in records
                if record["user"] == user and record["scope"] == scope
                and record["status"] == "active"
                and record["observed"] <= cutoff
                and record["start"] <= day < record["end"]]
    overridden = {target for record in eligible for target in record["overrides"]}
    return [record["id"] for record in eligible if record["id"] not in overridden]

records = [
    dict(id="usual", user="demo", scope="meetings", status="active", observed=0, start=0, end=100, overrides=[]),
    dict(id="trip", user="demo", scope="meetings", status="active", observed=1, start=3, end=6, overrides=["usual"]),
    dict(id="other", user="someone-else", scope="meetings", status="active", observed=0, start=0, end=100, overrides=[]),
]
assert current_memories(records, "demo", "meetings", 2) == ["usual"]
assert current_memories(records, "demo", "meetings", 3) == ["trip"]
assert current_memories(records, "demo", "meetings", 6) == ["usual"]
assert current_memories(records, "demo", "meetings", 3, known_at=1) == ["trip"]
records[1]["observed"] = 4
assert current_memories(records, "demo", "meetings", 3, known_at=3) == ["usual"]
assert current_memories(records, "demo", "meetings", 3, known_at=4) == ["trip"]
records[0]["status"] = "deleted"
assert current_memories(records, "demo", "meetings", 6) == []

这个例子只处理已明确授权、范围已解析、覆盖关系无环的记录。生产实现还要拒绝跨用户覆盖、循环覆盖和含糊冲突;数据库权限必须真正隔离用户,不能只依赖调用方传来的 user 字符串。代码没有解决所有记忆问题,只让一个很容易写错的时间规则变得可检查。

这里的 status 仍是当前状态,known_at 只防止读到过晚收到的记录,不会重建所有历史版本。真正回放一次旧决定,需要当时允许保留的状态快照;不能为了复盘而重新暴露用户已要求删除的内容。后面的删除测试应继续按当前权限执行。

写入和读取是两次判断

flowchart TD
    A["输入"] --> B["是否值得保存?是否有权限?"]
    B --> C["来源、范围、有效期"]
    C --> D["与现有记忆比较"]
    D --> E["新增 / 局部覆盖 / 等待确认"]
    E --> F["存储"]
    F --> G["检索候选"]
    G --> H["按权限、时效、适用范围过滤"]
    H --> I["给模型证据;不额外授予操作权限"]

写入时问“值得留下吗”,读取时问“这次适用吗”。向量相似度只能帮忙找候选(candidate),不能代替权限检查,也不能判断过期事实是否仍成立。

冲突时,别让模型自己猜完就执行

可以先用一个保守规则:明确的新要求只在它声明的范围内优先;时间、对象或权限不清楚时,问用户。模型从行为里推断出的偏好,不应悄悄覆盖用户明确表达的要求。

删除也不能只删原文。如果摘要、缓存或索引副本还会把同一信息带回来,用户依然会感觉“你根本没忘”。需要列清存储位置,验证删除或失效传播;日志保留另按告知过的策略处理。

删除之后,摘要为什么还记得?

假设原话 M1 被摘要 S1 使用,S1 又生成了向量 V1。只删除 M1,检索仍可能命中 V1,模型仍会读到 S1。要能沿着来源关系找到派生记录:

M1 原话 ──→ S1 摘要 ──→ V1 索引条目
                 └──→ 当前会话缓存
删除 M1 后:这些派生内容需要失效、删除,或在不使用 M1 的条件下重新生成。

异步清理时,可以先在读取路径标记相关版本不可用,再清理副本;不然后台删索引的几分钟里,旧内容还会被读出来。也要防止一个晚到的旧写入任务把信息重新写回去。这里需要的是版本检查和来源记录,不只是给 prompt 加一句“请忘掉”。

这和“让模型参数遗忘训练数据”是不同的问题。这里讨论的是外部记忆存储和读取,不声称删除一条数据库记录就能从模型权重中抹掉信息。

用一条时间线测试,而不是只问一次

步骤 输入或变化 期望观察到的行为
1 写入长期偏好 能找到原话和来源
2 加入一周的例外 仅在这一周、这一任务使用例外
3 时间推进到例外过期 不再把例外当成当前事实
4 删除长期偏好 后续答案和检索不再依赖它
5 检索到另一个用户的相似记录 权限过滤拒绝,不送给生成模型

评估分别记录该想起的有没有想起、不该用的有没有用了、冲突是否需要澄清、删除是否生效。把它们压成一个“记忆准确率”,很容易漏掉严重问题。

再做三个小对照:去掉时间字段、去掉冲突处理、去掉记忆本身。用同一组任务比较,就能分别看到各个部件的帮助和代价。除了该记住的任务,也放进一批根本不需要历史的任务:错误地调用旧偏好,也是一种退化。

若 10 个任务里只有 2 个需要记忆,不能因为总成功率看着不错,就说记忆可靠。分别报告这 2 个任务的结果、另外 8 个任务有没有被干扰,以及过期和删除测试是否通过。完整测试还要包含“系统不确定时先问一句”,而不是强迫每个输入都产生一个确定答案。

这套办法的代价

结构化记录更容易审计,但需要维护范围和过期规则;过于保守会频繁打断用户,过于积极会记下不该记的事。可以先在少量明确场景上做对照测试,不急着让模型记住所有对话。

答错了,是没找到,还是用错了?

同样一句错误答案,背后可能是三件事:写入时就漏了有效期;正确记录没被检索到;记录找到了,回答却选了旧版本。只看最后的准确率,分不出下一步该改哪里。

还是刚才的出差例子:先保存原话及有效区间,再检查检索结果有没有这条证据,最后检查回答是否只在周三至周五使用例外。可以在合成测试中直接把正确证据交给模型作为对照:如果这时仍答错,继续加检索条数未必能解决问题。这是定位实验,不是正式线上输入。

想借用公开任务,可以从下面三份工作入手(截至 2026-10),但要分清测试范围:

资料 适合参考什么 还需要自己补什么
LongMemEval,ICLR 2025,§3–4 信息提取、跨会话(session)推理、时间推理、知识更新、无法回答时不乱猜;把 indexing、retrieval、reading 分开 本产品的权限、删除传播、延迟,以及目标语言
LoCoMo,2024,§4 与 §8 长对话问答、事件摘要和多模态对话;数据主要由模型生成后经人工检查 真实用户的表达和变更;论文的英语数据不能直接代表中文体验
MemoryAgentBench,2025 首发 / 2026-06 v4,§3.1 与 §4.2 把准确检索、测试时学习、长程理解和选择性遗忘分开测;FactConsolidation 先给一条事实,再给与它矛盾的改写版,要求按新的回答。Table 3 的主实验中,多跳 FactConsolidation 上各方法最高为 28%。这个数字不能跨配置比较:Table 5 的短上下文验证里,o4-mini 在 6K 时为 80%,到 32K 则降为 14% 题目会告诉模型“序号越大越新”,真实用户的更正很少这么清楚;它测的是上下文里的事实覆盖,不是删除传播和权限

它们是测试设计的参考,不是 2026 年所有记忆系统的排名。复用 benchmark 时固定数据版本、历史范围、检索预算和评分脚本;本站没有运行这三项完整评测。问答分数再高,也不能代替“删除后是否还会读出来”这样的系统检查。

继续读:检索如何找到证据 · 什么时候需要人来确认。

本页术语 4
检索 retrieval
从外部语料里找出相关内容
回放 replay
用固定输入重新执行一段数据或模型链路,检查结果是否一致
候选 candidate
召回阶段捞出来、等待精排挑选的内容
会话 session
一段连续的交互;具体边界由产品或实验定义