写作

Agent 产品中的状态所有权

可靠的 Agent 产品始于为每一类事实确定唯一 authority,而不是让推理 Loop 变得更加复杂。

当 Agent 从 Demo 走向真实产品时,一个很自然的想法是让整个 Loop 持久化:保存每条消息、每次 Tool 事件、计划、部分结果、暂停与重试。如果进程断开,就重建一切,像什么都没有发生过一样继续运行。

我曾经也认为这是通往可靠性的道路。但在实践中,它经常会在产品内部造出第二套操作系统。

数据库开始复制 Runtime,前端又构建一个 Reducer 来解释这份副本,恢复逻辑需要判断哪一个 Snapshot 更新。某项工作可能已经完成,但对话仍然认为它正在运行。

根本问题不是 Durability,而是 authority。

一类事实,只能有一个 authority

Agent 产品里同时存在多种看起来相关、生命周期却完全不同的状态:

  • Conversation 保存用户与系统说过什么;
  • 当前 Stream 表示一段仍在生成的回答;
  • Background Work 负责必须活过当前 Request 的执行;
  • Artifact 保存这项工作最终交付的长期结果;
  • 领域记录 保存产品特有的事实、权限与 lineage。

当每一类事实都有唯一的权威所有者时,系统会更可靠。其他界面可以从它派生视图,却不应该创造平行的真相。

如果 Message Timeline 与自定义 Run Snapshot 都声称拥有当前回答,重连就会变成一次 Merge。如果浏览器和 Worker 都声称拥有任务状态,刷新就会变成竞态。如果同一个 Artifact 被复制进多条消息,“哪一个结果才是当前版本”会变得出乎意料地难以回答。

一个架构是否清晰,可以用一个简单问题判断:

当同一事实的两种表示互相冲突时,谁说了算?

Request 不是工作的生命周期

HTTP Request 是传输边界,但它不会自动成为工作的生命周期。

只要 Agent 提交了耗时、昂贵或会影响外部世界的任务,这个区分就非常重要。关闭页面可以结束当前 Stream,却不应该让已经被接纳的工作消失。相反,取消一次 Request,也不等于外部系统已经停止了它接受的任务。

这些其实是不同的事件:

  1. 用户断开连接;
  2. 模型停止生成当前 Turn;
  3. 产品要求 Background Task 取消;
  4. 外部执行方接受或拒绝取消;
  5. 一个迟到的结果仍然返回。

如果把它们全部压缩成一个叫作 isRunning 的布尔值,重复执行、幽灵失败和结果丢失就会进入产品。

恢复结果,而不是每一个 Token

Durability 并不要求完整重放 Agent 的内部体验。

有些状态因为会影响产品结果,所以必须恢复:用户已经确认的决定、已经被接纳的任务、付费边界、完成的 Artifact,或者仍在等待人的输入。另一些状态则可以是短暂的:只生成了一半的句子、已经放弃的推理分支,或者 UI 动画状态。

更有用的恢复契约其实更窄:

  • Conversation 仍然连贯;
  • 已被接纳的工作仍然可以找到;
  • 完成的结果只被呈现一次;
  • 用户知道接下来还有什么需要处理;
  • 重试不会再次产生同一个外部副作用。

它未必会恢复断线前存在过的每一个 Token,却保住了用户真正在意的东西。

Chat 负责解释,Artifact 负责交付

消息非常适合表达意图、协商与解释,却不适合作为长期结果的 authority。

Artifact 应该拥有自己的身份、状态、版本、来源,以及与执行过程之间的关系。消息可以引用 Artifact,而不需要变成它的又一份副本。

这种分离会消除大量界面歧义。Conversation 说明发生了什么,Artifact 界面展示当前结果,任务系统表达执行是否仍在继续。它们不再需要互相冒充。

自主权存在于不变量之内

明确 authority,并不意味着产品需要提前规定 Agent 的每一条路径。

产品应该拥有权限、消费上限、有效输出契约、身份与会产生外部后果的状态。Agent 可以拥有计划、探索、Tool 选择、候选数量,以及何时改变方向。

这就是不变量与 Workflow 的区别。不变量保护系统,Workflow 则提前决定方法。

如果我们把一组固定阶段持久化,再称它为 Agent Runtime,我们只是让 Control Plane 变得更耐用,却没有让 Agent 变得更有能力。

可观测性应该沿着所有权展开

好的可观测性不是把所有内部事件塞进一条时间线,而是让我们可以在正确的 authority 上回答问题:

  • 用户要求并确认了什么?
  • 哪项工作被谁接纳?
  • 什么正在运行、已取消、失败或完成?
  • 哪个 Artifact 来自哪一次 Invocation?
  • 哪个外部副作用可能已经发生?

当答案来自明确的所有者,排障就会变成收集证据,而不是在相互竞争的 Snapshot 之间考古。

可靠性经常来自删除

我们很容易用另一个状态字段、兼容路径、恢复 Worker 或 Reducer 分支来回答每一次失败。有时这确实必要,但更多时候,它意味着两个系统都相信自己拥有同一个事实。

更有效的重构经常是做减法:选定 authority,让其他层从它派生,然后删除平行的 Control Plane。

真正耐用的 Agent 产品,不是记住每一次内部动作的产品,而是能保住正确结果、让每一类事实拥有明确所有者,并且在网络、进程、模型或用户做出意外行为时,依然可以被人理解的产品。

文章数据

次阅读条评论

讨论

评论区

关于文章本身,也欢迎留下不同意见或补充。

留下评论

评论会立即公开,并可在当前浏览器中删除。

发布时会进行一次隐私友好的人机验证。