异步 Subagent:任务交出去以后,主 Agent 怎么接着做?
读者收获:
- 分清一次任务里哪些环节提前定好、哪些由模型临场决定,以及什么任务会交给 Subagent。
- 看懂 goal 和 brief 各自记录什么,子 Agent 实际能拿到哪些信息。
- 串起结果回收的过程:监听回调、排队、比对版本、合并,以及不符合预期时怎样打回。
全文约 2600 字 · 预计阅读 6 分钟
核心论点是:
异步 Subagent 把任务整理成 brief 交给子 Agent,主 Agent 派完就可以结束这一轮;子任务完成后,系统新开一轮,主 Agent 对照 goal 检查结果,合并之后再做后处理。
Agent-as-Tool 中,主 Agent 把任务交出去,等结果回来再继续。这一篇讨论异步协作。如果主 Agent 不等了,结果回来的时候,由谁接住,又由谁接着处理?
一、先从问题开始
1. 用户提了一个需求,要不要交给 Subagent,是流程里提前写死的,还是主 Agent 临场决定的?
两部分都有:流程的骨架是固定的,骨架里面的执行是动态的。
用户下达任务后,我们先做意图识别,把自然语言转成后续路由可以使用的 schema,再根据问题选派对应的 Agent 去处理。这几步的顺序是提前定好的。
进入某个 Agent 之后,就没有固定路线了。它要用到哪些工具,要不要调用 Agent-as-Tool,要不要 Handoff,都是具体问题具体分析。
要不要交给 Subagent,属于动态的这一部分,由主 Agent 判断:除了特定的几类任务会用其他方式处理,剩下的都交给 Subagent。子 Agent 拿到 brief 就开始执行,执行过程同样是不确定的。
2. 任务交给 Subagent 之后,主 Agent 这一轮已经结束了,它怎么知道任务做到哪一步,结果回来又由谁处理?
主流程通过一份 goal 文档记录当前会话有哪些预期目标在进行中,subagent 任务完成后,主 agent 需要对照 goal 进行验证处理。
主 Agent 确认要用 Subagent 之后,调用工具,生成 brief,启动子 Agent。任务拆成几块,就在主流程里开几个子 Agent。等 goal 里每一个模块都有了负责执行的 Agent,确认没有待执行的内容,主 Agent 就结束这一轮,输出内容。
接下来就是等结果。子 Agent 完成任务后,主流程监听到它的状态变化,创建一个新的 run 来处理结果:对照 goal 验证,把修改合入主流程,再做后处理。如果结果和预期不符,就打回给子 Agent 重新处理。
二、什么任务会交给 Subagent
这里的“纯任务”,指一块工作只靠明确的输入和必要资料,就能独立处理。
落到实际项目里,我们发现任务并不是天生就纯。主 Agent 会参考自己的上下文,把每一个 goal 模块转成纯任务所需的 brief,让子 Agent 能理解完整的任务,以及对应的背景、目的和最终的验收方案。
所以能不能交出去,要看主 Agent 能不能把这件事讲完整。讲完整了,子 Agent 不需要再回头翻主会话,也能把任务做完。
三、goal:这一轮要做的事记在哪里
goal 是一份 Markdown 文档,记录本轮对话经过意图识别之后需要完成的任务。内容由模型识别出来,作为参数交给工具,再由工具写入文件。
比如用户在对话里说:“帮我把最近对话里提到的待办整理成一份清单。”主 Agent 识别之后,会写出下面这样一个模块。这是一个示例,字段和结构与真实项目一致,内容是为了说明而写的:
## @user_001 todo-summary
goal_id: goal-a1b2c3
revision: 2
scope: 最近两周的对话记录
intent: 从最近两周的对话里找出提到的待办事项,整理成一份清单
done: 清单按项目分组,每条待办注明出自哪次对话,没有遗漏和重复
additional context: 只读取对话记录,不修改任何内容;已经完成的待办单独列出
第一行是模块标题,写的是所属用户和模块短名。后面六个字段各有用处:
- goal_id:模块的 id。
- revision:版本,同一个意图再次修改时变更。
- scope:任务范围,这个模块覆盖哪一块。
- intent:任务目标,也就是本轮识别出的意图。
- done:完成标准,做到什么程度算完成。
- additional context:补充信息,比如需要保持不变的内容。
最重要的是 id。子 Agent 返回结果时,主 Agent 靠它找到对应的模块。
版本用来处理“中途改主意”。上面这个模块的 revision 是 2,因为范围中途改过:我们先说整理上周的对话,子 Agent 已经启动执行;随后又说范围要改成最近两周。这时“整理待办”这个模块的版本就发生了变更。
正在执行的子 Agent 不知道 goal 已经变了。这类情况都放在主 Agent 这一侧处理:等结果回来,合并的时候由主 Agent 判断。
四、brief:交出去的是什么
4.1 子 Agent 收到什么
brief 是把 goal 里的单个模块转成一份任务说明。接着上一节的例子,子 Agent 收到的 brief 是下面这样的。这同样是一个示例,字段和结构与真实项目一致:
{
"assignment": {
"title": "整理最近两周的待办清单",
"task": "从最近两周的对话记录里找出提到的待办事项,整理成一份清单",
"constraints": [
"只读取对话记录,不修改任何内容",
"已经完成的待办单独列出"
],
"acceptance_criteria": [
"清单按项目分组",
"每条待办注明出自哪次对话",
"没有遗漏和重复"
]
}
}
四个字段都能在 goal 里找到来源:
- title:这份任务的简短名称。
- task:具体要做什么,对应 goal 里的 intent。
- constraints:必须遵守的约束,对应 additional context。
- acceptance_criteria:验收条件,对应 done。
brief 作为 user message 进入子 Agent。主会话的信息不会进入子 Agent。
4.2 程序另外记了什么
派工的同时,程序还会留一份记录,写明是谁派的、派给了谁、对应哪个 goal 模块的哪个版本:
{
"agent_id": "sub-7f3e",
"child_session_id": "sub-7f3e",
"run_id": "run-sub-01",
"parent_session_id": "session-main-01",
"parent_run_id": "run-main-08",
"progress_binding": {
"goal_id": "goal-a1b2c3",
"goal_revision": 2
}
}
- agent_id:子 Agent 的 id。
- child_session_id:子 Agent 自己的 session。一个子 Agent 对应一个 session,所以示例里两者相同。
- run_id:这一轮执行的 id。同一个子 Agent 每执行一轮,就换一个新的。
- parent_session_id、parent_run_id:任务是哪个主会话、在哪一轮派出的。
- progress_binding:对应哪个 goal 模块的哪个版本。
这份记录不发给子 Agent。结果回来的时候,主 Agent 靠它找到对应的模块,再比对版本。
4.3 Subagent 执行流程简述
五、结果回来之后
子 Agent 的结果从产生到处理完,是一条连续的链路。前半段是结果怎样回到主会话,后半段是主 Agent 拿到之后怎样处理。
回调不会直接让主流程创建 run。它先判断主流程是否正在执行:正在执行,结果就进入待处理队列;主流程执行完成后,再去查看队列,处理后面的事情。所以主 Agent 手头正在处理的消息不会被打断,结果也不会丢。
新的 run 里,主 Agent 先拿结果记录和 goal 比对,这时会出现几种情况。
- 版本不一致,这份结果就不必再判断处理;
- 意图没有达成,主 Agent 需要判断后续怎么办。正常情况下还是交给原来的子 Agent。每个子 Agent 都另开一个 session,打回就是把任务发回原来的 session。之前的信息全部保留,只需要返回一个错误原因,它就可以继续执行。这时子 Agent 的 id 和 session 都不变,只是新开一个 run,并记下它接的是上一轮的哪个 run。
- 意图达成,合入的时候,对应的信息都返回给主 Agent,由它去 diff 更改的内容,再做处理。任务满足了就直接完成,goal 里对应的模块也随之移除。
小结
异步 Subagent 的这条链路,是主 Agent 把 goal 里的模块写成 brief 交出去,自己结束这一轮;子 Agent 在独立的 session 里完成任务,产物暂不合入;结果通过监听回调回到主会话,再由主 Agent 比对、合并、做后处理,必要时打回。
任务可以交出去,对结果负责这件事交不出去。