多步 AI Agent 为什么执行起来像在排队?Claude 动态工作流中的图 vs 链
多步 AI Agent 为什么执行起来像在排队?Claude 动态工作流中的图 vs 链
你正在用 Claude 构建一个复杂的多文件代码审查系统:提示里写着“先读取所有路由文件,总结差异,然后检查缺失的权限控制,然后生成最终报告”。执行时,Agent 却像单线程队列一样一步步等,前一步的总结还没产出,后面的安全检查就空转;上下文窗口越来越满,早期发现的细节开始被遗忘;某个子步骤抛错,整个流程就直接中断。
这不是个例,而是大多数人构建多步 Agent 的默认模式——一条直线。
我起初也以为,只要把任务拆得更细、步骤写得更清晰,Agent 就能可靠地处理复杂工作。后来真正理解 Claude Code 动态工作流的底层机制后才发现:问题的核心从来不是步骤数量,而是执行拓扑的形状。一条“然后”链和一个真正的图,差的不是一点点速度,而是整个系统的并行能力、容错能力和可验证性。
节点与边的真正定义:工作单元与数据流动的承诺
一个节点就是一个有界的工作单元:明确输入、明确输出、单一职责。它可以是一个子 Agent,也可以是纯代码处理。边则更严格——它只在数据真正从上游流向下游时才存在。
“总结文件差异,然后查询天气”里,“然后”并不是边。因为天气查询并不消费差异总结的输出。这两个节点其实是独立的,只是被人为按顺序串起来了。真正的边必须回答一个问题:下游节点是否需要上游的输出结果?如果不需要,这条等待就是纯浪费。
类比一下,这就像工厂里的装配线。你以为“先组装引擎,然后安装车轮”必须顺序执行,但如果引擎组装和车轮安装互不依赖,它们本可以同时开工。只有当车轮需要用到引擎的安装孔位数据时,才真正建立一条边。把“顺序”误认为“依赖”,就是线性 Agent 最大的隐形税。
你的线性脚本其实是退化的图
当你写下“做 A,然后 B,然后 C,然后 D”时,你已经在画图了——只不过画了一条没有分支、没有冗余的单链。每个节点只有一条入边和一条出边。
这样的链能跑通,但跑得慢且脆弱:C 卡住,D 就永远等不到;A 的工作成果被困在上游,无法被其他并行路径复用。把链重绘成图的第一步,就是对每一条“然后”箭头问同一个问题:它是否真的在传递数据?把那些不传递数据的箭头剪掉,链就会自然展开成更宽的结构——几个独立节点可以同时运行,最后只在真正需要汇总的节点处汇聚。
给每个节点一个清晰的契约
无法被独立推理的节点,就无法被安全地并行化。解决办法是给节点立一个契约:输入是什么形状、输出是什么形状、只做一件事。
在 Claude 动态工作流里,这个契约通过 JSON Schema 强制执行。当你用带 schema 的 agent() 调用时,Claude 生成的子 Agent 必须返回结构化数据,验证失败会自动重试,而不是把一堆自由文本扔给你让你手动解析。
这和“把输出扔给人类再读”完全是两回事。前者是可被图连接的节点,后者只是一个黑盒。
边是数据契约,而非执行顺序
把边命名为它实际传递的数据形状,而不是“第 3 步之后”,两件事会立刻变简单:你能一眼看出这条边是否真实存在,以及你可以在不破坏下游的前提下,随时替换上游或下游的节点实现。
更重要的是,大量原本需要 Agent 思考的工作,其实只是边上的数据处理——去重、过滤、扁平化。这些操作用纯 JavaScript 完成,零 token 消耗。这也是图思维带来的安静红利:很多人烧掉的模型 token,其实本该由编排代码免费承担。
扇出并行(parallel)是真正能回本的动作
当你有 N 个独立来源需要检查、N 个文件需要审查、N 条路由需要审计时,不要把它们串成链。直接让 Claude 用 parallel() 把它们扇出,同时运行。
这个 barrier 会等待所有并行任务完成后再返回结果,同时单个任务失败只会返回 null,不会拖垮整个批次。之后只要 .filter(Boolean) 即可。并发上限受限于你的核心数,多余任务会排队,但整体吞吐量远超线性等待。
关键在于:并行执行的上下文是分散在各个子 Agent 里的,主会话的上下文永远不会同时塞进九个来源。这让 Claude 能轻松协调几十甚至上百个子 Agent,而不会因为上下文爆炸而失忆。
只有真正需要全局视角时,才用扇入屏障
扇出之后必须有扇入,但屏障(barrier)是有成本的。只有当后续阶段确实需要看到所有上游结果的完整集合时,才应该用它——比如跨来源去重、按影响排序、或根据整体情况提前退出。
如果中间只是简单扁平化一个列表,那就直接用代码在边上处理,不要强行加一个 barrier。写出 parallel → transform → parallel 的模式时,要问自己:这个 transform 是否真的有跨项目的依赖?如果没有,就该改成 pipeline,让快任务早点完成,而不是让所有任务都等最慢的那个。
钻石拓扑:分发 → 并行工作 → 合并
把扇出和扇入组合起来,就得到了几乎所有严肃 Agent 图的核心骨架——钻石。
一个节点负责拆分任务(Split),多个节点并行执行具体工作(Work),一个节点负责合并与合成(Merge)。市场扫描、依赖审计、代码审查、研究报告……几乎所有需要广度 + 深度的工作,都能套用这个骨架。
规范形式可以记住为:扇出收集广度 → 纯代码 Reduce 压缩 → 最终 Agent Synthesize 写答案。一旦你看到钻石形状,就不会再问“怎么让 Agent 多做几步”,而是问“在哪里拆分?在哪里合并?”——这才是真正能 scale 的问题。
(建议的逻辑架构图,用 Mermaid 表示钻石拓扑):
运行时路由让图具备条件分支
不是所有图都是静态的。有时下游走哪条路,取决于上游节点发现了什么。路由节点读取结果后决定分支:票据分类后走不同处理路径;diff 规模小就快速审查,大就启动完整审计。
这个决策可以用子 Agent 做判断,但路由本身是 Claude 写的 JavaScript 代码——确定性执行,不会因为“Claude 今天心情好”就突然跳过审计。
边缘上的验证器:用怀疑杀死不可靠发现
图结构真正的杠杆不是“更多 Agent”,而是你在结构里能包裹的置信度机制。
验证器节点坐在边上,只做一件事:试图杀死上游的发现。如果它活下来了,就放行;杀不死,就不往下传。
常见模式包括:
- 对抗性验证:为每个发现 spawning 多个独立怀疑者,多数通过才保留;
- 视角多样验证:不同验证者分别从正确性、安全性、可复现性等角度攻击;
- 评委面板:多角度生成,再用并行评委打分,合成最佳结果。
这正是让真实团队把 Bun runtime 成功移植时,内置对抗性代码审查的原因。
隔离节点,让单点失败无法毒害全局
链式结构里,一个节点死掉,后续全军覆没。图结构则要求失败被严格隔离在自己的节点内。
parallel() 里抛错的 thunk 已经默认解析为 null;扇入阶段要设计成能容忍缺失输入;当节点需要并行写文件时,使用独立 git worktree 做沙箱隔离,避免碰撞。
收敛循环:直到干涸才停止
有些任务的规模事先未知(未知数量的 bug 扫描、持续发现新来源)。这需要受控的循环边。
危险在于不收敛的循环会无限消耗预算。收敛模式是 loop-until-dry:连续 K 轮没有新发现就停止。关键细节是去重对象必须是“所有已见过的发现”,而不是仅“已确认的结果”。否则被拒绝的发现会反复出现,循环永远干涸不了。
跨节点分层模型:把贵模型用在真正需要判断的地方
图结构让模型分层变得显而易见:重复性、边界清晰的节点(字段提取、票据分类)可以用廉价模型;需要综合判断的节点(最终合成、裁决发现)才用顶级模型。
在动态工作流里,单个 agent() 调用可以指定模型,Claude 会把该节点路由到对应层级,而不会让整个大规模运行都按会话默认的高价模型计费。
拓扑本身就是成本与延迟的杠杆
parallel() 的 barrier 会让所有后续阶段等待最慢节点;pipeline() 则让每个项目独立流经所有阶段,快项目早完成。
默认用 pipeline。只有当阶段真的需要看到完整上游集合时(跨集合去重、基于整体的提前退出),才用 barrier。“代码更干净”不是理由,barrier 带来的真实等待时间才是。
让 Claude 自己绘制图:动态工作流
最高阶的做法,是不再手动画图。对于无法提前规划的任务,直接描述目标,让 Claude 自己写编排脚本——它会分解任务、选择扇出、生成协调子 Agent 的舰队,并合成结果。
三种进入方式:提示里说“workflow”;直接运行已保存的 workflow(如 /deep-research 就是现成的 scope → 并行搜索 → 获取 → 对抗验证 → 合成);或开启 ultracode,让 Claude 为每个实质性任务自动规划 workflow。好的 workflow 可以保存到 .claude/workflows/,版本控制,按名称重新运行。
可立即落地的 6 个图结构场景
- 跨所有路由的安全扫描:每个路由文件一个子 Agent,并行 hunting 缺失权限,验证器再确认所有发现。
- 带引用的研究报告:使用内置
/deep-research图,并行多角度搜索、去重、对抗验证后再合成。 - 模块逐文件移植:并行翻译 + 测试门禁 + 失败循环反馈。
- Diff 的对抗性审查:根据 diff 大小路由到快速审查或完整并行审计 + 评委面板。
- 定时生态系统扫描:保存一次 workflow,永久重跑并行检查多源、按影响排序、生成摘要。
- 未知规模的发现任务:并行 finder + 全量去重 + 验证 + loop-until-dry,直到连续两轮无新发现。
真正决定上限的,从来不是 Agent 的“聪明程度”,而是它站立的形状
一个提示是一句话,一个循环是一个周期,而编排脚本才是 Agent 真正站立的地板。线性链只是大家最先抓到的形状,因为它和我们打字的习惯一致。
一旦你能看到节点和边,就不再追问“怎么让 Agent 多做几步”,而是开始问“哪里可以扇出?哪里需要验证?哪里该用廉价模型?”——这些问题才能真正让 Agent 跑出舰队,而不是永远卡在单线程队列里。
下一次你设计多步 Agent 时,先停下来画一张节点与边的草图,问问自己:这些“然后”里,有多少其实没有数据流动?把它们剪掉之后,结构会变成什么样子?
我是紫微AI,在做一个「人格操作系统(ZPF)」。后面会持续分享AI Agent和系统实验。感兴趣可以关注,我们下期见。
更多推荐


所有评论(0)