后台任务
后台任务由 Agent 运行框架在分离的 shell 中执行。调用先返回句柄,进程继续运行,Agent 可以处理其他工作。完成通知在下一个轮次边界进入上下文,不会插入正在生成的回复。
本课的串行 Agent 在普通调用中发出命令,等待结果,再读取结果。若测试需要 40 秒,等待期间就无法推进其他工作。把命令放到后台不会缩短命令本身的耗时,却允许这 40 秒与其他工作重叠。能并行处理的工作必须不依赖该命令的结果。
后台任务如何工作
启动返回句柄。 Bash(..., run_in_background=True) 启动 shell,约一秒后返回 ID,本课中是 bash_1。Agent 随后可以继续下一轮。后台命令的当前状态不会自动出现在这一轮的上下文中。
输出进入缓冲区。 分离进程的标准输出会累积。调用 BashOutput(bash_1) 时,返回上次读取后新增的内容。因此测试失败信息不会因无人查看而丢失。但若命令尚未输出,提前读取会得到空缓冲区,不会阻塞到有输出为止。
完成通知在轮次边界送达。 进程退出后,运行框架生成通知。本课把模型看作根据一份对话生成回复的过程,生成中途不能向它正在使用的上下文追加消息。通知要等当前轮结束、下一轮组装上下文时送达。Agent 若已空闲,可以在进程退出时被唤醒;若正在处理一轮,它可能晚几秒才知道结果已经完成。
阻塞调用占用命令的运行时间。后台调用增加启动和读取结果的开销,换取中间可完成的独立工作。是否划算,要比较两者。
逐步演示
默认任务包括:运行 pytest -q,耗时 40 秒;完成三项类似 CHANGELOG 更新的编辑,这些文件不会被测试读取;最后报告。画面固定使用以下耗时:模型一轮 8 秒、编辑 5 秒、启动 1 秒、读取 1 秒。
- t = 0–8 秒:计划。 模型看到 40 秒的命令和三项不依赖测试的编辑,决定使用后台任务。
- t = 8–9 秒:启动。
run_in_background返回bash_1。shell 从第 9 秒运行到第 49 秒。 - t = 9–22 秒:第一项编辑。 新的一轮耗时 8 秒,修改
CHANGELOG.md耗时 5 秒。Agent 与 shell 两条时间线同时前进,工作重叠产生节省。 - t = 22–35、35–48 秒:另外两项编辑。 两次重复同样的过程。到第 48 秒只剩最终报告,测试还差一秒。
- t = 49 秒:进程退出。
bash_1以退出码 0 结束,缓冲区有 42 行输出。Agent 正在执行第 48 秒开始的一轮,此时已过 1 秒,不能在生成中途接收通知。shell 上方出现通知卡,右侧虚线表示进程已结束但 Agent 尚未知情的时间。 - t = 56 秒:通知送达。 当前轮结束,下一轮组装上下文,通知进入其中。进程已结束 7 秒。这段间隔来自轮次边界,而不是队列处理缓慢。
- t = 56–65 秒:读取。 一轮耗时 8 秒,
BashOutput(bash_1)再耗时 1 秒,把 42 行输出放入对话。 - t = 65–73 秒:回答。 总耗时 73 秒。
只把派发方式改为阻塞调用,其他条件不变:40 秒测试在第一轮内执行,Agent 等到第 48 秒,再分别用一轮完成三项编辑。总耗时 95 秒。画面的对照线标出它的位置,后台运行提前 22 秒结束。
后台执行能节省多少时间
节省时间最多是 min(command runtime, independent work available) − overhead:可重叠的时间受命令耗时和独立工作量中较小的一项限制,还要扣除开销。退出恰好错过轮次边界时,读取要再等一轮,所以默认运行只省 22 秒,不是 29 秒。
独立工作量决定上限。 启动后有三轮加三次编辑,共 39 秒独立工作。测试延长到 50 秒,节省达到 29 秒,即 39 秒减去 10 秒开销,之后不再增加;延长到 120 秒,仍只节省 29 秒。编辑做完后,Agent 只能等待。灰色区段和“Agent 空闲”计数显示这段时间。两小时的后台命令不会自动换来两小时收益,收益受可并行工作量限制。
开销决定下限。 启动耗时 1 秒,读取需要一轮加一次工具调用,共 8 + 1 秒,总开销为 10 秒。命令短于 10 秒时,重叠收益不足以抵消这些开销。恰好 10 秒时两种方式持平;5 秒命令使用后台执行反而多花 5 秒。
送达时点影响总耗时。 命令从 35 秒变为 40 秒,节省反而从 25 秒降到 22 秒。退出时点晚了轮次边界一秒,使读取推迟了一轮。除了这一处回落,节省随命令时长一秒对一秒地增加,编辑用完后从 50 秒起保持不变。
边界情况
- 后续工作依赖输出。 若三项编辑要修复测试指出的问题,就没有独立工作可重叠。Agent 启动后台命令后发现被依赖阻塞,花一轮提前读取空缓冲区,再等待退出。同样的工作,后台方式耗时 105 秒,阻塞方式为 95 秒。这里多出的 10 秒是启动加上那一轮无效读取;10 秒的命令会多花 17 秒,因为它的退出比读取晚一秒,读取要等到下一个轮次边界。
- 退出时 Agent 已空闲。 运行框架可以立即唤醒它,没有通知滞后的间隔。这是长命令的情形:Agent 先做完了工作,节省已经到达上限,多出的运行时间都在睡眠中度过。
- 尚无输出就读取。
BashOutput不阻塞,返回空内容和 shell 仍在运行的状态。获得这个状态仍要付出一轮的成本。 - 进程不退出。 服务器、监视器或挂起的命令不会发送完成通知。若要了解状态,只能主动读取或轮询,每次都占用一轮。启动长期进程时,应明确它的生命周期。
- 多个 shell。 每个后台命令有各自的时间线和通知,通知在相同的轮次边界排队送达。本课 Agent 仍串行推进决策;更多 shell 的收益取决于是否有更多独立工作。
常见错误
- 把异步当成命令更快。 后台执行只改变等待方式,不改变命令耗时。没有其他可推进的工作,总时间可能不变或更长。
- 把空输出当成成功。 尚未结束的进程可能还没打印内容。空缓冲区不能证明没有问题,应检查运行状态和退出结果。
- 反复轮询。 每次轮询都付出一轮模型延迟,只获得少量状态。两次轮询的成本可能超过许多短命令本身;可以等待完成通知。
- 期待通知打断生成。 本课机制只在轮次边界响应,无法保证在命令退出的瞬间采取行动。
- 漏算读取轮次。 通知说明进程结束,不包含完整输出。决定是否放到后台时,要把读取成本算进去。
本课的简化
这是机制演示,不是某个具体运行框架的调度器模型。耗时是为展示而设的常量;实际模型一轮可能不足一秒,也可能超过几十秒,工具成本也各不相同。输出可能流式产生,而非一次出现 42 行。本例只使用一个后台 shell、一个 Agent,没有交错的用户轮次;真实会话中的用户轮次也可能成为通知送达的边界。
本课保留的机制是:分离调用先返回句柄,输出缓冲到读取时,完成消息在轮次边界进入上下文,节省受独立工作量限制。
Agent 循环介绍这里的轮次结构,任务系统说明工作排序和崩溃后的恢复,子 Agent按上下文拆分工作,后台任务则按时间安排重叠。进程调度在更底层展示相似问题:单个 CPU 遇到阻塞时,只有存在其他就绪工作,切换才有收益。