作者揭示了一个 AI Agent 实战陷阱:向目标服务查询「是否已发布」时,读到的是该服务自身的异步数据快照而非最新状态,导致重复检查逻辑失效。
有一个问题,我们的无人值守 agent 在发布任何内容之前都会问自己,而且很长时间以来我们以为那只是一个简单的问题。问题是:我之前发布过这个吗?
我们的答案是调用目的地的列表接口,拉回账户最近的条目,然后做比对。这个设计看起来很合理。目的地是内容真正待的地方。如果有谁知道我们发布了什么,那一定是存储它的服务。所以我们去问服务。
我们没有考虑到的是,一个答案背后有一条供应链。当我们的 agent 向那个接口提问时,接口并不会去查看持久记录然后回来回答。它给我们的是它自己的读取路径在那个时刻所持有的东西,而这个数据可能是很久之前由一个我们无法控制的过程拼装出来的,出于和我们的任务毫无关系的理由。我们读的并不是真相。我们读的是一份真相的快照,上面有一个未知的时间戳,而接口并不会在响应中带上那个时间戳。
实际的后果是,我们的重复检查继承了一个我们从未选择过的属性。那个 API 携带的任何陈旧性,现在我们的正确性也跟着携带了。我们并没有写一个陈旧性检查。我们在一个异步更新的数据源之上写了一个完全正确的检查,而异步在唯一一个方向上是会传染的——向下游传染。
这个框架改变了我们对修复方案的想法。本能反应是把问题问得更精准。加一个参数。更激进地问。问两遍。所有这些仍然是在问同一个对象,而对象的答案仍然是由同一套机制产生的。你无法通过审问来摆脱别人的刷新间隔。更激进地轮询能给你带来的,只是从同一个分布中取到一个稍微不同的样本,再加上尝试过所带来的自信——而这才是清单上最危险的东西。
真正有效的方法是把问题移到一个不同的见证者那里。产生输出的那个任务,以绝对的确定性、零延迟,知道它自己产生了输出。它就在现场。所以我们让那个任务自己写一条记录——在本地,在同一个工作单元里,在写操作发生的时刻。不是发送到某处的报告。不是通知。是作为做这件事的同一任务的一部分被提交的一条记录。
区别不在于存储技术,而在于同步性。生产者写的本地记录,在描述事件的同一时刻更新,因为它就是同一个操作。远程列表在那个服务内部传播逻辑决定的时刻更新,而这个时间表没有任何人发布给我们,也没有任何人欠我们。一个可以迟到,另一个如果迟了就会立即表现出错误。
这也重新定义了远程 API 的用途。它仍然有用。只是在我们自己最近历史的权威性上,它不是权威。它是在它已经吸收完毕的事情上的权威,这是一个真正不同的事实,而且出于其他原因值得了解。错误不在于调用它。错误在于让它回答一个关于我们的问题。
对于任何无人值守运行的东西,这个区别值得在设计中明确说出来,因为没有人会坐在那里注意到答案看起来过时了。人类会眯着眼睛看一个列表里少了自己一小时前记得做过的东西。Agent 不会眯眼。它拿过列表,找不到匹配,认为工作没做完,然后再来一遍——自信地、按时地、带着干净的日志行。
所以我们最终得出的规则很小:如果你需要知道你做了什么,就问做这件事的那部分系统。问其他任何人,你问的都是他们的账本,而不是你的行为。