从串行到异步:慢操作填坑指南
🎯 学习目标
- 后台化改变的是「谁等待结果」还是「谁拥有权限」?
- run_in_background 三态(true/false/null)分别什么决策?
- 为什么后台提交必须在权限之后,不能先创建 worker 再补权限?
- EventInbox 传递什么?能接收普通字典或伪 tool result 吗?
- 后台只后台化了哪个工具?Cron/teammate/后台subagent 在范围内吗?
- 工具执行顺序在后台下变了几条?
🧠 核心概念
1 · 后台改变「谁等待」,不改变「谁拥有权限」 ▸
第12章 Task DAG 描述「要做什么」,不替你执行慢命令。第13章增加生命周期:工具调用通过参数校验、Hook、权限和审批后,先返回后台占位,完成后通过 typed runtime event 回到同一 Loop。
2 · 显式参数是三态 ▸
只有 P13 主 Agent 的 shell schema 增加 run_in_background;P01-P12 和一次性 subagent 仍只看到 command。
| 输入 | 决策 |
|---|---|
| true | 强制后台 |
| false | 强制同步,即使命中慢命令关键词 |
| null 或省略 | 才使用启发式 |
启发式只判断慢命令关键词(cargo build/npm install/pip install/pytest 等),不预测任意命令耗时。concurrency:"background_eligible" 是第二道门,没该标记的工具永远直接执行。
3 · 为什么提交必须在权限之后 ▸
AgentRunner 唯一工具执行点:先解析 JSON+Zod,再 Pre Hook+PermissionPolicy,最后才调可注入的 ToolDispatcher。Dispatcher 拿到的是 Hook 改写后、权限审查过的 PreparedToolCall。
4 · 五个角色的责任边界 ▸
| 角色 | 责任 | 不负责 |
|---|---|---|
| BackgroundDispatcher | 判断同步/后台,提交已获批调用 | 不做权限判断,不重放原始JSON |
| JobSupervisor | 持有worker,控制容量/超时/取消/关闭/终态 | 不充当Task DAG |
| query/cancel_background_job | 按job_id查询/取消持久化后台job | 不暴露给subagent与P01-P12 |
| EventInbox | FIFO传递typed RuntimeEvent | 不接收普通字典或伪tool result |
| JsonBackgroundJobStore | workspace内原子保存job状态 | 不盲目重放崩溃前未知副作用 |
三个关键细节:①终态由 finishRunning 条件迁移仲裁——谁先写进磁盘谁定终态,晚到一方拿到 undefined 安静跳过发事件,「只发一次」靠磁盘状态不靠内存标记;②刻意不做进度事件:3 分钟每秒报一次=180 条 user 消息吃光上下文,模型想知道进度就调 query_background_job 查一次——拉取优于推送;③崩溃恢复把遗留 running 迁移为 interrupted(error_code=background_interrupted)且幂等:连续崩 5 次,模型也只收到一份中断通知。
5 · 范围:只后台化 PowerShell ▸
本章只后台化 P13 主 Agent 的 PowerShell 工具。Cron、持续 teammate、后台 subagent、输出流文件、交互式提示看门狗不在范围内(后续章节)。
Task、TODO、Hook、权限、恢复、记忆、动态 Prompt 和上下文压缩全部保留,不复制第二套 Loop。同样刻意不做的:排队(容量满直接报 background_capacity,否则「提交成功」变成谎言)、抢占式取消(JS 杀不死 Promise,协作式发信号+等+超时报错)、自动重试。P13 的后台任务本质是 Promise 不是子进程——架构可平移,换的只是 BackgroundOperation 内部实现。