子智能体该不该并回主循环:一份架构取舍清单
原文:Anthropic Blog - 《Building agents: When to fold subagents into your main agent loop》(https://claude.com/blog/building-agents-when-to-fold-subagents-into-your-main-agent-loop)
一、先把三种架构摆在同一张桌上
Anthropic Applied AI 团队在 10 月 22 日发了这篇文章,作者是 Diane Sarkis。它比较的不是模型,而是三种把同一个业务装进 Agent 的方式:
| 架构 | 长什么样 | 典型症状 |
|---|---|---|
| 一提示全包 | 一个超长系统提示,把流程、规则、口径全写进去 | 规则互相打架,改一句话影响三处 |
| orchestrator + 子智能体 | 一个路由器 + 规划器 + 若干领域子智能体 | 交接丢细节,链路变长,延迟上去了 |
| 单 Agent + skills | 一个模型、一个上下文窗口,能力靠 skills 与工具渐进加载 | 需要管好系统提示与上下文,靠纪律 |
原文的结论很直接:在多个企业部署的横向对比里,单 Agent 带 skills 在质量上常常优于另外两种,而且往往每任务的成本与延迟都更低。有一个客户拿自己的多 Agent harness 和单循环做了对照,后者的运行时间大约快 2 倍。
也就是说,过去两年我们被教的那套"先分层、再拆专家"的做法,在这个模型代际上开始反向走了。有两个真实观察支撑这个转向:
一是某公司的面向客户 Agent 里塞了十几个专家子智能体,结果品牌口径难以保持一致,延迟也把体验拖坏了。二是不少企业按团队或问题类型一一切分子智能体,最后架构变成了组织架构图的复印件------这就是 Conway 定律在 Agent 上的复现。
二、什么情况下该保留子智能体
这篇文章没有走向"全合并"的极端。它给了三条明确的保留条件,我按可直接落地的说法重写一遍:
- 单循环因为上下文膨胀出现了可测量的性能下降。注意是"可测量",不是"感觉有点长"。
- 多个任务需要并行,并且各自需要一份用完即弃的干净上下文窗口。
- 单 Agent 已经选不准工具,或者系统提示里出现了互相冲突的指令。
这三条的共同点是:它们是症状,不是偏好。你如果只是因为"架构图上分专业更清楚"而拆,那属于组织需求,不是技术需求------而这类需求完全可以用 skills 承接,不必付出子智能体那层开销。
三、合并顺序:按共享上下文,不按问题类型
这是我认为全文最值得抄下来的一条工程法则。拆分维度选错了,后面怎么调都是徒劳。
按上下文切分是有效的:给每个子智能体一块几乎不需要系统其余部分的工作,让它交回一个紧凑结果。独立的调研路径、接口干净的独立组件、黑盒验证,都属于这一类。
按问题类型切分是有害的:因为问题类型之间天然共享上下文,一旦切开,信息就得靠交接来回传递。原文用的词是传话游戏(telephone game),每一次交接都掉一层保真度。
落到合并动作上,有四条可操作的判据:
- 从使用最频繁的子智能体开始折,收益最快兑现。
- 相互串联输出、访问同一份数据、经常被一起调用的专家,优先合并。
- 如果生产流量里很大一部分是"A 专家紧接着 B 专家",那这就是一对紧耦合,应该并成一个领域 Agent。
- 一次只折一个,折完用评测对比,分数变差就退回去。
最后那条是整篇文章的方法论核心,比任何架构图都重要。
四、单循环到底该长什么样
原文给了一份设计规格,可以直接照着改代码。它描述的是一个模型、一个上下文窗口,跑开放式工具循环,而所有硬约束都由外层 harness 承担:
- 每一轮,模型发一批工具调用;harness 并行执行,把原始结果回给同一个模型。
- 模型看着结果决定下一步:再发一批、向用户提问、或者给答案。
- 循环在模型给出答案或撞到预算时结束。答案之后可以再跑一次目标检查或验证器。
- 模型在第一轮就把计划写进上下文,随结果到达而修订计划。
第 4 条是和 orchestrator 架构最本质的差别:写计划的那个模型,同时也是执行计划的模型,所以它可以在轮次中途改计划。而 orchestrator 模式下,计划在路由器那里定死,子智能体只能在自己那格里执行。
配套的上下文策略有两条,都是为了把"拆出去的理由"直接消掉:
- 上下文靠 skills 渐进披露。Agent 先看到 skill 的简短描述,需要时才完整加载。
- 工具靠 tool search 渐进披露。同理,先看描述,用时再载入。
原文提了一个数字:为更先进的模型重写 Claude Code 的系统提示时,缩减幅度超过 80%,其中就包括与 skills、工具渐进披露相关的裁剪。它给的第三个理由是模型本身变强了------从 Opus 4.5 起,模型在单循环里能管的事情远超大多数团队的预期,而且会边做边播报进度、自己回头检查,这让一部分 harness 组件变得多余。
工具层面还有几个硬数字和硬规则:
| 项目 | 建议 | 原因 |
|---|---|---|
| 系统提示内工具定义数 | 超过 30 到 50 个后开始退化 | 工具选择准确率下降 |
| 起步规模 | 一个清晰系统提示 + 4 到 5 个瘦数据工具 + 硬输出护栏 | 覆盖多数用例 |
| 分组方式 | 按用户意图分组,不是一个 API 一个工具 | 减少工具数量与歧义 |
| 权限 | 每个工具自行校验调用者权限,返回模型能读懂的普通拒绝 | 一个循环即可服务不同权限的用户 |
最后一条很实用:它意味着你不需要为每个权限档位单独做一个 Agent。
至于什么时候仍然需要保留子智能体,原文的边界划得很窄------只有子智能体必须直接和用户对话时,才值得做完整交接。其余场景都可以拆成"skill + 工具"塞回主循环。
五、迁移时最容易漏的一步:验证
这部分是我最想单独拎出来讲的,因为它描述的失败太真实了。
原文举了一个支持类 Agent 的例子:把 A 子智能体折进 B 之后,延迟确实降了,但一条原本在跑的流程断了------主 Agent 自己握着订单查询工具,直接回答了问题,没有再去问发货专家要那个日期。也就是说,能力合并了,但"必须去问另一个角色"这个隐性流程约束消失了。
更麻烦的是频率:这种断裂在两次运行里只出现一次。你跑一遍全绿,就以为没事了。
所以评测要按这几条搭:
- 结构上,中央团队维护一套端到端评测集,每个 skill 由所属团队带一套小评测一起发。
- 规模上,10 个 golden example 就足够起步,不必等评测体系建完。
- 指标上分阶段:还有 orchestrator 时看路由准确率(选对了子智能体没有);折进去之后改看是否加载了对的 skill、调了对的工具。
- 每条生产上会一起跑的 Agent 链路、每条不许破的规则,都要有对应用例,并且每条用例至少跑多次。
- 对比时不要只看最终分数,要看哪些任务发生了翻转。
- 环境难复现时,给关键事件打时间戳,之后按时间戳重放成定向评测。
这里有个我自己踩过的坑值得补一句:只看总分时,"A 变好、B 变差、总分不变"这种情况是完全隐形的,而它恰恰是合并最典型的中间状态。
六、一段折叠前后的对比骨架
下面这段是我按文章的方法论自拟的示意代码,不是官方实现,也不依赖任何 SDK,目的是把"跑多次 + 看翻转"这两件事固化下来。
python
# fold_compare.py ------ 折叠前后对比,重点不是平均分,是"跑多次"和"看翻转"
import statistics
import time
def run_suite(agent, cases, repeats=3):
"""agent 需要有 run(case) 方法,返回带 passed 字段的字典。
返回 (runs, latencies):runs 是每次运行的 {case_id: bool},latencies 是每轮延迟中位数。"""
runs, latencies = [], []
for _ in range(repeats):
one_run, lats = {}, []
for case in cases:
t0 = time.perf_counter()
out = agent.run(case)
lats.append(time.perf_counter() - t0)
one_run[case["id"]] = bool(out.get("passed"))
runs.append(one_run)
latencies.append(statistics.median(lats))
return runs, latencies
def unstable(runs):
"""同一用例在不同运行里结果不一致 → 说明评测本身还不稳,先别拿它下结论。"""
ids = runs[0].keys()
return {i for i in ids if len({r[i] for r in runs}) > 1}
def flipped(before, after):
"""只比通过/失败的反转,不看总分。before、after 各取一次运行的 {case_id: bool}。"""
won = {i for i in before if not before[i] and after[i]}
lost = {i for i in before if before[i] and not after[i]}
return won, lost
if __name__ == "__main__":
# 伪代码式调用:先 orchestrator 版,再折掉一个子智能体的单循环版
base_runs, base_lat = run_suite(orchestrator_agent, CASES, repeats=3)
fold_runs, fold_lat = run_suite(single_loop_agent, CASES, repeats=3)
print("抖动用例:", unstable(base_runs) | unstable(fold_runs))
won, lost = flipped(base_runs[0], fold_runs[0])
print(f"折叠后变好 {len(won)} 条,变差 {len(lost)} 条")
print(f"延迟中位数 {statistics.median(base_lat):.2f}s → {statistics.median(fold_lat):.2f}s")
参数与返回值:run_suite 收三个参数------被测 Agent、用例列表、重复次数,返回两个列表,runs 里存的是每轮每题的布尔结果,latencies 里存的是每轮延迟中位数。它的返回结构刻意做成"按轮存"而不是"直接给个总分",因为后面两步都要用到逐题逐轮的原始数据。
unstable 只吃 runs,返回在多次运行中结果不一致的用例集合。这个函数应该在比较之前先跑一遍:如果抖动集合很大,说明你的评测是噪声,此时看翻转毫无意义。
flipped 收两次运行的逐题结果,返回变好、变差两个集合。调用链是:准备用例 → 跑 baseline → 折叠一个子智能体 → 重跑 → 先排除抖动用例 → 比翻转 → 只有净收益为正才保留这次改动。
这段代码刻意没做的一件事是算总分。因为一旦它吐出总分,人就会只看总分。
收尾
这篇文章的价值不在于"子智能体过时了"这种断言------原文自己留了三条保留条件,也明说子智能体在研究前沿仍有位置。它真正的贡献是把架构争论变成了可执行的迁移流程:测量、一次只改一个、留下扛得住的那个。
如果你的 Agent 现在有路由器、规划器和一堆领域子智能体,我建议的下一步不是重构,而是先花半天写 10 条 golden example,跑三遍,看看有多少用例是抖动的。抖动大的话,先修评测,再谈架构。
文中数据与判断来自 Anthropic 10 月 22 日的这篇博客,涉及具体模型能力与工具阈值的部分,以官方文档为准。