← 返回观点

为什么超时要贯穿到底层执行器

超时如果只停在助理表层,底层命令、子进程和桌面动作仍可能继续跑。本文解释为什么执行型 AI 助理必须把 timeout 一路传到底层执行器,并用优雅停机替代简单粗暴的直接杀掉。

先说结论:对执行型 AI 助理来说,超时不是“前端等多久”的 UI 参数,而是一条必须贯穿到最底层执行器的约束。 如果只有助理表面停止等待,而底层 shell 命令、子进程、浏览器自动化或桌面动作还在继续,系统就会出现最糟糕的状态:用户以为任务已经停了,机器却还在改文件、占资源、写日志,甚至继续朝错误方向推进。

这也是为什么 OmniGoAI 的 GoWork 这类常驻助理,不能把超时简单理解成“到点就杀主进程”。真正可靠的做法是:上层声明超时,执行链路逐层感知,先尝试优雅停机,必要时再升级为强制终止。 只有这样,任务状态、资源释放和用户预期才会重新对齐。

如果你看过 为什么长任务要定期回报进度心跳为什么“下一步我会去做”不等于任务完成,这篇文章可以看作更底层的一环:为什么助理系统不能只会启动任务,还必须会把任务停干净。

为什么“表层超时”会制造假完成

超时停在助理层,最容易出现状态错位

很多系统表面上有 timeout,但它实际只约束了“当前这一轮等待多久”。一旦时间到了,界面显示超时、对话里回一句“任务已停止”,看上去像是结束了;可底下的命令、脚本、浏览器或桌面自动化还在继续。

这会带来三个直接问题:

  1. 用户状态错觉:用户以为任务没在跑,实际上副作用还在发生。
  2. 资源不释放:端口、文件句柄、浏览器窗口、桌面控制权可能继续被占用。
  3. 后续任务互相污染:下一轮任务启动时,会撞上上一轮残留的进程、日志或锁文件。

对一个只会回答问题的聊天机器人,这种问题还没那么致命;但对一个会读写文件、跑命令、调度子任务、操作桌面的助理,状态错位本身就是事故。

“已经超时”不等于“已经停下”

这是执行系统里最容易被忽视的一点。超时只是上层做出的一个判断,不是底层已经完成的事实。

例如一个构建命令超时,可能只是宿主不再等待 stdout;一个网页自动化步骤超时,可能只是当前调用栈返回了,但浏览器实例还在;一个后台任务超时,可能只是 supervisor 不再阻塞这轮对话,而不是子进程已经结束。

如果系统把“等待超时”和“执行停止”混成一回事,后面所有状态判断都会偏:你会误以为清理完成、资源已释放、结果不会再变化,实际上这些都还没发生。

为什么超时必须一路传到底层执行器

每一层都必须知道“剩余预算”是多少

可靠的超时控制,不是只在入口传一个固定数字,而是让执行链路上的每一层都知道:

  • 总预算是多少;
  • 现在已经用了多久;
  • 自己还剩多少可用时间;
  • 到时后该进入哪一种停机路径。

这就是“超时传播”的核心。不是 supervisor 自己记一个倒计时,而是 shell、脚本、子任务、网络请求、桌面控制都能感知同一个 deadline。

这样做有两个好处:

  1. 下层可以提前收尾。例如命令知道只剩 10 秒,就不再开启下一批下载,而是先 flush 日志、写检查点。
  2. 上层不用盲猜。不是超时后才去猜“它会不会自己停”,而是所有层都按同一个截止时间行动。

不传播超时,下层就会按自己的节奏继续跑

底层执行器天然只理解自己收到的控制信号。没有 deadline、取消信号或 stop token,它就会认为自己应该继续。

比如:

  • shell 进程会继续等待子进程退出;
  • 子进程会继续处理队列里的剩余项;
  • 桌面自动化会继续尝试点击或等待窗口;
  • 后台服务会继续监听端口,直到被显式关闭。

换句话说,如果超时没有被传播,下层不是“故意不配合”,而是压根不知道现在该停。 很多所谓“超时失效”,本质上不是 timer 坏了,而是 timer 只存在于最外面那层。

为什么优雅停机比“直接杀掉”更可靠

直接杀掉很快,但经常把系统留在脏状态

很多人遇到超时的第一反应是 kill。它当然快,但快不等于可靠。直接强杀通常会留下这些后果:

  • 临时文件还没清;
  • 日志最后几行没 flush;
  • 锁文件没释放;
  • 浏览器/驱动残留进程还在;
  • 任务历史里没有明确的“我是如何结束的”记录。

对一次性脚本来说,这可能只是“不够漂亮”;对常驻助理来说,这会直接影响下一轮执行。你今天强杀留下的脏状态,明天就会变成“为什么这个任务莫名其妙失败”的排查成本。

优雅停机的目标,是让副作用在可控边界内结束

优雅停机不是“慢一点退出”,而是按顺序完成下面几件事:

  1. 停止接收新工作:不要再启动新命令、新点击、新网络请求;
  2. 让当前最小原子步骤收尾:例如写完当前文件、结束当前 API 请求;
  3. 记录可恢复状态:写 checkpoint、run summary 或失败原因;
  4. 释放资源:关闭窗口、停止子进程、断开占用;
  5. 把最终状态回写给上层:让 supervisor 知道这是 cancelled、timedOut 还是 partially completed。

这样即使任务没做完,系统也能保持可恢复、可解释、可继续,而不是突然断电式消失。

正确姿势通常是“先优雅,后强制”

最稳的策略不是二选一,而是分级:

  1. 先发送取消信号;
  2. 给底层一个很短的 grace period 去收尾;
  3. 若仍未退出,再升级为 kill process tree;
  4. 强杀后明确记录“本次非优雅结束”。

这样既不把系统无限卡住,也不一上来就把现场打烂。对 GoWork 这类会持续执行很多轮任务的系统,这种分级终止比“超时即秒杀”稳得多。

在 AI 助理系统里,哪些地方最需要 timeout propagation

1. Shell 命令与子进程树

这是最常见的坑。很多宿主只停止等待顶层命令,却没有清理它拉起的整个进程树。结果就是父进程没了,子进程还在跑,端口还占着,日志还在写。

因此,超时控制至少要覆盖两件事:

  • 前台命令的等待超时
  • 后台和子进程树的真正终止

这也是为什么执行型助理不能只说“命令超时了”,还要继续验证:进程是否还在、端口是否还占用、窗口是否还存在。

2. 桌面自动化与浏览器控制

桌面动作不是纯计算任务,它会占用真实的鼠标、键盘、窗口焦点和用户屏幕。这里的超时如果不传播,风险比普通命令更高。

因为一旦上层超时了,但底下还在等窗口、找控件、尝试点击,用户就会看到“明明说停了,鼠标还在自己动”。这对信任是致命打击。

所以桌面执行器必须在收到取消后立刻停止排队新动作,并尽快释放独占资源。否则后续任务即使逻辑上无关,也会被上一轮残留动作拖住。

3. 长链路任务与多阶段 supervisor

多阶段任务最容易在 phase 边界上出现“半停半跑”。例如 supervisor 觉得这轮该收尾了,但下游的构建、发布、回查还各自悬着一个执行上下文。

这类场景最需要:

  • 统一 deadline;
  • 统一取消信号;
  • 每阶段都能写 checkpoint;
  • 下一轮只依赖已验证完成的产物。

如果没有这套机制,续跑时你根本不知道:哪些步骤是真的完成了,哪些只是“看起来像结束了”。

GoWork 这类常驻助理为什么更需要优雅停机

因为它不是一次性脚本,而是长期协作系统

一次性脚本跑完就丢,脏一点也许还能忍;常驻助理不行。它今天留下的残留,会直接进入明天的上下文。它不仅要完成任务,还要维护一个长期可执行的环境。

这也是为什么在 GoWork 这类系统里,超时设计和 为什么多步任务不能只在当轮写计划 是连在一起的:你要能继续,就先得停干净。否则所谓续跑,只是把未清理的旧状态继续往后拖。

因为用户在意的不只是“停没停”,还有“系统现在是不是可信”

用户并不会用“timeout propagation”这个术语描述问题。他们真正感受到的是:

  • 我都说停了,怎么还在动?
  • 这个任务到底结束了没?
  • 下一轮会不会被上一轮污染?
  • 你现在说的状态,我还能信吗?

所以超时传播和优雅停机,本质上不是底层工程洁癖,而是用户信任问题。一个停不干净的助理,即使能力再强,也会越来越难让人放心托付任务。

实践上应该怎么设计

如果你在做执行型 AI 助理,至少要满足下面这份 checklist:

  1. 入口层定义总 deadline,而不是只有一个局部 timeout 数字;
  2. 每一层执行器都接收取消信号或剩余时间预算;
  3. 到时先停接新工作,再做短暂优雅收尾;
  4. 必要时升级为强制终止整个进程树,而不是只停父进程;
  5. 把终止方式写入运行记录:正常完成、用户取消、超时优雅结束、超时强制结束;
  6. 续跑时只依赖已验证的产物,不依赖“可能已经停掉”的假设。

这几条看起来偏底层,但它们直接决定了你的助理是“能跑”,还是“能长期稳定地跑”。

你可以在 GoWork 下载页 继续了解这种面向执行的助理设计;如果你还想把“停得干净”和“状态讲得清楚”连起来看,也建议接着读 为什么任务状态不能只看会话摘要为什么失败后别重来,要从上次进度继续

常见问题

FAQ 1:给每个命令单独设 timeout,不就够了吗?

不够。单个命令 timeout 只能约束那一个调用点,约束不了它拉起的子进程、旁路线程、后台服务和后续阶段。真正可靠的是把 deadline 与取消信号贯穿整条执行链,而不是在几个局部位置各自设表。

FAQ 2:优雅停机会不会拖慢系统?

会增加一个很短的收尾窗口,但通常远小于事后清理残留的成本。对常驻助理来说,几十秒的优雅收尾,往往比下一轮排查半小时残留问题便宜得多。

FAQ 3:什么时候该直接 kill?

当取消信号已经发出、grace period 已经过了、底层仍不退出,或者继续运行会造成更大副作用时,就应该升级为强制终止。关键不是“永不 kill”,而是不要把 kill 当成唯一策略。

FAQ 4:这和用户看到的任务状态有什么关系?

关系很大。只有底层真正停下并把结束方式回写,上层才能诚实地说“已取消”“已超时结束”或“已部分完成”。否则任务状态只是表面文案,不是可验证事实。

#GoWork#AI 助理#超时控制#优雅停机

更多文章

13 分钟

为什么任务状态不能只看会话摘要

任务状态查询不能只盯着 conversation summary。真正可靠的进度判断,要同时看 runtime 状态、recent events、等待原因和最近执行证据。本文解释这几个层次分别回答什么问题。

阅读