跳到主要内容

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

· 阅读需 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 分类法:修正、覆盖、取消

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

航向修正(Course correction) 是增量式的。代理正在做大致正确的事情,但用户想要调整细节:“比起内联样式,更倾向于使用 Tailwind”、“跳过认证模块,它已经完成了”、“在重构之前编写测试,而不是在之后”。航向修正进入转向收件箱并合并到上下文中。它们不应暂停执行;代理会在下一个自然边界读取它们。在 UX 层面,它们需要一个始终位于流下方的文本输入框,最好带有明显的“在第 N 步送达”的回执,这样用户就知道它何时生效了。

覆盖(Override) 是指令式的。代理正朝着用户不希望的方向前进,用户需要停止当前轨迹但保留状态:“放弃这种方法,回滚到数据库迁移之前,尝试不同的角度。”覆盖映射到检查点操作——将状态回滚到命名点,可选地重写计划,然后让代理继续。它们应该短暂暂停执行(你不希望针对即将回滚的状态发起新的工具调用),并在销毁已完成工作时要求显式确认。

取消(Cancellation) 是终结性的。用户希望运行结束、释放资源,除了对话记录外不保留任何内容。这就是原始的“停止”按钮,一旦分类法就绪,它终于可以表现得像一个杀掉进程的开关而非万灵丹。取消应该是一键式的且不可逆;如果用户可能想要保存任何内容,他们本该使用“覆盖”。

这种分类法之所以重要,是因为大多数关于代理 UI “过于激进”或“过于被动”的抱怨,实际上是模式选择的失败。想要修正的用户被迫选择覆盖,因此他们学会了干脆不去中断,让代理错误地完成任务。想要取消的用户担心丢失工作,因此他们守着糟糕的运行直到结束。三个标签清晰、背后有正确语义支撑的按钮,可以消除这两种失败模式。

晚到一个工具调用的竞态

最难处理的失效模式并非架构上的,而是时间维度的。用户看到智能体即将出错,输入了更正,按下回车,却眼睁睁看着错误的工具调用仍然触发了,因为消息恰好落在模型决策与工具分发之间。

这种竞态并非假设。在多个智能体框架(agent harnesses)的 Issue 中都描述了同样的模式:用户在工具调用期间按下 Escape 键,中止处理器(abort handler)查找 activeChatRunId,却发现它尚未设置,因为它是请求返回后才被赋值的,于是中止操作静默地执行了空操作。智能体则心安理得地完成了变更。

三种缓解措施可以防止这变成一场永无止境的 Bug 追踪。首先,在第一个工具调用触发之前设置运行标识(run identity),而不是之后——这样竞态中止的窗口期会缩小到微秒级。其次,将工具分发路径设计为在其前导阶段检查转向收件箱(steering inbox),这样即使是 1 毫秒前到达的消息,也能在副作用发生前被察觉。第三,将破坏性工具(文件写入、HTTP 变更、数据库更改)设计为在调用端可取消或可补偿,这样即使在调用开始后才到达的中断,仍然可以阻止提交。最后一点往往是团队最容易忽略并感到后悔的:如果用户的覆盖指令无法撤销 3 秒前触发的变更,那么整个转向机制的逻辑就会崩塌。

对于非破坏性的工具调用——如读取、检索、网页抓取——这种竞态并不那么重要,因为最坏的情况只是浪费了算力。但对于破坏性调用,这关乎成败。最简单的原则是在你的清单(manifest)中为工具标记可逆性标志,并对不可逆的工具要求确认步骤(或更长的转向窗口)。这虽然会在少量调用中增加些许延迟,但能为你换来一个在用户最需要的时刻不会失效的转向机制。

何时不该构建此功能

转向机制的成本很高。检查点机制(checkpointer)在每一步都会产生存储开销和写入延迟。双向通道会使你的部署拓扑变得复杂。计划即对象(plan-as-object)的原则会限制你编写智能体循环的方式。如果你的产品运行智能体的时间只有 30 秒,且用户可以耐心等待,那么你完全不需要这些——“中止并重试”的流程就足够了,工程预算最好花在其他地方。

当运行时间长到重启会令人痛苦、当工具调用在执行真实任务(消耗资金、改变状态、联系用户)、或者当用户是希望与智能体协作而非仅仅从高处监管的专家时,转向机制就成了核心支撑。编码智能体、研究智能体以及数据管道智能体都会迅速跨过这条线。而面向客户的单轮对话助手大多不会。

诊断性问题很简单:观察你产品中的 5 个用户,数数他们有多少次按下“停止”并立即带入微小修改重新提示。如果答案大于零,你就遇到了伪装成“重启习惯”的转向问题,而每一次重启都在丢弃那些本可以通过“检查点与注入循环”保留的工作。

修复的形态

从“仅支持中止”到“可转向”智能体的转变是一项具体的基础设施工程:一个带检查点的状态存储、一个持久化的运行通道、一个用户可以编辑的计划展示面,以及 UI 上的三个动词控制词汇。这些都不是研究前沿——在 LangGraph、Claude Agent SDK 以及每一个认真对待长时间任务的智能体框架中,这些原语都已经存在。这项工作的核心在于,在你的用户学会不再打断智能体之前,决定这项投入是否足够重要。

在 2026 年,那些让人感到具有协作性的智能体,并不是在推断意图方面变得更聪明的那些。而是那些实现了可被打断且不必被彻底杀掉的智能体。这不是模型的能力,而是一种架构选择,它就在“中止”按钮的另一端等待着被实现。

References:Let's stay in touch and Follow me for more thoughts and updates