记忆与状态的混淆
当 agent 忘记一笔订单已经下过时,本能反应是加一个向量存储。这通常是错误的修法,因为故障不在于召回,而在于权威性。
在大多数产品中,「记忆」指的是对过往轮次的检索:把对话做 embedding,把新查询做 embedding,拉取最近的片段,粘贴进上下文窗口。这个机制对于语气的连贯性、对于用户三个会话前提到的引用、对于影响决策的背景事实,确实有用。它是对一堆只追加文本的概率相关性过滤器。
状态是另一种对象。状态是任务已承诺事项的权威记录:哪些步骤已执行、哪些副作用已落地、哪些义务仍未了结、哪些事实当前被认为为真以及依据谁的证据。状态有不变量。如果一笔订单已下,那么这笔订单恰好存在一次,再高的语义相似度也改变不了这一点。
检索改善召回。它无法强制不变量。检索器可以捞出「我下了订单 4471」这句话,也同样可能捞不出它,或者捞出一句声称订单已取消的过期句子。然后 agent 就在赢得相似度竞赛的那些片段上流畅地推理。整条流水线中没有任何东西能断言某个承诺恰好发生了一次。
这个模式在各团队间反复出现:一个偶尔重复扣款、重复发邮件或重新打开已关闭工单的工作流;一次得出「agent 忘了」结论的调试;一个提高 embedding 模型质量或 top-k 的提案。真正的缺口是缺失的事务日志。人们让 embedding 去干账本的活,而账本不是相似度问题。
状态层必须保证什么
状态层不是换了个好听名字的数据库。它是系统其余部分被允许假定的一组保证。其中三条最重要。
幂等性。 重跑一个步骤不得重复其副作用。这意味着每个对外可见的动作都携带一个由任务身份和步骤身份推导出的稳定键,执行器在行动前检查该键是否已被提交。检查与提交相对于该动作必须是原子的,或者至少其顺序要使得两者之间发生崩溃时可恢复。没有这一点,重试——每个健壮的 agent 运行时都需要重试——就会成为损坏的来源。
顺序。 agent 必须知道哪些承诺先于哪些承诺。有些依赖是因果性的(付款未结清就不能发货),有些只是顺序性的(日志未创建就不能追加)。顺序让一次恢复的运行能够判断还有什么是合法可做的。一袋检索到的事实没有顺序;状态机有。
可恢复性。 崩溃的运行必须从检查点恢复,而不是从头开始。这条保证把演示和系统区分开来。如果唯一的恢复方式是重放整条轨迹,那么每个步骤都必须幂等,每个外部调用都必须容忍重复——而实践中总有一个做不到。

这些保证不是免费的。状态层会增加写入延迟、schema 维护成本,以及每次任务模型变化时的迁移负担。它还会约束 agent:以前自由形式的步骤变成了必须声明其输入和效果的操作。这种约束正是重点所在,但它确实是实打实的成本,随意采纳它的团队会发现自己是在跟 schema 较劲,而不是在解决问题。
一个最小状态 schema
最小的可用状态模型把通常混成一团上下文的三类信息分开。
情节上下文——说过什么。轮次、工具输出、检索到的文档、摘要。这是 embedding 的自然归宿。它体量大、权威性低,且设计上就是有损的。
持久事实——什么是真的。每个事实都有版本、携带来源、携带时间戳。事实从不原地覆盖;新版本取代旧版本,旧版本仍可读。矛盾是需要解决的冲突,而不是静默更新。
待了义务——欠着什么。未完成的任务、等待中的响应、已安排的重试、未解决的冲突。这是 agent 在决定下一步做什么时查阅的工作集。
一个具体的形态,用伪 schema 表示:
facts(id, subject, predicate, value, source, observed_at, superseded_by)
obligations(id, task_id, kind, key, state, created_at, deadline)
episodes(id, task_id, role, content_ref, created_at)
义务上的 key 就是幂等键。事实上的 superseded_by 就是冲突解决链接。task_id 把所有东西串回单次运行。
设计的后半部分是暴露。状态的读写应该是显式工具,而不是运行时的隐式副作用。当 agent 调用 state.read_facts(subject) 或 state.commit(obligation_id, result) 时,这些调用会和其他所有工具调用一起出现在 trace 中。这让状态转换可以用与其他一切相同的机制来审计和调试。它也让故障模式变得可见:一次从未提交义务的运行,显然不同于一次提交了两次的运行。
代价是啰嗦。显式状态工具消耗上下文并增加轮次。把状态藏在运行时背后的团队得到更短的 trace 和更

暴露缺口的测试用例
三个测试能把真正的状态层和披着状态外衣的检索层区分开。
杀掉再恢复。 在运行中途终止进程,重启它,让它继续。如果结果与不中断的运行不同——副作用数量不同、最终状态不同、某步骤被跳过或重复——那么状态模型就是欠定义的。这个测试写起来便宜,揭示的东西却很残酷。大多数通过它的系统之所以通过,是因为任务短到可以从头重放;那和从检查点恢复不是一回事。
把同一个工具调用重放两次。 用相同的幂等键发出一个相同的动作,断言恰好存在一个副作用。然后用不同的键发出它,断言存在两个。后半部分很重要:把所有东西都合并掉的幂等方案,和什么都不合并的方案一样错。
注入一个过期事实。 写入一个事实,取代它,然后给 agent 喂一个只包含旧版本的上下文。agent 必须检测到冲突,并对照权威记录进行调和,而不是依据过期值行动。没有版本化事实的系统会静默地通不过这个测试,因为过期值看起来和新鲜值一模一样。
对于多 agent 设置,还值得加第四个测试:两个 agent 并发尝试同一个义务。状态层必须将它们串行化,落败的一方必须观察到工作已经完成,而不是继续推进。这正是共享的事务性记录体现价值之处;共享的向量存储提供不了这种保护。
检索仍然该待在哪里
以上没有任何一点是在反对检索。情节上下文确实庞大、确实模糊,经过摘要和搜索后确实有用。争论的焦点是权威性:检索可以为一个决策提供信息,但它不能成为决定某个承诺是否已经存在的东西。
结论
当前产品意义上的记忆是一种召回机制。状态是一种承诺机制。它们解决不同的问题,把它们混为一谈会产生流畅、索引良好但错误的系统。修法很少是更好的 embedding 模型。它是一条事务日志、一个幂等键和一个检查点——那些不光鲜的机制,让 agent 能够被托付有后果的工作。
封面照片:panumas nikhomkhai / Pexels。
