能够叫停并改变一个运行中智能体的方向,是一项功能,而非一条错误路径。
长时间运行的智能体有时会走错方向,而用户往往会比智能体更早看出来。如果用户唯一的控制手段是"等它跑完"和"杀掉它、丢失全部进度",那他并没有真正的掌控权——他只有一个急停按钮和一份侥幸。本文把中断、任务中引导、以及人类与智能体之间干净的接管/交还,当作一等交互来对待:如何在不毁掉工作的前提下叫停、如何在不彻底重启的前提下注入纠正、以及如何在不丢失状态的前提下让控制权在人/机边界双向传递。
中断必须及时响应且不具破坏性。
两个属性才能让"停止"控件成为真的。响应及时:智能体必须迅速到达一个安全的停止点——而不是要等当前这个 90 秒的工具调用以及接下来计划好的三步跑完。不具破坏性:停止必须保留已完成的工作,而不是把它丢弃。一个会扔掉十分钟进度的中断,会训练用户永远不去中断,这意味着他们任由一次明显跑偏的运行继续下去——正是这个控件本该阻止的行为。
- 在步骤边界设检查点,使一次停止最多只损失当前这一步。
- 让进行中的副作用可中止;对那些无法中止的,用关卡(H2)把守,使中断永远不会落在一个不可逆动作的中途。
- 停止时呈现一份状态快照——什么已完成、什么待处理——让用户基于事实而非一片空白的暂停,去决定引导 / 接管 / 放弃。
区分暂停、引导与中止——它们是三种不同的意图。
把它们合并成一个"停止"按钮,会逼用户做得过头或不够。暂停:冻结,保留一切,意图是原样恢复。引导:停下,接受一个纠正,在保留先前进度的前提下带着新约束恢复。中止:停下并丢弃,意图是不再继续这次尝试。引导是价值最高、却最常缺失的那一个——用户通常并不想杀掉这次运行,他只想轻推它一下。
# Steer = correction folded into preserved state, not a restart ckpt = agent.checkpoint() await agent.interrupt(reason="steer") ckpt.constraints.add(user_correction) await agent.resume(from=ckpt) # keep prior work; apply new constraint
让引导无需停下整个世界即可触达:提供一个"另外,做 X / 别做 Y"的输入,用户可在智能体运行时提交,并在下一个步骤边界生效。多数纠正并不需要一次彻底的停机。
接管与交还是对称的——两者都要设计。
团队往往执着于智能体把疑难情形交给人类,却忘了返程。一次干净的接管会传递上下文,使人类不必重新推导智能体已经知道的东西:目标、已采取的步骤、状态,以及关键的它为何停下。一次干净的交还更难、也更被忽视:人类处理完后,智能体必须从人类编辑后的状态恢复——而不是从它自己接管前的陈旧记忆恢复。经典缺陷正是:智能体在恢复时覆盖了人类的手动修复,因为它从未调和过状态。
状态是每一次移交的底座——把它做成一个共享、可检视的对象。
中断、引导与交接,最终都归结为同一个原语:一个能在控制权变更后存活、且可被当前掌权方读写的状态对象。如果智能体的工作状态只存在于一个不透明的上下文窗口里,那么这些交互没有一个能做对——人类看不到自己接管的是什么,智能体也看不到人类改了什么。把任务状态(目标、计划、进度、产物、未决决策)外化为一个双方都能读写的结构。当前由谁掌握控制权必须对双方都显式且可见——控制权含糊不清,正是两个行动者编辑同一对象并丢失一次改动的方式。
恢复时先调和状态;绝不盲目继续。
在任何人类干预之后,智能体恢复时的第一个动作必须是重新读取共享状态、检测发生了什么变化,而不是从中断前的计划接着跑。世界可能已经变了:人类编辑了某个产物、先前的目标如今已部分达成、某个假设现在为假。把恢复当作"先对照当前状态重新定位,再行动"——这与一个细心的人接手被打断的任务时所用的、保守重启的纪律是同一个。
一个从自身记忆而非调和后的共享状态恢复的智能体,会悄无声息地回退人类的干预。这是最摧毁信任的交接缺陷——用户修好了它,眼看着智能体撤销了这个修复,从此再也不会交还。
什么时候不该允许自由形式的任务中引导。
如果某一步正处于一个不可逆副作用的中途,正确的控制是"干净地完成或回滚这一步",而不是"现在就接受一个任意的纠正"——把一个尚未提交的外部动作引导偏,对状态的破坏比完全不中断还要糟。