第十五节 子任务与后台执行

复杂任务往往需要把工作拆给"子助手"。AVL Code 内置子任务调度能力,支持前台同步与后台并发两种模式。

15.1 派发子任务

助手在对话中可以调用一个「子助手」工具,把一段提示词派给独立的子任务执行,子任务完成后把结果合并回主对话流。这一过程对你是透明的,但你可以在工具调用卡片里看到每个子任务的详情。

15.1.1 子 Agent 继承外部 MCP 工具

派出去的子 Agent 自动继承你在工作区配好的外部 MCP 工具(之前子任务用不到这些外接工具,得回到主会话才行):

  • 机制:派发时经 /admin/routes + /admin/backends 解析本工作区所有外部后端的工具前缀(如 notion / memory / channels 等命名空间),通过环境变量 ZAGENT_MCP_INHERIT_PREFIXES 注入;子 Agent 的 ZAGENT_ALLOWED_TOOLS 白名单无条件穿透这些前缀。
  • 覆盖范围:所有声明了 tools: 的子代理(plan / assess / prepare / mcp-admin 与用户自建);所有工作模式统一(含 plan)。
  • fail-safe:解析失败 → 返空 → 不继承,不影响子任务正常派发。

15.1.2 子任务模型 / 服务提供方匹配修复

之前在多服务提供方配置下,派出去的子任务沿用父会话的模型,但服务提供方恒取列表里的第一个,两者对不上时会报「模型不存在」起不来。本版改为服务提供方跟着继承的模型走 — 优先选真正包含该模型的那个服务提供方,子任务能稳定启动。

15.1.3 并行 fan-out:一次拆多路同时跑

除了一次派一个子任务,AI 还能用 AgentParallel 工具把一个任务一次拆成 2–16 条「腿」同时跑,每条腿是独立的 fresh-context 子 Agent、互不可见,全部完成后自动汇总结果返回。适合需要多路并发的活(如同时审好几个模块、并行检索多个来源),比一个个排队串行快很多。

  • 每条腿各自带提示词、可单独指定子代理类型与模型;提示词需自包含(腿看不到主对话历史)。
  • 并发上限 16 条(远低于后台任务总并发 64,单次 fan-out 不会霸占全部槽位);不足 2 条请改用单个 Agent。
  • 整体等待超时默认 600 秒、上限 1800 秒;到时未完成的腿返回 pending 并在后台继续,可用 TaskWait / TaskOutput 续跟。
  • 单条腿失败 / 被拦不会拖垮整批,失败原因会一并随结果返回让 AI 自行处置。每条腿输出过长会截断(提示用 TaskOutput 取全文)。
  • 执行期就能看到实时状态:并行子任务的调用卡片在执行期间即出现(不必等终态才冒出来),并实时反映各子任务的运行状态;停止后不再持续转圈,极简密度下结果块也不会被误折叠。

15.2 前台 vs 后台

  • 前台:主助手等待子任务完成(默认 10 分钟超时),适合"必须等结果"的任务。
  • 后台:子任务异步运行,主助手立刻得到一个任务编号,后续可主动拉取状态或停止该任务。后台任务有总数上限(防失控)和自动回收(终态超时清理)。

15.2.1 「空闲制」看门狗 + 绝对兜底

子任务超时改成「按空闲计时」:只要还在持续产出就不算超时,看门狗只针对真的卡住、长时间无任何输出的情形。再叠加一道绝对兜底(默认 60 分钟),防止极端死循环 / hang 把任务永远留在跑的状态。

  • 空闲窗口:默认 15 分钟无输出 即超时(错误信息会明确写「子 Agent 空闲超时」)。
  • 绝对上限:默认 60 分钟 强制结束(写「绝对兜底」)。
  • 正常长任务不受影响:只要还在 stream 产出(包括思考段、增量字符)就一直续命。
  • RunAgentToolBackground 后台路径不计执行超时(你显式把它放后台跑就接受它跑久)。

15.3 并发上限

为防止一次发起过多后台任务造成失控,AVL Code 会限制同时在跑的子任务数;超出后助手会被告知"先稍等再发起",避免雪崩。

15.3.1 阻塞等待 — TaskWait 工具

主助手可以「一次调用、阻塞等待」后台任务 / 子代理跑完,不用再一轮一轮反复查询进度:

  • 支持等「全部完成」或「任一完成」。
  • 可设超时(默认 5 分钟、最长 30 分钟);到点会如实返回「还在跑」让模型决定是否继续等。
  • 等待期间只回状态与最终结果,不把中间过程灌回主对话,省上下文也更省 token。

等待期间你能看见什么:

  • 等待中的 TaskWait 调用行显示一条总体倒计时,按这次调用的超时预算往下走,"还要等多久"心里有数;没有后台任务在跑时不启动倒计时(不会空转),倒数到 0 也不会停在 0ms 干耗,而是回落成灰色的静态预算值。
  • 等完之后条头图标换成打钩的时钟,一眼分清"还在等"和"等完了"。
  • 调用行支持紧凑模式,配合子任务状态马赛克(一格一个子任务、按状态着色),一眼看出这批子任务跑到哪了。
  • 耗时显示不再逐秒跳动导致整条行左右抖动(时长格式已收敛,分钟档秒数补零)。

结果里的两件事:

  • other_tasks:结果里会带上同会话里没被这次等待覆盖的其它后台任务,避免"等了一批、忘了另一批"。工作区范围内的全景同样可见。
  • 终态可见「给主代理的最终回复」:被等待的任务跑完后,它给主代理的那段最终回复在界面里直接可读,不用去翻 JSONL。
  • 被 TaskStop 主动终止的任务不再显示成"✓ 成功完成";被停止的任务、以及记录已被清理的任务都如实呈现,不会再误报"未命中,可能已被清理"或凭 JSONL 兜底谎报"已完成"。
  • 运行中的任务在 TaskWait / TaskOutput 的结果里会带上已运行时长;任务被终止时当场结算耗时,不会记成 0。

15.4 终止

  • 在桌面端,点击输入框右侧 停止 按钮会同步终止主助手与所有派生子任务。
  • 在微信侧,发送 /stop 同样会触发级联终止。
  • 子任务也能单独停止:子代理抽屉里每个正在跑的子任务都有独立的停止按钮,不必整轮一起停 —— 后台任务、并行 fan-out 的腿、以及同步内联的子代理现在都支持单停。停止时可选择保留已产生的子任务记录。
  • 只有你点停止才会级联:重新发消息、自动重启对话等内部动作不再顺手把已派出去的子代理一起杀掉。

15.4.1 真实总消耗看得清(含子代理)

用量统计会把子代理(并行 / 子任务)的真实消耗也算进来:目标条(GoalStrip)与每轮结束的标识都显示真实总 token 消耗 + 工具调用次数,有子代理时还会拆出「自身 + 子代理」的构成。该统计按会话持久化,长会话看到的是全程真实总量(即使没设目标也照常累计)。

15.4.2 子任务抽屉:看得见,也管得住

点子任务调用卡片上的「在右侧打开实时流」,右侧抽屉会展开这个子代理的完整运行记录 —— 它不只是"只读日志",你可以直接在里面接管这次运行。

抽屉里看到什么:

  • 任务 prompt(含父会话给的「任务输入」与「系统指令」)、逐条记录、记录条数。
  • 状态:运行中 / 已暂停 / 完成 / 已结束 / 失败 / 已终止 / 被拒绝。
  • 跑完后有「给主代理的最终回复」区块,直接读结论。
  • 顶部标签栏在多个子任务同时跑时可切换;装不下的会提示「还有 N 个运行中的子任务未显示」。
  • 「复制 JSONL 路径」「复制 TaskWait 参数」两个按钮,便于自己接着用工具跟踪(TaskOutput / TaskStop 用同一个 id)。

「已结束」和「完成」不是一回事:导入的会话、或应用重载后回灌的记录里,如果一个子代理没有留下明确的终态,现在显示为「已结束」而不是笼统的「完成」—— 此前它会谎报"已完成",让人以为活干完了。真正被停掉 / 失败的任务会按盘上记录如实回灌为「已终止」/「失败」。

运行控制(抽屉底部):

动作 说明
停止 终止这个子代理;可选择保留已产生的子任务记录
暂停 / 继续 暂停后可随时「继续」,可在「补充指令」里补一句再接着跑
重试 截断到最后一条用户消息后重新运行
再跑一次 用相同的类型 + prompt 重新发起一次(prompt 缺失时该按钮禁用并说明原因)

按消息粒度接管:抽屉里的每条消息左侧有消息柄(与主会话同一套约定),点开菜单可以:

  • 编辑消息 / 删除消息(运行中拒绝编辑,避免改到一半被覆盖)
  • 删除某个工具调用(连带删掉配对的工具结果;导入的旧记录没有 id 时会说明无法单独删除)
  • 从此处重试 —— 此消息起的内容重新生成(用户消息保留提问重答,助手 / 工具消息重做该步)
  • 从此处继续 —— 保留到这条为止,其后内容丢弃,回到该时间点继续运行,下方「补充指令」的内容会一并带入
  • 复制 / 复制选中的部分
  • 菜单头部显示这条消息的时间(绝对 + 相对两行)

危险动作都带行内二次确认(「确认删除?」「确认重试?」「确认继续?」)。

编辑器挂在侧边,不挤消息流:点「编辑消息」弹出的是挂在列表外侧的侧挂编辑器(有拉线指回被编辑的那条),保存走增量更新 —— 不会因为改一条消息就让整条消息流跳位。被编辑的消息滚出视野时编辑器会提示并给「回到消息」;有未保存修改时关闭会问你「放弃修改 / 继续编辑」。

导航与右键:消息柄旁有「跳转到顶部 / 底部」快捷按钮;「滚动到底部」会一路盯到真正见底(运行中持续钉底,不会滚一半停下)。消息区支持右键菜单,与消息柄菜单同一套动作。

15.4.3 子任务状态条:一眼看全在跑的活

会话状态条里有一项「子任务」(形态见 §5.5.1),汇总当前运行中与已暂停的子任务:

  • 条上直接显示「N 运行中 / N 已暂停」。
  • 点开逐条列出,点某条即在右侧抽屉中打开(暂停 / 继续 / 重试等操作都在抽屉里做)。
  • 条目过多时提示「+N 条未展示」,完整清单去左侧「子会话」面板看。
  • 左侧「本会话的子会话」列表与「全部子会话历史」按最后活跃时间分组折叠,默认只展开最近一批,历史再多也不会刷屏。

子代理不会被"顺手"杀掉:重新发消息、自动重启对话等动作不再级联终止已派出去的子代理 —— 只有你显式点停止才会终止它们及其进程。

15.5 输出聚合

后台任务的中间日志可作为独立卡片在主对话流里展开查看;任务完成后会以一段精简摘要返回,避免淹没主线索。

15.5.1 AI 建议任务:一键拆分去做

AI 干活时如果顺手发现了分外但值得做的事(死代码、过期文档、确认存在的 TODO、安全隐患等),会在消息流里挂出一张「建议任务」卡片,而不是打断当前正事。

卡片上的执行按钮是个 split button:

  • 主按钮一键:默认「开启新会话执行」。
  • 展开 ▾ 还有 4 种方式:
    • 在新会话中执行(主按钮默认)
    • 在当前会话中执行
    • 复制到输入框
    • 复制到剪贴板

由你来决定怎么处置。已执行的建议会标记状态,AI 也可以撤回已经过时的建议。多条建议会聚合到输入框上方的一个条里统一管理。

15.6 例行程序:让 AI 按计划自动干活

「例行程序(Routine)」让你给某个工作区定一条指令,让 AI 按时自动或按需手动执行 — 比如每天早上拉取并总结昨日 issue。

新建入口:

  • 工作区侧栏「新建例行程序」一键打开对话框(也支持从 Header 当前工作区进)。
  • 表单字段:名称、指令内容、执行模式、模型(与正常对话同款选择器:模式色卡说明、模型搜索分组)、计划时刻。
  • 写到一半自动暂存 — 中途跳去设置或 Agent 管理器调配置,回来自动重开并恢复已填内容。

计划方式:

类型 行为
每小时一次(hourly) + 指定分钟 在每小时该分钟点触发一轮;复用 HH:MM 字段但只看分钟,小时锁 00、触发器显示 :MM
每天 / 工作日 / 每周某几天 + 指定时刻 到点自动触发一轮
不设计划(手动) 不自动跑;保存后立即触发一轮,按钮显示「运行一次」;之后需要时再点立即运行

时刻选择顺手:

  • 时 / 分双列下拉(选中居中、选完分钟自动收起);每小时一次模式下只露分钟列
  • 周几用圆形单字字块,附「工作日 / 周末」一键预设
  • 调度精度:30 秒 tick;hourly 跨日 / DST 用时间算术天然正确

统一管理:

设置 → 例行程序 Tab 按来源工作区分组:

  • 启停开关(与其它设置页同款)
  • 立即运行
  • 查看最近运行痕迹
  • 未打开的工作区有徽标提示并可一键打开
  • 侧栏图标直达本工作区的例行程序列表
  • 支持按名称 / 工作区搜索

运行行为:

  • 每次运行会在对应工作区生成正常会话,过程与结果都可回看。
  • 同一条例行程序运行中会自动跳过下一次触发,不会叠跑。

15.6.1 会话内例行程序:让 AI 在当前会话里定时提醒自己

除了上面这种"每次开新会话独立跑"的工作区例行程序,Agent 还可以在当前会话里挂定时提示 —— 比如「每隔 10 分钟看一眼构建是否跑完」「30 分钟后检查一下下载结果」。到点后,提示会送进设置它的那个会话:会话正在运行就直接插入当前循环,空闲时按设定起一轮或跳过。两套例行程序数据各自独立,设置 → 例行程序 里只显示工作区例行程序。

怎么建:直接跟 AI 说你要什么节奏,由它调用 RoutineCreate 设定(界面上没有新建按钮);目标达成或不再需要时它会调用 RoutineCancel 自行取消,不必等跑满次数。仅主会话可用(auto / execute 模式可新建;plan / prepare / assess 模式只能查看与取消),子代理与终端界面(TUI)不可用。

计划方式(均按本机本地时间):

类型 含义 约束
N 分钟后 一次 1–1440 分钟
每隔 N 分钟 重复 1–720 分钟;默认有效期 8–24 小时、默认 30 次
定点 指定时间一次 须在将来且最远 30 天
每小时 每小时某分钟 默认有效期 72 小时
每日 / 工作日 / 每周 到点触发 每周至少选一天;默认有效期 7 天(最长 30 天)

到点时怎么送:会话在跑时,可选「优先」(当前步骤结束后插入,默认)或「立刻处理」(截停正在生成的回复后插入),语义与即时指导相同(见 §5.4.2);会话空闲时按「会话空闲时」设定「起一轮」或「跳过」。

输入框上方的「例行」条(形态见 §5.5.1)列出本会话的例行程序,每条显示名称、计划、「下次 HH:MM · 已送达 n/m」与空闲时的处理方式:

  • 「暂停 / 恢复」「编辑」(就地改名称、提示内容、计划、空闲时与在跑时的处理、次数上限、有效期)「删除」。
  • 下方「已结束」区列出结束原因(已完成、已达次数上限、已到期、模型已取消、你点了停止、错过触发时间等),点「知道了」收起;结束时也会弹提示。
  • 每次送达在消息流里显示为紧凑的一行「⏰ 例行 · 名称 · 3/30」,点开可看提示全文;它不是你的新消息,也不代表新的授权,超出你原本请求的操作 AI 会先向你确认。

边界与安全阀:

  • 点停止按钮会一并取消本会话的全部例行程序(随行通讯里的 /stop 同效);删除会话会连同例行程序一起清掉,归档会话则将其结束。
  • 每个会话最多 5 个、每个工作区最多 50 个未结束的例行程序;每会话 24 小时内最多新建 10 个;每个工作区每天最多送达 300 次。
  • 例行程序发起的回合连续失败 3 次自动暂停(点「恢复」清零);会话在跑却插不进去时会重试一段时间,仍不行则跳过这一次。
  • 重启后保留(存在本机配置目录 workspaces/<工作区 ID>/session-routines.yaml);只在工作区处于打开状态时调度,错过的时间点不补发。
  • 由例行程序起的一轮,回复会转发给绑定了该会话的随行通讯对端(抬头「⏰ 例行「名称」」)。

15.7 长程任务崩溃后可恢复

放到后台跑的长任务(即以 run_in_background 派发的子任务),其进度会实时落盘保存;应用意外退出 / 被杀 / 崩溃 / 升级打断后重新打开,能把没正常结束的任务找回来接着干,不必从头再来。

它怎么工作:

  • 每个未结束的后台任务都以一份快照写到本机(跨工作区的全局目录);任务正常走到 done / error / killed 终态时会删除该快照。所以重开后还留在盘上的,就是上次被中断、没跑完的任务。
  • 启动时会异步扫描这些残留任务,先做一次「脏条目清理」——父会话已不存在、再也无法恢复的条目自动删掉;扫描不阻塞启动,没有残留就静默无感。
  • 恢复并不是直接重启子进程,而是往原来的父会话里注入一句「之前委派的后台任务尚未完成,请继续或重新委派」,让主助手以「先派发」的方式接着完成——这一步对你可见,你能看到「正在恢复任务 X」。

你会看到什么(「中断任务」状态条):

  • 有被中断的任务时,会话状态条里出现一项「中断任务 · N 待恢复」(窄屏在输入框上方、宽屏在消息区右上,形态见 §5.5.1);没有就不显示。它不占用页面空间,任务再多也不会把消息区往下顶。
  • 点开后浮窗按条列出,标题形如「N 个后台任务上次被中断」;每条任务旁有两个按钮:恢复(,注入续跑消息并从盘上移除该条)/ 忽略(从盘上删除,下次不再提示)。标题右侧还有「全部忽略」。全部处理完后该项自动消失。
  • 恢复成功后弹 toast「已恢复:将在原会话继续该任务」;失败则提示具体原因(如「原会话已不存在,已清除该恢复项」)。

自动恢复(可选,默认关):

  • 在 设置 → 智能体 → 自省 打开「自动恢复被中断的任务」开关,重启后会自动接着跑上次崩溃 / 被杀时未完成的后台任务,适合夜间无人值守的长任务。
  • 为防「续跑消息风暴」,启动自动恢复每次最多 5 个,其余仍留在「中断任务」状态条供手动恢复。
  • 关闭该开关时(默认),所有被中断的任务都只在「中断任务」状态条里列出,由你手动决定恢复或忽略。