智能体完成任务时房间已空:异步后台任务中的过时上下文交付
一个需要 90 秒才能完成任务的后台智能体,其操作基于的是 90 秒前的世界快照。当它返回结果时,用户可能已经导航到了不同的视图,开始了一个新的对话,归档了原始请求,或者完全关闭了标签页。大多数智能体框架无论如何都会交付结果,修改状态以反映结果,并将这次往返视为成功。但这并不是成功。这是智能体在一间空屋子中结束。
这种失败模式比直接丢弃结果更糟糕。丢弃结果只是一次投递失败——虽然烦人但可以恢复。而应用陈旧的结果则是对一个用户不再提出的问题的回答,它是针对不再匹配的状态编写的,往往会覆盖用户已经开始的新工作。用户会注意到发生了他们没有要求的事情,却无法重构原因,从而对系统失去信任,这种信任损失是简单的超时永远不会造成的。
解决办法不是更快的智能体,而是一个交付时的相关性门控,它将返回的时刻视为一个全新的决定,而不是派发时刻预设的定论。
派发时间谬误
异步智能体任务的默认心理模型是等待时间较长的“请求-响应”。用户提出要求,智能体进行处理,然后在某个时间点返回答案。框架也倾向于这种构架——续订令牌、轮询端点、完成回调(webhooks)、以及工作完成时的推送通知。基础设施很稳固。
但基础设施没有体现的是,任务的输入不仅仅是派发时的提示词(prompt)。输入是从派发到交付期间会话状态的整个轨迹。如果用户说“帮我找下周二去东京最便宜的机票”,然后在 90 秒后,他们已经查看了日历,取消了下周二的会议,并重新预订了下个月的行程,那么原始答案不仅是陈旧的——相对于智能体从未见过的世界状态来说,它甚至是错误的。
派发时间谬误假设:用户提问时成立的事实,在智能体回答时依然是正在处理的问题。在同步对话中,这一假设是成立的,因为用户正盯着加载动画。而在后台任务中,这一假设本身就是个 Bug。
为什么 90 秒是危险区间
亚秒级的响应存在于同步世界中;用户被锁定在对话中。耗时数天的任务存在于明确交付的世界中;用户已经提交了请求并离开了,结果明天出现在收件箱里正是他们所预期的。
危险区间是中间地带——时间长到用户不会等待,但又短到他们还没有在心理上将该任务归类为离线处理。30 秒到 5 分钟大约是“空屋子”问题的高峰期,因为系统基于“用 户仍停留在派发任务时的页面”这一假设在运行,而用户则基于“可以自由去做别的事情”这一假设在行动。
这也是智能体框架竞争最激烈的区间。运行数十分钟的深度研究智能体足够庞大,以至于每个人都会为它们设计收件箱。亚秒级的工具调用足够快,以至于没人会费心去搞异步。唯独在这个中间地带,懒惰的默认做法——“我们直接把结果发回用户之前所在的位置”——产生的危害最大。
交付时的相关性门控
架构上的转变是在交付时刻插入检查,而不仅仅是在派发时刻。该检查回答一个问题:结果是否仍然适用于当前的会话状态?
这种门控的一个可行方案包含三个部分。
首先,在任务开始时快照相关的会话状态,并随任务一起发送。这不是完整的对话记录,而是智能体推理所依赖的一切——当前文档、对话线程 ID、页面或工具上下文、以及使提示词具有意义的参数。对其进行哈希(Hash)处理。
其次,在交付时,根据当前会话状态重新计算相同的哈希值并进行比较。哈希值相等意味着用户没有移动,结果可以直接应用。哈希值不同意味着用户已经移动,系统需要判断这种差异是否重要。
第三,根据该差异将结果分类到三个桶中。仍适用 (Still-applicable):当变化只是表面性的——用户在相同上下文中导航、滚动,或者进行了不影响答案有效性的编辑。仅建议 (Advisory-only):当变化是实质性的,但结果作为信息而非操作仍然有用——将其作为通知呈现,让用户选择是否应用,而不是直接修改状态。丢弃 (Discard):当变化已使底层问题失效——记录结果,告知用户原始任务已被后续事件取代,不要静默应用任何内容。
对于模糊情况,默认应为“仅建议”,而非“仍适用”。存疑时,显示通知,不要修改状态。
快照需要捕捉的内容
人们往往容易产生过度快照的冲动——存储完整的会话、完整的 DOM 状态、每一项偏好。这会产生一个过于敏感的哈希值:每一次细微的视觉变动都会导致结果失效,丢弃率飙升,工程师们最终会因为干扰太大而关闭门控(gate)。
另一个极端——仅对提示词文本进行快照——是目前大多数系统的做法。这又太迟钝了:系统无法察觉用户是否已切换到另一个文档、开始了新会话,或者撤销了最初触发任务的操作。
合适的粒度应该是提示词的因果包络(causal envelope):即在保持恒定的情况下,能使原始提示词的原意依然成立的最小状态集。对于“总结此文档”任务,它是文档 ID 和版本。对于“寻找最便宜的航班”任务,它是行程参数加上日历窗口。对于编码代理的“修复此测试”任务,它是文件路径、测试名称以及工作树哈希。这种准则是针对每种任务类型去问:世界需要发生什么样的变化,才会导致这个答案不再是用户想要的答案。
这并非框架的通用属性。它是代理作者拥有的、针对每个任务的契约,就像 HTTP 端点拥有其幂等键(idempotency key)一样。
