跳到主要内容

飞行中转向:无需重启即可重定向长时运行的智能体

· 阅读需 11 分钟
Tian Pan
Software Engineer

观察一个开发者使用代理型 IDE 二十分钟,你会看到同样的小剧场上演三次。代理开始了一个长任务。在两次工具调用后,用户意识到他们想要一个函数式组件而不是类,或者想要 v2 接口而不是 v1,亦或是想用 Vitest 而不是 Jest 编写测试。他们手中只有一个杠杆:红色的停止按钮。他们按了下去。代理在编辑中途阵亡。他们复制并粘贴上一个提示词,加上修正,然后为前八分钟的工作支付了两次费用。

中止按钮是错误的交互设计。它将“我想调整计划”和“我想丢弃这次运行”视为同一种动作。在实践中,它们就像方向盘和弹射座椅一样迥异,而将两者混为一谈,正是为什么许多代理产品在任务耗时超过一屏输出时就显得脆弱不堪的原因。

解决方案不是更好的进度条或更自信的模型。它是一套架构接缝(architectural seams),让用户能够在不丢弃正确工作的情况下引入新信息;以及一套 UX 词汇,将航向修正、覆盖与取消区分开来,让用户无需思考就能选出正确的操作。

仅支持中止的 UI 是伪装下的分布式系统 Bug

大多数代理 UI 继承了请求-响应式 Web 应用的形态。你提交一个提示词,流(stream)回传,你看着它落地。反向流动的唯一控制信号只有 HTTP 连接本身——掐断套接字,你就杀死了运行。当工作单元能塞进单个响应时,这种模型没问题。但当工作单元是一个包含十几次工具调用的十五分钟计划,而用户在第七分钟有了新信息时,它就崩溃了。

根源在于缺少一个通道。令牌流和事件向下发送给客户端;但在运行结束前,几乎没有任何东西向上回传。当用户最终按下 Escape 键时,他们触碰到的是一个无状态的 HTTP 边界,该边界必须同时销毁流和推理流水线。这里不存在“继续运行但听我指挥”的原语。因此,产品发布时将“停止”作为唯一选项,每一次修正都变成了重启。

到 2026 年,交付真正的“飞行中转向”的团队——从 GitHub Copilot 的代理模式工作到 Codex 风格的编辑器——都得出了相同的诊断:你需要一个持久的双向通道、一个明确的在途运行标识(in-flight run identity),以及服务器上的一个接收转向输入的地点,以便代理在行动之间能获取这些输入。如果没有这三样东西,每一次中断都是一场与取消语义的竞态,用户的修正通常会比工具调用晚到一步。

值得构建的三个架构接缝

一旦你接受了转向是一个传输与状态问题而非 UX 皮肤,三个接缝就能完成大部分工作。它们不是替代方案,而是可以组合的。

检查点与注入(Checkpoint-and-inject)。 代理的状态——计划、草稿板、工具调用历史、检索结果——存储在一个检查点工具(checkpointer)之后,它在每一步后都会写入。转向输入变成了一个结构化的更新,在下一步读取之前合并到检查点中。LangGraph 的 interrupt() 原语是参考实现:图形持久化、暂停,并从同一个线程 ID 恢复,同时合并入用户的负载(payload)。这个接缝的价值在于正确的工作得以保留。你不会因为第八个调用方向不对,就撤销前七个工具调用。

计划修订钩子(Plan revision hooks)。 在循环的某个地方,代理会提交一个计划——一个明确的步骤列表、一个子目标树或一个 TODO。将该计划暴露为一个一等公民的可编辑对象(不是日志行,也不是消息),为用户提供了一个自然的干预点。编辑步骤、重新排序、删除、添加约束,并让代理在下一次迭代中重新读取计划。这就是在对话框中大喊“停下,停下,换个做法”与指向第 4 步并输入“在此处使用 v2 API”的区别。后者是可操作的,因为它落在了代理已经参考的结构化表面上。

软中断标记(Soft-interrupt tokens)。 在工具调用之间——或在长任务步骤内的任何自然检查点——代理会读取转向收件箱。如果有消息等待,它会被剪辑进上下文中作为系统笔记,由代理决定是重新规划、确认还是继续。收件箱是非阻塞的:用户可以随时向其中放入笔记,代理在下一个安全点提取它们。Claude Code 的钩子事件(PreToolUsePostToolUse、活跃运行期间的 UserPromptSubmit)是一个具体的实例化;为 Copilot 代理模式提议的异步转向笔记则是另一个。共同的见解是:代理即使在不暂停时也应该轮询用户输入。

UX 分类法:修正、覆盖、取消

具有接缝只是工作的一半。另一半是给用户提供三个不同的动词,而不是一个。

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates