说实话,提到 AI Agent 和 Subagent 的协作,我最早是持怀疑态度的。大概两三个月前,我接手了一个任务:把几十份格式混乱的行业资料整理成结构化问答库。一开始我用单个 AI Agent 跑,前面几轮效果还行,但随着材料累积,问题立刻暴露——上下文窗口被塞满,它开始把 A 文档的结论安到 B 文档身上,甚至重复输出已经给过的答案。当时同事建议:"不如拆成多个 Subagent,各自处理一块?" 我想着这不过就是"主 Agent 派活、子 Agent 干活",结果一上手就连续踩坑,前后重构了两版才稳定下来。这篇文章就是我从"多次翻车"到"基本跑稳"的完整复盘,聊聊 Agent 和 Subagent 到底是怎么协作的,哪些环节最容易出问题,以及我最后沉淀下来的那套协作约定。标题讲的是"协作",但本质是在解决三件事:怎么把大任务拆出去、怎么把必要的信息交出去、怎么把结果可靠地收回来。文章适合所有正在用 Agent 做自动化任务的人,尤其是那些已经发现"嵌套子代理跑得很欢,结果却一塌糊涂"的朋友。如果你还没开始用 Subagent,这篇文章也能帮你少走很多弯路。1. 单 Agent 卡壳的本质:不是能力不够,而是上下文在打架1.1 一个典型的多任务场景那批资料包含几十份文档,有扫描件转出的文本、有表格、有会议纪要。如果全部塞给一个 Agent 去"读完并整理",会出现一个很尴尬的矛盾:你希望它保持全局视角,但它同时只能处理有限的上下文。实测中,当输入材料累积到接近窗口上限时,模型的注意力会明显下降,具体表现就是前后矛盾、遗漏关键数字、把不同来源的信息混在一起。这其实不算模型的能力问题,而是信息太多导致的"注意力稀释"。类比一下:让一个人同时盯几十块监控屏幕,他一定会漏看和串台。所以从方向上来说,把任务拆给多个 Subagent 是对的。但问题出在后面三步——怎么拆、怎么传、怎么收。这三步没设计好,拆了反而比不拆更糟。1.2 委派机制真正解决的是什么很多文章把 Subagent 说得特别玄,什么"自主规划、自主决策"。以我实际项目里的体会,它的价值其实可以归纳成三条:上下文隔离:每个子代理只处理自己负责的那块材料,避免无关信息互相干扰。职责聚焦:子代理的提示词可以只围绕单一任务,不需要在它头上压一堆全局约束。并行潜力:互不依赖的子任务可以同时跑,整体耗时能明显压下来。但这三条优势没有一个会自动生效。如果主 Agent 委派时把整段对话历史原封不动传给子代理,那"上下文隔离"从根上就失效了;如果任务描述写得模棱两可,子代理就会按自己的理解脑补,这时候"职责聚焦"也成了一句空话。我发现很多团队在引入 Subagent 时,看中的是"并行潜力",却忽略了前两条才是稳定性的关键,最后并行没吃到红利,反而多了不少协调成本。1.3 最典型的错误认知我见过不少方案(包括我自己最初那版),设计思路都是这样的:主 Agent 拿到一个复杂需求,先写一段自然语言"帮我把这个任务拆一下",然后把拆出来的子任务再塞给下一层 Agent,下一层如果觉得任务还不够小,就继续往下拆。听上去很自动化,在实际跑起来之后,问题会一个个浮现:拆出来的子任务不一定比原来的任务更清楚,只是变得更碎。每一层转述都会丢信息、添油加醋,任务目标传到第三层时已经偏离原意。没有任何机制保证最终的结果能拼回主任务的目标。这就是"看起来很智能,用起来很崩溃"的来源。走到这一步我才真正意识到,Agent 和 Subagent 之间需要的不是"更自由的对话",而是一套具备约束力的协作协议。自由对话适合人跟人之间,因为人有常识、有默契;但模型每次调用都是"全新的人",它不会自动继承你的全局意图。把意图显式写清楚,是整个协作设计的第一原则。2. 协作的三段式链路:任务分发、上下文交割、结果回收要把协作做得稳定,我的做法是把主 Agent 与子 Agent 之间的交互切成三段:任务分发、上下文交割、结果回收。每一段都有明确的产出物和检查点,缺一段都会出问题。2.1 任务分发:别用一句话派活先看看最常见的做法:"你负责整理 PDF 里的财务数据,整理好给我。" 这句话在传统职场里都很难保证不出错,更别说是让模型去执行。模型会默认补充很多你没想到的细节,然后给你一份看似完整、实则跑偏的输出。我现在验证过最稳定的任务分发,是一份结构化"任务单"。下面是一个简化版示例,字段不一定全,但思路可以参考:{ "subtask_id": "subtask_001", "objective": "从 input_doc_03 中提取 2023 年度的所有成交额数据,按月份输出", "input_ref": ["storage://docs/input_doc_03.txt"], "constraints": [ "只使用 input_ref 指向的材料,不得引入外部推测", "如果原文没有明确月份,标注 null 而不是猜测" ], "output_schema": { "type": "array", "items": { "month": "string", "amount": "number" } }, "acceptance_criteria": [ "覆盖 1 到 12 月,缺失的月份必须显式列出并标记为 missing", "金额保留两位小数", "金额单位为万元" ], "max_retries": 2 }这里最关键的是最后两项。output_schema告诉子代理"交什么作业",acceptance_criteria定义了"什么才算合格作业"。没有这两个字段,子代理大概率自由发挥,你事后还得花大量精力清洗和兜底。从实战角度说,任务单里的每一项最好都能被自动校验。"覆盖 1 到 12 月"就比"把数据整理完整"实用得多,因为它是可检查的;"金额保留两位小数"也比"输出规范"可执行。凡是不能校验的要求,本质上都是在给模型留模糊空间。2.2 上下文交割:只传必要的,不传全部的第二个容易踩的坑是:为了让子代理"理解全局",主代理把整段对话历史、其他子代理的输出也一并塞给它。这样做的结果通常是三连:token 消耗暴涨;子代理的注意力被无关信息干扰,反而忽略了真正要处理的内容;多个子代理各拿一份大上下文时,成本线性叠加,甚至更高。我的做法是,在任务单之外单独维护一份"上下文卡片"。它包含三个部分:任务背景摘要:用三到五句话讲明白这份材料在整个大任务里的位置;本子任务需要的局部事实:比如相关文档的编号、需要重点关注的关键段落;明确排除的信息:比如"不需要处理 2022 年及以前的数据"。这份上下文卡片不是让主 Agent 随口生成的,而是按固定格式生成。理想情况下,上下文卡片的长度控制在 500 token 以内。如果某个子任务确实需要读全文,那就传"文件引用路径",让子代理按需读取,而不是把全文复制进上下文。这个习惯帮我省下的成本相当可观,后面第 5 章会详细算。2.3 结果回收:把子代理的输出当"代码提交"看待子代理返回结果时,它自己通常自信满满,但千万别直接采信。我在内部推行的原则是:把子代理输出当成一次代码提交来审核,而不是当成最终答案。审核分三步:第一步是格式校验,按output_schema检查,结构不对直接打回;第二步是自洽性校验,比如数字之间是否互相矛盾、是否有明显越界值;第三步是交叉核对,如果任务单要求对照原文档,就把关键字段和源材料再做一次比对,这一步可以由主 Agent 完成,也可以用代码里的规则完成。任何一步没过,都应该把具体失败原因写回任务单,重新委派给子代理修正。这里有个关键细节:反馈要具体。不要说"结果不对,请重试",要说"3 月和 4 月的成交额缺失,且 5 月的金额没有保留两位小数"。模型对具体修正指令的响应,比对笼统的"再来一遍"要敏感得多。原因也不难理解:模型需要知道差异在哪里,而不是单纯被要求重复劳动。3. 实测踩坑复盘:一次"三层嵌套"设计引发的连环翻车3.1 背景与最初的设计为了防止纸上谈兵,分享一段真实的踩坑经历。当时我在做一个模拟项目,目标是把几十份项目文档整理成统一格式的"知识库问答条目"。第一版我设计成了三层结构:主 Agent:负责理解整体需求、做任务规划;中层 Agent:负责把主 Agent 发来的子任务再拆成更细的步骤,当时我觉得这样更"专业";底层 Agent:真正执行读取文档、生成条目的工作。理论上,这像是一个正经公司的三层汇报体系。但实际跑起来,三层结构几乎没有带来任何好处,反而触发了一连串事故。现在回头看,这根本就是把组织管理的直觉错误地搬到了 Agent 协作里——公司里加管理层可以减少沟通成本,但模型之间加"中层"只会增加信息损耗。3.2 第一轮翻车:任务目标在传递中漂移第一轮事故相当典型。主 Agent 给中层 Agent 派的任务是"基于 2023 年度文档生成知识条目"。中层 Agent 为了把任务拆得更细,自作主张地在委派单里加了一句"重点关注团队与协作相关内容"。这句补充是它自己根据文档标题推测出来的,并不是主 Agent 的本意。于是底层 Agent 交回来的条目,大量聚焦在"团队协作流程",而整体需求里占比最大的"数据指标说明"反而被漏掉了。最后我核对成果时,发现产出的知识条目和项目需求对不上,只能返工。排查时看日志,中层 Agent 的输出里有一行它的"自我理解",与主任务的原意已经有了明显偏差。这类偏差不是模型"坏",而是每一层转述都在做有损压缩,层数越多,损失越大。语言模型在转述时总会加入自己的推测性补充,这是它的工作方式,你不能怪它,只能从结构上减少转述次数。3.3 第二轮翻车:上下文拼接爆炸三层结构还有另一个隐藏问题。为了让底层 Agent 能准确处理每一份文档,我当时采用的策略是:主 Agent 把所有文档的核心段落传给中层,中层再原样传给底层。结果就是每一层都在重复携带同一份巨大的上下文。跑了几次任务之后我统计了一下,单轮整理的 token 消耗大约是单 Agent 方案的 4 到 5 倍。更尴尬的是,上下文变大并没有带来更准的输出,因为大量无关段落在底层 Agent 那里只是徒增噪音。一段时间里接口费用涨得很快,项目差点被叫停。这个问题的根子就是没有做"上下文按需裁剪",把"隔离"做成了"扩散"。很多初学者会复制同样的错误,以为上下文传得越全模型就越聪明。但在实际体验里,给模型塞一堆无关内容,效果等同于让一个人带着一堆不相干的文件开会,他反而找不到真正需要的那张纸。3.4 第三轮翻车:子代理擅自创建子代理,出现循环委派第三轮最离谱。某次一个底层 Agent 在读取文档时发现有一小段表格无法解析,它没有按约束直接标记"无法识别",而是自己又创建了一个新的子代理,让新子代理帮忙"解析表格"。新子代理拿到任务后,又把问题抛回给了原来的底层 Agent。日志里能看到连续几十次"创建新子代理"的记录,任务迟迟不结束。虽然我设置了单次请求的超时,但整个流程的编排层没有做最大深度限制,导致它在超时前消耗了大量 token,还拖慢了整个任务队列。这轮事故给我的教训非常直接:不要让子代理拥有无限的"创建权"。无论是工程约束还是调度策略,都必须限制递归深度,最好直接禁止"子代理再建子代理"。不要指望模型在运行中去"自我克制",它没有这种长期自控能力,约束必须写死在外层代码里。3.5 三轮翻车的小结把这轮复盘整理成一张表,方便自查:问题表现根因关键修复任务漂移产出与主需求不符多层转述损失信息,中层擅自补充目标减少层级,用结构化任务单固定目标上下文爆炸token 成本 4-5 倍,输出质量下降未经裁剪的上下文全量传递上下文卡片按需供给,控制长度循环委派子代理不断创建新子代理缺少深度限制与禁止递归规则限制最大深度,禁止子代理创建子代理这张表现在还贴在我工作台旁边,每次设计新的 Agent 协作方案时,我都会照着这三行检查一遍,踩过的坑就不再踩第二次。4. 目前验证过最稳的协作范式:扁平化委派 + 结构化任务单 + 校验环踩完坑之后,我重构方案时就盯着三条原则:层级要扁、任务单要结构、结果要验证。三件事缺一不可。4.1 扁平化:最多两层,禁止往下再建目前我所有在跑的任务,都遵守这条硬规矩:主 Agent 可以直接管理多个子代理,但子代理不允许自行创建子代理。如果某个子任务仍然太大,由主 Agent 负责把它二次拆分成更小的委派单,而不是让子代理自行去"外包"。这样做的好处有三个:信息损耗可控:主 Agent 到子代理只有一跳,任务目标不会经过多轮转述;排查简单:出了问题只需要检查主 Agent 和对应子代理之间的单次通讯,不用追一整条委派链;成本可控:不会出现无法预测的递归扩张。如果业务场景真的需要多级分解,比如主任务极其复杂,那也应该由编排层代码去分步调度,而不是靠 Agent 自主递归。代码层做分解和 Agent 自主递归是两回事,前者每一步都可控,后者随时可能失控。这句话是我用真金白银买来的经验。4.2 任务单与上下文卡片的完整示例下面是一个我在用的任务单模板,去掉了具体业务信息,保留通用结构:{ "subtask_id": "subtask_004", "objective": "根据 docs/contract_2023.md,生成 5 条面向用户的问答条目", "context_card": { "background": "这批问答条目用于某企业内部知识库,用户为非技术岗位", "local_facts": [ "contract_2023.md 第 3 节是付款条款", "第 5 节是违约责任", "不要引入合同之外的价格信息" ], "excluded": [ "不需要解读 2022 年旧合同" ] }, "constraints": [ "每条回答不超过 120 字", "必须引用原文中的具体条款编号" ], "output_schema": { "type": "array", "items": { "question": "string", "answer": "string", "clause_ref": "string" } }, "acceptance_criteria": [ "条目数量不得少于 5 条", "每条 answer 必须包含 clause_ref" ], "max_retries": 2, "forbidden_actions": [ "禁止创建任何新的子代理来处理本任务" ] }注意最后那个字段:forbidden_actions。它专门用来阻断"子代理创建子代理"的路径。别小看这一行,它能在运行日志里给你一条明确的判断依据——一旦出现违反 forbidden_actions 的操作,直接判定任务失败,不给模型狡辩的机会。4.3 校验环:反馈要带着证据校验环节的代码思路很简单,但比代码本身更重要的是"错误信息要具体":def validate_result(subtask, result): errors = [] # 1. 结构校验 if not isinstance(result, list): errors.append("输出不是数组,和 output_schema 不一致") # 2. 数量校验 if len(result) < subtask["acceptance_criteria"][0]: errors.append("条目数量不足") # 3. 关键字段校验 for item in result: if not item.get("clause_ref"): errors.append("存在缺少 clause_ref 的条目") return errors def dispatch_with_retry(subtask): for attempt in range(subtask["max_retries"]): result = call_subagent(subtask) errors = validate_result(subtask, result) if not errors: return result # 把具体的错误反馈写回任务单 subtask["feedback"] = "; ".join(errors) raise TaskFailed(subtask["subtask_id"])这里的核心模式是:errors必须带着具体原因,重新委派时把这些原因写进新的任务单。模型在收到"3 月和 4 月成交额缺失,5 月金额未保留两位小数"这类反馈后,修正成功率会明显高于收到"结果不合格请重试"。因为前者指出了明确的修正方向,后者只是告诉它"你又错了"。4.4 为什么这套范式有效用大白话讲,这套范式本质上是把 Agent 协作从"人和人聊天"降维成"接口调用"。人聊天的时候可以靠默契补全信息,但模型没有跨会话的默契,每一次调用都是全新的人。所以协作协议必须把目标、上下文、边界、验收标准都显式写清楚。这也是我一直跟身边人强调的:不要追求"Agent 之间像同事一样自然对话",要追求"每次委派都像一张填写完整的工单"。自然对话的容错率很低,你预期对方听懂了你没说出口的意思,但它根本没这个能力;工单式协作则几乎每一步都可检查、可回滚、可重试。稳定,比"看起来智能"重要得多。5. 工程化收尾:成本、并发、可观测性一个都不能少协作协议定了,最后是工程层面的几个细节。它们不直接决定"能不能跑通",但决定了"能不能长期稳定运行"。很多项目前期 Demo 跑得很漂亮,一上量就崩,基本都是栽在这几个细节上。5.1 成本控制的三道闸门第一道是上下文闸门:每个子代理收到的上下文卡片必须控制在 500 token 以内,更长的材料走文件引用,不直接复制全文。这能从源头压住大部分 token 浪费。最初我这道闸门形同虚设,每次看到 token 账单都很心虚。第二道是输出闸门:任务单中的output_schema只允许子代理输出必要字段。比如整理问答条目时,只返回 question、answer、clause_ref,不需要它把整段源文档再抄一遍。这能避免"子代理把原文重复返工"这种隐蔽开销,这类开销不加日志统计时根本发现不了。第三道是重试闸门:重试次数必须写死在任务单里,一般 1 到 2 次就够。超过上限就记录失败并交给人工审查,不要无限重试。无限重试不仅烧钱,还会把错误结果一遍遍加强,最后可能给出一个看起来很完整、实则全是幻觉的答案。5.2 并发与超时:把失控概率压到最低并行跑多个子代理确实能提速,但要注意三点:限制最大并发数。我常用的上限是 5,具体取决于接口速率限制和上下文总预算。并发太高时,单个任务出问题会导致"群体性失败",排查起来极其麻烦。每个子代理必须有自己的超时时间。整体超时和子代理超时要分开设置,这样失败时才知道是哪一环超时。如果只设一个全局超时,你只能看到一个笼统的"超时",定位不到具体是哪个子代理卡住了。递归限制落实到代码层。至少要加一个max_depth参数,默认值为 1 或者 2,而不是默认无限。很多框架默认不限制递归深度,这是生产事故的高发区。5.3 可观测性:日志里要有"委派单"和"校验结果"跑了几十次任务之后,我最大的感触是:Agent 协作出问题,最怕的不是报错,而是不报错但结果不对。所以日志里至少要有三样东西:每次委派的任务单完整内容,方便事后复盘"它到底接到了什么指令";子代理返回的原始输出以及校验结果,校验失败时能看到具体的 error 列表;每次调用的 token 消耗和耗时,方便做成本归因。我在实际项目中会给每次委派生成一个运行 ID,把任务单、输出、校验结果、token 消耗绑定存起来。后续排查某条知识条目质量差时,直接按 ID 查记录——当时给子代理传了什么、它答了什么、哪一步验证没过,一目了然。没有这套日志体系时,排查一次异常往往要翻半天聊天记录,效率极低。5.4 一个值得保留的小习惯最后分享一个小习惯:在任务单末尾增加一个字段,内容大概是"如果本任务确实无法完成,请直接说明阻碍你的具体原因,不要尝试自行扩大任务范围"。这个字段看起来增加了自由度,实际反而减少了模型的盲目发挥。模型在承认"我做不了"这件事上,通常比"我必须做出来点什么"更诚实。而诚实的失败永远比虚假的成功好修——至少你知道问题出在哪,而不是面对一份看似完成、实则全错的垃圾输出,还得逐条去核对。
说实话,提到 AI Agent 和 Subagent 的协作,我最早是持怀疑态度的。大概两三个月前,我接手了一个任务:把几十份格式混乱的行业资料整理成结构化问答库。一开始我用单个 AI Agent 跑,前面几轮效果还行,但随着材料累积,问题立刻暴露——上下文窗口被塞满,它开始把 A 文档的结论安到 B 文档身上,甚至重复输出已经给过的答案。当时同事建议:"不如拆成多个 Subagent,各自处理一块?" 我想着这不过就是"主 Agent 派活、子 Agent 干活",结果一上手就连续踩坑,前后重构了两版才稳定下来。这篇文章就是我从"多次翻车"到"基本跑稳"的完整复盘,聊聊 Agent 和 Subagent 到底是怎么协作的,哪些环节最容易出问题,以及我最后沉淀下来的那套协作约定。标题讲的是"协作",但本质是在解决三件事:怎么把大任务拆出去、怎么把必要的信息交出去、怎么把结果可靠地收回来。文章适合所有正在用 Agent 做自动化任务的人,尤其是那些已经发现"嵌套子代理跑得很欢,结果却一塌糊涂"的朋友。如果你还没开始用 Subagent,这篇文章也能帮你少走很多弯路。1. 单 Agent 卡壳的本质:不是能力不够,而是上下文在打架1.1 一个典型的多任务场景那批资料包含几十份文档,有扫描件转出的文本、有表格、有会议纪要。如果全部塞给一个 Agent 去"读完并整理",会出现一个很尴尬的矛盾:你希望它保持全局视角,但它同时只能处理有限的上下文。实测中,当输入材料累积到接近窗口上限时,模型的注意力会明显下降,具体表现就是前后矛盾、遗漏关键数字、把不同来源的信息混在一起。这其实不算模型的能力问题,而是信息太多导致的"注意力稀释"。类比一下:让一个人同时盯几十块监控屏幕,他一定会漏看和串台。所以从方向上来说,把任务拆给多个 Subagent 是对的。但问题出在后面三步——怎么拆、怎么传、怎么收。这三步没设计好,拆了反而比不拆更糟。1.2 委派机制真正解决的是什么很多文章把 Subagent 说得特别玄,什么"自主规划、自主决策"。以我实际项目里的体会,它的价值其实可以归纳成三条:上下文隔离:每个子代理只处理自己负责的那块材料,避免无关信息互相干扰。职责聚焦:子代理的提示词可以只围绕单一任务,不需要在它头上压一堆全局约束。并行潜力:互不依赖的子任务可以同时跑,整体耗时能明显压下来。但这三条优势没有一个会自动生效。如果主 Agent 委派时把整段对话历史原封不动传给子代理,那"上下文隔离"从根上就失效了;如果任务描述写得模棱两可,子代理就会按自己的理解脑补,这时候"职责聚焦"也成了一句空话。我发现很多团队在引入 Subagent 时,看中的是"并行潜力",却忽略了前两条才是稳定性的关键,最后并行没吃到红利,反而多了不少协调成本。1.3 最典型的错误认知我见过不少方案(包括我自己最初那版),设计思路都是这样的:主 Agent 拿到一个复杂需求,先写一段自然语言"帮我把这个任务拆一下",然后把拆出来的子任务再塞给下一层 Agent,下一层如果觉得任务还不够小,就继续往下拆。听上去很自动化,在实际跑起来之后,问题会一个个浮现:拆出来的子任务不一定比原来的任务更清楚,只是变得更碎。每一层转述都会丢信息、添油加醋,任务目标传到第三层时已经偏离原意。没有任何机制保证最终的结果能拼回主任务的目标。这就是"看起来很智能,用起来很崩溃"的来源。走到这一步我才真正意识到,Agent 和 Subagent 之间需要的不是"更自由的对话",而是一套具备约束力的协作协议。自由对话适合人跟人之间,因为人有常识、有默契;但模型每次调用都是"全新的人",它不会自动继承你的全局意图。把意图显式写清楚,是整个协作设计的第一原则。2. 协作的三段式链路:任务分发、上下文交割、结果回收要把协作做得稳定,我的做法是把主 Agent 与子 Agent 之间的交互切成三段:任务分发、上下文交割、结果回收。每一段都有明确的产出物和检查点,缺一段都会出问题。2.1 任务分发:别用一句话派活先看看最常见的做法:"你负责整理 PDF 里的财务数据,整理好给我。" 这句话在传统职场里都很难保证不出错,更别说是让模型去执行。模型会默认补充很多你没想到的细节,然后给你一份看似完整、实则跑偏的输出。我现在验证过最稳定的任务分发,是一份结构化"任务单"。下面是一个简化版示例,字段不一定全,但思路可以参考:{ "subtask_id": "subtask_001", "objective": "从 input_doc_03 中提取 2023 年度的所有成交额数据,按月份输出", "input_ref": ["storage://docs/input_doc_03.txt"], "constraints": [ "只使用 input_ref 指向的材料,不得引入外部推测", "如果原文没有明确月份,标注 null 而不是猜测" ], "output_schema": { "type": "array", "items": { "month": "string", "amount": "number" } }, "acceptance_criteria": [ "覆盖 1 到 12 月,缺失的月份必须显式列出并标记为 missing", "金额保留两位小数", "金额单位为万元" ], "max_retries": 2 }这里最关键的是最后两项。output_schema告诉子代理"交什么作业",acceptance_criteria定义了"什么才算合格作业"。没有这两个字段,子代理大概率自由发挥,你事后还得花大量精力清洗和兜底。从实战角度说,任务单里的每一项最好都能被自动校验。"覆盖 1 到 12 月"就比"把数据整理完整"实用得多,因为它是可检查的;"金额保留两位小数"也比"输出规范"可执行。凡是不能校验的要求,本质上都是在给模型留模糊空间。2.2 上下文交割:只传必要的,不传全部的第二个容易踩的坑是:为了让子代理"理解全局",主代理把整段对话历史、其他子代理的输出也一并塞给它。这样做的结果通常是三连:token 消耗暴涨;子代理的注意力被无关信息干扰,反而忽略了真正要处理的内容;多个子代理各拿一份大上下文时,成本线性叠加,甚至更高。我的做法是,在任务单之外单独维护一份"上下文卡片"。它包含三个部分:任务背景摘要:用三到五句话讲明白这份材料在整个大任务里的位置;本子任务需要的局部事实:比如相关文档的编号、需要重点关注的关键段落;明确排除的信息:比如"不需要处理 2022 年及以前的数据"。这份上下文卡片不是让主 Agent 随口生成的,而是按固定格式生成。理想情况下,上下文卡片的长度控制在 500 token 以内。如果某个子任务确实需要读全文,那就传"文件引用路径",让子代理按需读取,而不是把全文复制进上下文。这个习惯帮我省下的成本相当可观,后面第 5 章会详细算。2.3 结果回收:把子代理的输出当"代码提交"看待子代理返回结果时,它自己通常自信满满,但千万别直接采信。我在内部推行的原则是:把子代理输出当成一次代码提交来审核,而不是当成最终答案。审核分三步:第一步是格式校验,按output_schema检查,结构不对直接打回;第二步是自洽性校验,比如数字之间是否互相矛盾、是否有明显越界值;第三步是交叉核对,如果任务单要求对照原文档,就把关键字段和源材料再做一次比对,这一步可以由主 Agent 完成,也可以用代码里的规则完成。任何一步没过,都应该把具体失败原因写回任务单,重新委派给子代理修正。这里有个关键细节:反馈要具体。不要说"结果不对,请重试",要说"3 月和 4 月的成交额缺失,且 5 月的金额没有保留两位小数"。模型对具体修正指令的响应,比对笼统的"再来一遍"要敏感得多。原因也不难理解:模型需要知道差异在哪里,而不是单纯被要求重复劳动。3. 实测踩坑复盘:一次"三层嵌套"设计引发的连环翻车3.1 背景与最初的设计为了防止纸上谈兵,分享一段真实的踩坑经历。当时我在做一个模拟项目,目标是把几十份项目文档整理成统一格式的"知识库问答条目"。第一版我设计成了三层结构:主 Agent:负责理解整体需求、做任务规划;中层 Agent:负责把主 Agent 发来的子任务再拆成更细的步骤,当时我觉得这样更"专业";底层 Agent:真正执行读取文档、生成条目的工作。理论上,这像是一个正经公司的三层汇报体系。但实际跑起来,三层结构几乎没有带来任何好处,反而触发了一连串事故。现在回头看,这根本就是把组织管理的直觉错误地搬到了 Agent 协作里——公司里加管理层可以减少沟通成本,但模型之间加"中层"只会增加信息损耗。3.2 第一轮翻车:任务目标在传递中漂移第一轮事故相当典型。主 Agent 给中层 Agent 派的任务是"基于 2023 年度文档生成知识条目"。中层 Agent 为了把任务拆得更细,自作主张地在委派单里加了一句"重点关注团队与协作相关内容"。这句补充是它自己根据文档标题推测出来的,并不是主 Agent 的本意。于是底层 Agent 交回来的条目,大量聚焦在"团队协作流程",而整体需求里占比最大的"数据指标说明"反而被漏掉了。最后我核对成果时,发现产出的知识条目和项目需求对不上,只能返工。排查时看日志,中层 Agent 的输出里有一行它的"自我理解",与主任务的原意已经有了明显偏差。这类偏差不是模型"坏",而是每一层转述都在做有损压缩,层数越多,损失越大。语言模型在转述时总会加入自己的推测性补充,这是它的工作方式,你不能怪它,只能从结构上减少转述次数。3.3 第二轮翻车:上下文拼接爆炸三层结构还有另一个隐藏问题。为了让底层 Agent 能准确处理每一份文档,我当时采用的策略是:主 Agent 把所有文档的核心段落传给中层,中层再原样传给底层。结果就是每一层都在重复携带同一份巨大的上下文。跑了几次任务之后我统计了一下,单轮整理的 token 消耗大约是单 Agent 方案的 4 到 5 倍。更尴尬的是,上下文变大并没有带来更准的输出,因为大量无关段落在底层 Agent 那里只是徒增噪音。一段时间里接口费用涨得很快,项目差点被叫停。这个问题的根子就是没有做"上下文按需裁剪",把"隔离"做成了"扩散"。很多初学者会复制同样的错误,以为上下文传得越全模型就越聪明。但在实际体验里,给模型塞一堆无关内容,效果等同于让一个人带着一堆不相干的文件开会,他反而找不到真正需要的那张纸。3.4 第三轮翻车:子代理擅自创建子代理,出现循环委派第三轮最离谱。某次一个底层 Agent 在读取文档时发现有一小段表格无法解析,它没有按约束直接标记"无法识别",而是自己又创建了一个新的子代理,让新子代理帮忙"解析表格"。新子代理拿到任务后,又把问题抛回给了原来的底层 Agent。日志里能看到连续几十次"创建新子代理"的记录,任务迟迟不结束。虽然我设置了单次请求的超时,但整个流程的编排层没有做最大深度限制,导致它在超时前消耗了大量 token,还拖慢了整个任务队列。这轮事故给我的教训非常直接:不要让子代理拥有无限的"创建权"。无论是工程约束还是调度策略,都必须限制递归深度,最好直接禁止"子代理再建子代理"。不要指望模型在运行中去"自我克制",它没有这种长期自控能力,约束必须写死在外层代码里。3.5 三轮翻车的小结把这轮复盘整理成一张表,方便自查:问题表现根因关键修复任务漂移产出与主需求不符多层转述损失信息,中层擅自补充目标减少层级,用结构化任务单固定目标上下文爆炸token 成本 4-5 倍,输出质量下降未经裁剪的上下文全量传递上下文卡片按需供给,控制长度循环委派子代理不断创建新子代理缺少深度限制与禁止递归规则限制最大深度,禁止子代理创建子代理这张表现在还贴在我工作台旁边,每次设计新的 Agent 协作方案时,我都会照着这三行检查一遍,踩过的坑就不再踩第二次。4. 目前验证过最稳的协作范式:扁平化委派 + 结构化任务单 + 校验环踩完坑之后,我重构方案时就盯着三条原则:层级要扁、任务单要结构、结果要验证。三件事缺一不可。4.1 扁平化:最多两层,禁止往下再建目前我所有在跑的任务,都遵守这条硬规矩:主 Agent 可以直接管理多个子代理,但子代理不允许自行创建子代理。如果某个子任务仍然太大,由主 Agent 负责把它二次拆分成更小的委派单,而不是让子代理自行去"外包"。这样做的好处有三个:信息损耗可控:主 Agent 到子代理只有一跳,任务目标不会经过多轮转述;排查简单:出了问题只需要检查主 Agent 和对应子代理之间的单次通讯,不用追一整条委派链;成本可控:不会出现无法预测的递归扩张。如果业务场景真的需要多级分解,比如主任务极其复杂,那也应该由编排层代码去分步调度,而不是靠 Agent 自主递归。代码层做分解和 Agent 自主递归是两回事,前者每一步都可控,后者随时可能失控。这句话是我用真金白银买来的经验。4.2 任务单与上下文卡片的完整示例下面是一个我在用的任务单模板,去掉了具体业务信息,保留通用结构:{ "subtask_id": "subtask_004", "objective": "根据 docs/contract_2023.md,生成 5 条面向用户的问答条目", "context_card": { "background": "这批问答条目用于某企业内部知识库,用户为非技术岗位", "local_facts": [ "contract_2023.md 第 3 节是付款条款", "第 5 节是违约责任", "不要引入合同之外的价格信息" ], "excluded": [ "不需要解读 2022 年旧合同" ] }, "constraints": [ "每条回答不超过 120 字", "必须引用原文中的具体条款编号" ], "output_schema": { "type": "array", "items": { "question": "string", "answer": "string", "clause_ref": "string" } }, "acceptance_criteria": [ "条目数量不得少于 5 条", "每条 answer 必须包含 clause_ref" ], "max_retries": 2, "forbidden_actions": [ "禁止创建任何新的子代理来处理本任务" ] }注意最后那个字段:forbidden_actions。它专门用来阻断"子代理创建子代理"的路径。别小看这一行,它能在运行日志里给你一条明确的判断依据——一旦出现违反 forbidden_actions 的操作,直接判定任务失败,不给模型狡辩的机会。4.3 校验环:反馈要带着证据校验环节的代码思路很简单,但比代码本身更重要的是"错误信息要具体":def validate_result(subtask, result): errors = [] # 1. 结构校验 if not isinstance(result, list): errors.append("输出不是数组,和 output_schema 不一致") # 2. 数量校验 if len(result) < subtask["acceptance_criteria"][0]: errors.append("条目数量不足") # 3. 关键字段校验 for item in result: if not item.get("clause_ref"): errors.append("存在缺少 clause_ref 的条目") return errors def dispatch_with_retry(subtask): for attempt in range(subtask["max_retries"]): result = call_subagent(subtask) errors = validate_result(subtask, result) if not errors: return result # 把具体的错误反馈写回任务单 subtask["feedback"] = "; ".join(errors) raise TaskFailed(subtask["subtask_id"])这里的核心模式是:errors必须带着具体原因,重新委派时把这些原因写进新的任务单。模型在收到"3 月和 4 月成交额缺失,5 月金额未保留两位小数"这类反馈后,修正成功率会明显高于收到"结果不合格请重试"。因为前者指出了明确的修正方向,后者只是告诉它"你又错了"。4.4 为什么这套范式有效用大白话讲,这套范式本质上是把 Agent 协作从"人和人聊天"降维成"接口调用"。人聊天的时候可以靠默契补全信息,但模型没有跨会话的默契,每一次调用都是全新的人。所以协作协议必须把目标、上下文、边界、验收标准都显式写清楚。这也是我一直跟身边人强调的:不要追求"Agent 之间像同事一样自然对话",要追求"每次委派都像一张填写完整的工单"。自然对话的容错率很低,你预期对方听懂了你没说出口的意思,但它根本没这个能力;工单式协作则几乎每一步都可检查、可回滚、可重试。稳定,比"看起来智能"重要得多。5. 工程化收尾:成本、并发、可观测性一个都不能少协作协议定了,最后是工程层面的几个细节。它们不直接决定"能不能跑通",但决定了"能不能长期稳定运行"。很多项目前期 Demo 跑得很漂亮,一上量就崩,基本都是栽在这几个细节上。5.1 成本控制的三道闸门第一道是上下文闸门:每个子代理收到的上下文卡片必须控制在 500 token 以内,更长的材料走文件引用,不直接复制全文。这能从源头压住大部分 token 浪费。最初我这道闸门形同虚设,每次看到 token 账单都很心虚。第二道是输出闸门:任务单中的output_schema只允许子代理输出必要字段。比如整理问答条目时,只返回 question、answer、clause_ref,不需要它把整段源文档再抄一遍。这能避免"子代理把原文重复返工"这种隐蔽开销,这类开销不加日志统计时根本发现不了。第三道是重试闸门:重试次数必须写死在任务单里,一般 1 到 2 次就够。超过上限就记录失败并交给人工审查,不要无限重试。无限重试不仅烧钱,还会把错误结果一遍遍加强,最后可能给出一个看起来很完整、实则全是幻觉的答案。5.2 并发与超时:把失控概率压到最低并行跑多个子代理确实能提速,但要注意三点:限制最大并发数。我常用的上限是 5,具体取决于接口速率限制和上下文总预算。并发太高时,单个任务出问题会导致"群体性失败",排查起来极其麻烦。每个子代理必须有自己的超时时间。整体超时和子代理超时要分开设置,这样失败时才知道是哪一环超时。如果只设一个全局超时,你只能看到一个笼统的"超时",定位不到具体是哪个子代理卡住了。递归限制落实到代码层。至少要加一个max_depth参数,默认值为 1 或者 2,而不是默认无限。很多框架默认不限制递归深度,这是生产事故的高发区。5.3 可观测性:日志里要有"委派单"和"校验结果"跑了几十次任务之后,我最大的感触是:Agent 协作出问题,最怕的不是报错,而是不报错但结果不对。所以日志里至少要有三样东西:每次委派的任务单完整内容,方便事后复盘"它到底接到了什么指令";子代理返回的原始输出以及校验结果,校验失败时能看到具体的 error 列表;每次调用的 token 消耗和耗时,方便做成本归因。我在实际项目中会给每次委派生成一个运行 ID,把任务单、输出、校验结果、token 消耗绑定存起来。后续排查某条知识条目质量差时,直接按 ID 查记录——当时给子代理传了什么、它答了什么、哪一步验证没过,一目了然。没有这套日志体系时,排查一次异常往往要翻半天聊天记录,效率极低。5.4 一个值得保留的小习惯最后分享一个小习惯:在任务单末尾增加一个字段,内容大概是"如果本任务确实无法完成,请直接说明阻碍你的具体原因,不要尝试自行扩大任务范围"。这个字段看起来增加了自由度,实际反而减少了模型的盲目发挥。模型在承认"我做不了"这件事上,通常比"我必须做出来点什么"更诚实。而诚实的失败永远比虚假的成功好修——至少你知道问题出在哪,而不是面对一份看似完成、实则全错的垃圾输出,还得逐条去核对。