【Agent工程】(8)—— 子 Agent 分工与委托边界
文章目录
- 【Agent工程】(8)—— 子 Agent 分工与委托边界
- 1. 多 Agent 不是复制多个全权限 Agent
- 1.1 多 Agent 要解决的不是模型不够聪明
- 2. 主 Agent 与子 Agent 的角色划分
- 2.1 何时拆子 Agent
- 2.2 主 Agent 保留的最终责任
- 3. 委托子卡片要写清的字段
- 3.1 委托不可放大权限
- 3.2 子卡片模板与主卡片字段对齐
- 4. 委托执行流程
- 4.1 串行委托与后续篇目
- 5. 贯穿示例:工单升级中的通知子 Agent
- 5.1 子 Agent 失败时主链路怎么处理
- 6. 可运行示意:委托子集校验与汇总
- 6.1 子卡片校验
- 6.2 汇总 delegation 到主 trace
- 7. 成本与审计
- 7.1 总验收如何引用子结果
- 7.2 成本表示例
- 8. 四个常见误区
- 8.1 与第 7 篇状态机的关系
- 9. 适用边界
- 9.1 从单 Agent 迁移
- 9.2 评测集应覆盖委托越权
- 9.3 与工具注册表的关系
- 10. 术语速查
- 11. 小结与下一篇
摘要:第 7 篇把单 Agent 任务闭环跑通后,常见下一步是「再加几个 Agent 分工」。若每个子 Agent 仍拿全量工具与全库 scope,成本与越权会同步放大。本篇说明主 Agent 如何拆子任务、用委托子卡片收窄工具面与作用域,并汇总子轨迹做总体验收。贯穿示例继续用工单助手。适合读完 【Agent工程】(7)—— 单 Agent 任务闭环、准备引入多 Agent 但尚未定委托边界的工程师。读完可以独立完成:为主任务编写委托子卡片,并限制子 Agent 的工具与 budget。
1. 多 Agent 不是复制多个全权限 Agent
单 Agent 闭环适合一张卡片、一条链路跑完的场景(见 【Agent工程】(7)—— 单 Agent 任务闭环)。业务变复杂后,常见诉求是:
- 查询与写操作分开,降低写工具被误用的面
- 通知文案单独起草,再交主流程入 pending
- 不同子域用不同提示词,但共用一个工单目标
| 做法 | 结果 |
|---|---|
| 复制 N 个全权限 Agent | 每个都能删库、发通知,成本 N 倍 |
| 主 Agent + 收窄委托 | 子 Agent 只拿子工具、子 scope、子 budget |
工程上需要分工与委托边界:谁协调、谁执行子步骤、子 Agent 最多能碰哪些对象与工具。工具注册表、scope、pending 等机制(第 4~6 篇)仍然有效,但作用在子卡片上,而不是让每个 Agent 各读一份完整主卡片。
1.1 多 Agent 要解决的不是模型不够聪明
常见动机是「一个 Agent 提示词太长」。更稳妥的拆法仍按权限与副作用切:只读、可回滚写、对外通知、不可逆操作,尽量落在不同子卡片上。提示词变短是副产品;首要收益是子 Agent 碰不到不该碰的工具与对象。
2. 主 Agent 与子 Agent 的角色划分
推荐固定两类角色:
| 角色 | 职责 | 典型持有 |
|---|---|---|
| 主 Agent / 编排器 | 校验主卡片、拆子任务、汇总验收 | 完整卡片、总 budget、全局 trace |
| 子 Agent | 完成单一子目标 | 委托子卡片、局部工具、局部 scope |
主 Agent 不应把「整张主卡片」原样丢给子 Agent。子 Agent 收到的是委托子卡片:目标更窄、允许工具是主清单的真子集、scope 只能同等或更窄。
2.1 何时拆子 Agent
适合拆分的信号:
| 信号 | 示例子任务 |
|---|---|
| 工具面差异大 | 只读查询 vs 对外通知 |
| 提示词域不同 | 工单字段 vs 对外措辞 |
| 预算需隔离 | 通知起草最多 4 步,避免拖垮主链路 |
| 验收项可独立 | 子任务有单独 acceptance,再汇入总验收 |
不必为了「看起来先进」强行多 Agent。单 Agent 能稳定闭环时,拆分的收益应大于协调成本。
2.2 主 Agent 保留的最终责任
即使子 Agent 完成子步骤,总验收失败仍算主任务失败。主 Agent 不能把子 Agent 的「局部 done」直接当成业务 done。对外 status、降级标记、pending 入口,应始终由主 runtime context 持有,避免多个 Agent 各写一份终态导致验收分裂。
3. 委托子卡片要写清的字段
子卡片是主卡片的受控切片,建议显式字段如下:
{"parent_card_id":"card-100","sub_id":"notify-draft-1","sub_goal":"为 T-1024 起草值班通知文案,不执行发送","allowed_tools":["get_ticket"],"scope":{"write_ticket_ids":[],"read_ticket_ids":["T-1024"]},"budget":{"max_steps":4,"max_write_calls":0},"acceptance":{"draft_must_contain":["升级原因","T-1024"]}}要点:
- allowed_tools必须是主卡片允许清单的子集
- scope不得宽于主卡片;写 scope 为空表示本子 Agent 只读
- budget单独封顶,防止子 Agent 耗尽主任务步数
- parent_card_id便于审计与汇总 trace
子 Agent 运行仍走第 7 篇六阶段闭环,只是输入换成子卡片。
3.1 委托不可放大权限
运行时校验建议增加一条:子卡片权限 ⊑ 主卡片权限(子集关系)。若子 Agent 提议的工具不在子卡片、却在主卡片内,仍应拒绝——子 Agent 不能「借主卡片的名义」扩大自身面。若确需临时扩权,必须由主 Agent 重新签发新子卡片,而不是在对话里口头允许。
3.2 子卡片模板与主卡片字段对齐
第 2 篇主卡片六块字段,在子卡片上仍应可映射:子目标对应 goal、子验收对应 acceptance、子预算对应 budget。非目标同样要写,例如通知起草子卡片应声明「不发送、不改 priority」。缺非目标时,子 Agent 容易在起草阶段「顺手」调用写工具。
4. 委托执行流程
- 主 Agent 校验主卡片,进入 running
- 主 Agent 生成子卡片,调用子 Agent 运行时
- 子 Agent 独立 trace、独立 budget,返回 sub_result
- 主 Agent 把 sub_result 写入自己的 context,继续主链路或触发 pending
- 主卡片级验收读取汇总终态与子 trace
子 Agent 的used_tools应汇总进主 trace,格式建议:
{"delegations":[{"sub_id":"notify-draft-1","status":"done","used_tools":["get_ticket"],"sub_trace_ref":"trace-xyz"}]}总验收除检查主 Agent 直接调用外,还应检查:各 delegation 的工具集合均符合对应子卡片。
4.1 串行委托与后续篇目
本篇默认串行:主 Agent 完成一步后再签子卡片。多个子 Agent 并行、结果合并、冲突处理放在下一篇委托策略。串行阶段先把子集校验与 trace 汇总做扎实,再谈并行,否则越权更难查。
5. 贯穿示例:工单升级中的通知子 Agent
主任务:将T-1024升为紧急并通知值班。主 Agent 负责改优先级;通知文案交给子 Agent 起草,发送仍由主流程走 pending(第 6 篇)。
| 步骤 | 动作 |
|---|---|
| 1 | 主 Agent 执行update_ticket_priority |
| 2 | 主 Agent 签发 notify 子卡片,子 Agent 只读工单并产出 message 草稿 |
| 3 | 主 Agent 将草稿填入notify_oncall参数,进入 pending |
| 4 | 值班 approve 后发送,主链路跑总验收 |
子 Agent没有notify_oncall与写 priority 权限,避免「起草阶段顺手发出去」。主 Agent 保留高危写的 pending 入口,分工清晰。
5.1 子 Agent 失败时主链路怎么处理
| 子 Agent 结果 | 主 Agent 处理 |
|---|---|
| done,草稿合格 | 继续 notify pending |
| done,草稿缺字段 | 重签子卡片重试一次,或降级为人工写文案 |
| budget 用尽 | 标记 degraded,总验收记录 notify 未起草 |
| 工具被拒 | 检查子卡片是否过宽/过窄,勿静默扩权 |
子任务失败不应拖垮主任务无终态;主 Agent 应带降级字段进入总验收(与第 7 篇 degraded 一致)。
6. 可运行示意:委托子集校验与汇总
下面代码演示:主卡片签发子卡片前做子集校验;汇总时合并 delegation trace。
6.1 子卡片校验
from__future__importannotationsfromtypingimportAnydefis_subset(child:list[str],parent:list[str])->bool:returnset(child).issubset(set(parent))defvalidate_delegation(parent:dict[str,Any],sub:dict[str,Any])->dict[str,Any]:errors=[]ifnotis_subset(sub.get("allowed_tools")or[],parent.get("allowed_tools")or[]):errors.append("tools_not_subset")parent_write=set((parent.get("scope")or{}).get("write_ticket_ids")or[])sub_write=set((sub.get("scope")or{}).get("write_ticket_ids")or[])ifnotsub_write.issubset(parent_write):errors.append("write_scope_widened")parent_read=set((parent.get("scope")or{}).get("read_ticket_ids")orparent_write)sub_read=set((sub.get("scope")or{}).get("read_ticket_ids")orsub_write)ifnotsub_read.issubset(parent_read):errors.append("read_scope_widened")sub_budget=sub.get("budget")or{}parent_budget=parent.get("budget")or{}if(sub_budget.get("max_steps")or0)>(parent_budget.get("max_steps")or0):errors.append("budget_exceeds_parent")iferrors:return{"ok":False,"errors":errors}return{"ok":True}if__name__=="__main__":parent_card={"card_id":"card-100","allowed_tools":["get_ticket","update_ticket_priority","notify_oncall",],"scope":{"write_ticket_ids":["T-1024"],"read_ticket_ids":["T-1024","T-2048"],},"budget":{"max_steps":12},}sub_ok={"sub_id":"notify-draft-1","allowed_tools":["get_ticket"],"scope":{"read_ticket_ids":["T-1024"]},"budget":{"max_steps":4},}sub_bad={"sub_id":"evil-1","allowed_tools":["delete_ticket"],"scope":{"write_ticket_ids":["T-9999"]},"budget":{"max_steps":99},}print(validate_delegation(parent_card,sub_ok))print(validate_delegation(parent_card,sub_bad))6.2 汇总 delegation 到主 trace
defmerge_delegation_trace(main_trace:dict[str,Any],sub_id:str,sub_trace:dict[str,Any],sub_card:dict[str,Any],)->dict[str,Any]:used=sub_trace.get("used_tools")or[]allowed=set(sub_card.get("allowed_tools")or[])ifnotset(used).issubset(allowed):raiseValueError(f"sub{sub_id}used tools outside sub card")delegations=list(main_trace.get("delegations")or[])delegations.append({"sub_id":sub_id,"status":sub_trace.get("status","unknown"),"used_tools":used,})main_trace["delegations"]=delegations all_used=set(main_trace.get("used_tools")or[])all_used.update(used)main_trace["used_tools"]=sorted(all_used)returnmain_traceif__name__=="__main__":main={"used_tools":["update_ticket_priority"],"delegations":[]}sub_trace={"status":"done","used_tools":["get_ticket"]}sub_card={"allowed_tools":["get_ticket"]}print(merge_delegation_trace(main,"notify-draft-1",sub_trace,sub_card))生产环境子 Agent 应运行在独立 runtime 实例中,子卡片通过 API 传入,避免共享可变全局状态导致 scope 串线。
联调时建议至少跑三条路径:子卡片校验拒绝越权、子 Agent 成功 merge trace、子失败主链路 degraded。三条都过,再考虑把主 Agent 上的重复工具删掉。
7. 成本与审计
多 Agent 的第一风险往往是成本翻倍:每个子 Agent 都跑满主 budget,总 token 与工具调用线性叠加。
| 控制点 | 做法 |
|---|---|
| 子 budget | 单独 max_steps,且小于主 budget 剩余 |
| 委托次数 | 主卡片限制 max_delegations |
| 模型档位 | 子 Agent 可用更小模型做只读起草 |
| 审计 | 每条 delegation 记录 sub_id、耗时、used_tools |
主 Agent 签发子卡片时,建议把max_delegations写入主卡片 budget,例如默认 3。超过次数时不再 spawn 新子 Agent,而是走 degraded 或人工接管,避免主 Agent 在循环里无限「再开一个子 Agent 试试」。
审计应能回答:「通知文案是哪个 sub_id 生成的、用了哪些工具、是否超出子卡片」。这与第 7 篇 trace 字段一致,只是多一层 delegations 数组。
7.1 总验收如何引用子结果
主卡片 acceptance 可拆为:
| 类型 | 示例 |
|---|---|
| 主 Agent 直接写 | priority_equals urgent |
| 子 Agent 产出 | notify_draft_contains 升级原因 |
| 汇总 | 全部 delegations status 为 done 或已降级 |
子 Agent 的 sub acceptance 通过后再并入主 context;主验收不应跳过 sub 检查直接看最终 notify 是否发出。
7.2 成本表示例
假设主卡片max_steps=12,已用 5 步,剩余 7 步。通知起草子卡片建议max_steps=4,且不超过剩余值。主 Agent 在签发时用min(子卡片上限, 主剩余)计算,避免子 Agent 用满 12 步导致主链路无步数改 priority。
8. 四个常见误区
| 误区 | 典型表现 | 更稳妥的做法 |
|---|---|---|
| 子 Agent 全权限复制 | 每个都能 delete | 子卡片收窄 allowed_tools |
| 口头委托无子卡片 | 审计对不上 | 签发 sub_id 与 JSON 子卡片 |
| 子任务无 budget | 子 Agent 循环调用 | 子 budget 封顶 |
| 主 Agent 不汇总 | 局部对、全局错 | merge_delegation_trace + 总验收 |
还有一种隐蔽做法:子 Agent 返回「建议调用 notify_oncall」,主 Agent 不经 pending 直接执行。高危写仍应走第 6 篇 pending,子 Agent 只产出参数草稿,不替代确认链。
8.1 与第 7 篇状态机的关系
主 context 的 status 由总验收决定;子 Agent 内部可有 done / failed,但不直接映射为业务 done。推荐映射:子 Agent running 时主 status 仍为 running;子 await_confirm 不替代主 pending——notify 的 pending 仍挂在主链路上。
9. 适用边界
本篇方法适合:
- 单主任务可拆成 2~3 个可验收子步骤
- 工具面或提示词域明显不同
- 需要隔离子步骤成本
本篇不覆盖:
- 子 Agent 再委托子 Agent 的多层链——下一篇委托策略展开
- 并行多子 Agent 竞态与合并——后续编排篇目
- 跨租户、跨系统的联合 Agent——需单独信任域设计
若只有单一只读查询加一个写操作,单 Agent 闭环通常足够;等到出现「写操作与对外通知必须隔离」时再引入子 Agent,迁移成本更可控。
9.1 从单 Agent 迁移
- 选一个子步骤(如通知起草)试点子卡片
- 主 Agent 仍保留原工具,对比双轨 trace 一致后再收窄主 Agent 工具面
- 总验收增加 delegations 检查项
不要第一步就拆成五个 Agent;每多一个委托点,协调与审计复杂度上升一档。
9.2 评测集应覆盖委托越权
回归用例除主 Agent 越权外,应加:
| 用例 | 期望 |
|---|---|
| 子卡片含 delete | validate_delegation 拒绝 |
| 子 scope 宽于主 scope | 拒绝签发 |
| 子 used_tools 超子卡片 | merge 抛错或验收失败 |
委托链路过长时,这类用例比「最终 priority 对不对」更能提前发现问题。
9.3 与工具注册表的关系
子卡片的 allowed_tools 必须是注册表里已存在且 enabled 的 tool_id 子集。主 Agent 签发前可再查一次注册表,避免子卡片引用已禁用工具。子 Agent 运行时仍走第 4 篇门禁,只是允许清单换成子卡片上的更窄列表。
10. 术语速查
| 术语 | 含义 |
|---|---|
| 委托子卡片 | 主 Agent 签发给子 Agent 的收窄任务输入 |
| 委托边界 | 子 Agent 不得超出的工具、scope、budget 上限 |
| delegation | 一次子 Agent 调用的记录条目 |
| sub_id | 委托子任务唯一编号 |
| 权限子集 | 子卡片权限必须是主卡片的子集 |
| 汇总 trace | 主 Agent 合并自身与子 Agent 的工具轨迹 |
11. 小结与下一篇
多 Agent 的价值在分工 + 收窄,不在 Agent 数量:
- 主 Agent持主卡片,负责拆任务与总验收
- 子卡片限制工具、scope、budget,禁止放大权限
- 子 Agent跑独立闭环,结果以 delegation 汇总
- 高危写仍由主链路 pending,子 Agent 不替代确认
下一篇继续编排单元:委托策略(何时并行、何时串行、失败如何重试与谁承担 budget)。
系列导航:
- 上一篇:【Agent工程】(7)—— 单 Agent 任务闭环
- 下一篇:【Agent工程】(9)—— 委托策略与失败降级(撰写中)