跳转到主要内容

双写竞态:当你的智能体与用户同时编辑同一个日历事件时

阅读需 2 分钟Tian PanTian Pan

智能体自信地报告:“我已将会议改至周四下午 3 点。”用户却盯着原本周二上午 10 点的时段发呆,因为在智能体制订计划到提交更改的这段时间内,用户自己编辑了该事件。“最后写入者胜”(Last-write-wins)策略让自动化的操作覆盖了人类的修改,而用户对助手的信任也因这一次事故而崩塌。这就是双写竞争(dual-writer race),也是智能体工具链从未专门设计应对的 bug 类别。

大多数智能体平台都无意中继承了这一问题。工具层将 update_event 视为一个简单的函数调用:获取 ID,获取新字段,返回成功。底层的提供商 API 十多年来一直提供乐观并发原语(optimistic concurrency primitives)——ETags、版本令牌(version tokens)、If-Match 前提条件——但几乎没有人将它们贯通。模型无法知道它一分钟前所推理的世界已不再是现状,因为由于它所获得的抽象层静默地丢弃了这些信息。

只要智能体触及共享状态,这种模式就会随处可见:日历、CRM 记录、Jira 工单、共享文档、项目追踪器、工单队列。用户在智能体读取后的几秒钟内打开了一条记录,双方都进行了编辑,智能体最后写入,人类的更改随之消失。智能体报告成功。控制面板显示成功。用户却不认可,而且 Prompt 模板中没有任何道歉信息能修补这种损害。

为什么工具层丢弃了版本令牌

让我们看看生产环境下一个典型的“重新安排会议”工具是如何运作的。智能体调用 get_event(event_id),SDK 返回一个包含 summarystartendattendeeslocation 的已解析对象。智能体根据这些字段进行推理,选择一个新时间,然后调用 update_event(event_id, {start, end})。封装层对提供商执行 PATCH 请求。完成。

注意缺失了什么。提供商的响应中携带了一个 etag 字段——Google Calendar 在每个事件资源上都公开了一个,Microsoft Graph 在每个事件上公开了 @odata.etag。这些令牌标识了 智能体当时看到的版本。SDK 要么在反序列化过程中将其丢弃,要么保留了它但工具封装层从未读取,抑或是读取了但在 PATCH 请求中从未设置 If-Match。当调用离开你的网关时,请求的内容实际上是“无论该事件处于什么状态,都将其设置为这些字段”。“最后写入者胜”并不是智能体选择的默认行为,而是工具层内置的默认行为。

协议层面的修复是机械性的。Google Calendar 接受 If-Match: <etag>,并在资源自读取以来发生变化时返回 412 Precondition Failed。Microsoft Graph 要求对多种资源类型的 PATCH/DELETE 请求使用 If-Match,并在 ETag 过时时返回 409 Conflict——Planner 严格执行此规则,最近一篇关于将 AI 智能体连接到 Microsoft Planner 的实践指南明确指出:“永远在更新前立即获取,绝不要缓存 ETag。”Jira 的状态转换 API 出于同样的原因将其并发转换响应从 400 切换为 409 Conflict:409 是标准的信号,表示“你的写入在竞争中失败了”。

版本令牌是提供商免费提供给你的契约。工具层的任务就是确保它在端到端之间保持完整。

三层修复:带版本读取、带前提写入、将冲突视为结果

一个双写安全的工具需要三者协同工作,跳过其中任何一个都会在无意中重新引入竞争风险。

带版本的读取(Read-with-version)。 每一个读取工具都应返回对象及其版本令牌,并且智能体的工况内存应保持它们的配对。如果你现在的工具 schema 返回的是 {event: {...}},请将其改为返回 {event: {...}, version: "abcd1234"},并将 version 字段标注为“对模型不透明但在写入时必需”。智能体不需要理解这个令牌,它只需要将其传回即可。

带前提条件的写入(Write-with-precondition)。 每一个写入工具都应接受一个版本令牌,并将其作为 If-Match 转发给提供商(或提供商对应的机制——Microsoft Graph SDK 中的 IfMatch 参数、Google API 中的 If-Match 请求头、Jira 问题中的 version 负载字段、或者你在 Salesforce 上通过之前的 SELECT FOR UPDATE 存储的 _token)。如果智能体在调用写入时没有带版本号,工具应拒绝执行——“智能体忘记传递令牌”这一故障模式应该是显式的报错,而不是静默失败。

将冲突视为一种结果(Conflict-as-outcome)。 当前提条件校验失败时,工具不应进行重试。它应向规划器返回一个结构化的结果:“冲突:事件在你的读取和写入之间已被修改;新版本为 X。”这个结果必须是模型可以推理的一等结果,而不是隐藏的重试逻辑(即重新读取、重新应用并再次写入)。隐藏的重试是最糟糕的设计选择,因为它将“人类的更改很重要”变成了“人类的更改悄无声息地消失”——这正是版本令牌最初想要防止的失败情况。

第三层之所以重要,是因为如果只告诉智能体“写入失败,请重试”,它会很自然地重新读取,重新应用同样的意图,并再次覆盖人类的修改。智能体必须知道 世界已经发生了变化,这样规划器才能决定最初的意图是否仍然合理。一个用户刚刚删除了底层事件的会议改期请求,不再是改期操作,而是一个全新的决策。

当你无法简单重试时,冲突解决是什么样的

假设智能体(agent)计划将一个 30 分钟的周会从周二上午 10 点移至周四下午 3 点。在读取和写入之间,用户接受了来自他人邀请的同一日程的不同时间,现在该日程变成了周二上午 11 点。这时触发了 412 错误。智能体该怎么办?

存在三种可靠的路径,而选择哪一条取决于工具与用户之间的契约。

第一种是基于最新状态重新规划。冲突结果包含了该日程的新版本。规划器重新读取日程,看到用户的更改,并询问模型在新的初始状态下,原始意图(移至周四下午 3 点)是否仍然适用。通常情况下是适用的——用户更改了时间,但移至周四的目标没变。有时则不适用——用户已经亲自移动了会议,智能体现在的工作是什么都不做。

会员专享

余下内容仅对会员开放。

会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。

  • 完整文章,包含未公开存档的部分
  • 可落地的工作框架,附带权衡与决策依据
  • 新文章抢先看,先于公开发布

随时取消 · 一次订阅,畅读全部

参考资料

保持联系,关注我获取更多内容

阅读需 10 分钟

审计追踪的不匹配:当用户、智能体和工具各有各的日志时

智能体技术栈生成的四种日志往往无法对齐。解决方案并非增加更多日志,而是在用户操作边界生成的事务 ID、统一的审计记录,以及根据合规需求(而非子系统)确定的保留周期。

insider
ai-agents
阅读需 9 分钟

那个因等待另一个 Agent 而死锁的 Agent

两个能力出色的 Agent 可能会在你的账单飞涨时永远互相等待。为什么是协作——而非模型能力——导致了 Agent 群体的崩溃,以及分布式系统准则如何修复这一问题。

insider
ai-agents
阅读需 9 分钟

你的智能体读不懂的弃用通知

智能体不会阅读更新日志或 Sunset 响应头。本文将探讨为什么工具弃用对大语言模型智能体来说会静默失败,以及如何对工具契约进行版本化,从而让模型能够真正接收到通知。

insider
ai-agents
阅读需 9 分钟

那个批准了“单次调用成本”却从未衡量“单次解决任务成本”的智能体预算

一个使单次调用成本下降了 25% 但单次解决任务成本却上升了 40% 的智能体,是智能体部署中最常见的单位经济失效案例。本文将探讨为什么供应商的 SKU 并不是工作单元,以及如何建立正确的衡量指标。

insider
ai-agents
阅读需 10 分钟

重新路由回智能体的升级路径

当下游队列开始自行自动化时,`escalate_to_human` 工具就不再是人机回环了。探讨为什么契约的生命周期必须长于消费者。

insider
ai-agents