大多数尝试构建多步骤代理的人最终得到的都是一条直线。第一步、第二步、第三步——每一步都会礼貌地等待上一步完成后才开始。
很多线性流程里,一半左右的步骤并不依赖前一步;具体比例要从自己的任务图里计算,不能先写死。
它们不路由。它们不分支。它们不并行处理。它们只排队——一个头,一个上下文,一次处理一件事,直到窗口填满,代理忘记自己在做什么。
先拿一条真实的线性流程做练习。只画数据确实发生传递的边,其余步骤再判断能否并行。
下面用 14 步把线性代理改造成任务图:展开可以并行的工作,给结果加验证节点,并让失败回到明确位置。
最容易被忽略的是工作的形状:什么必须先做,什么可以同时运行,什么要等所有分支汇合。
但是工作的形状本身——什么在前,什么可以同时运行,什么必须等待其他所有事情完成——这种形状就是一个图。节点负责思考。边负责传递结果。
Claude Code 直接交付了构建这些图表的工具:动态工作流。
克劳德编写一个纯 JavaScript 的编排脚本,然后生成一个协调的子代理队列来执行它——而协调本身不消耗模型令牌,因为它是代码,而不是对话。
01 节点是任务。边是流动的内容。
一个图恰好有两样东西,把它们弄清楚可以解决大多数困惑。节点是一个工作单元——一个代理,一个有限的任务,一次输入和一次输出。
边是依赖关系:它表示这个节点的输出是另一个节点的输入。仅此而已。
错误在于将「然后」当作一个边。「总结文件然后告诉我天气」这两个动作之间没有边——天气信息并不依赖于总结。
那是两个不相连的节点,而一个线性脚本却无谓地将它们连接起来。只有当数据真的在其间移动时,边才存在。
学会去问,对于你代理中的每个「然后」:下一步是否读取了上一步的输出?如果没有,则没有边,等待就是浪费。
02 你的线性脚本是一个退化图
当你把一个代理写成「先做A,然后做B,然后做C,然后做D」时,你画出了一个图——一个单一的、没有分支的链。每个节点正好有一条入边和一条出边。
它运行正确。但运行缓慢且脆弱,因为链条没有冗余:如果 C 停滞,D 永远不会发生,而 A 的工作被困在上游,无处可去。
图工程的第一个真正技能是重新绘制链条。拿你的线性代理,对于每一条箭头,问一下步骤 1 的问题。
大多数链条有两三个不传递数据的箭头——它们只是你碰巧输入东西的顺序。
切断那些箭头,链条就会塌缩成更宽的结构:几个可以同时运行的独立节点,供给一个需要它们全部的单一节点。
03 给每个节点一个契约
一个你无法推理的节点是一个你无法并行化的节点。解决方法是一个契约:有界输入,有界输出,恰好一个任务。
输入是节点读取的内容——明确传入的,从不假定来自共享窗口。输出是定义好的形状,最好经过验证,以便下一个节点可以直接使用,而无需猜测。
在工作流中,这个合同通过一个模式来强制执行。当你向 Claude 传递带有 JSON 模式的 agent() 调用时,Claude 生成的子代理被迫返回经过验证的结构化数据——验证发生在工具调用层,因此在不匹配时 Claude 会重试,而不是交给你必须解析和祈祷的自由文本。
这是Claude可以连接到图中的节点与只有在人工读取其输出时才能工作的节点之间的区别。
04 将边视为数据契约
一个边不仅仅是「B在A之后」。它是关于数据流动的承诺:A产生这种形状,而B被构建用来消耗这种形状。当你用数据来命名边——而不是顺序时——有两件事情会变得更容易。
你可以立即看到边是否真实(数据是否真的在移动?),并且只要形状保持不变,你可以在不破坏图的情况下交换任一端的节点。
在实际操作中,边缘部分存在于纯 JavaScript 中。扇出与合成之间的 reduce 步骤——展平、去重、过滤——只是对节点返回的形状进行操作的代码。
不需要代理。图思维的一个安静的胜利:人们在模型令牌上浪费的大量内容实际上只是一个边,而边是免费的。
05 . 使用 parallel() 并行扩展
这是能支付一切的举动。当你有N个独立节点——N个需要检查的源,N个需要审查的文件,N条需要审核的路线——你不会把它们串联起来。
你告诉Claude让它们分开运行并立即执行。在一个parallel()的工作流中:Claude接受一个thunks数组,并为每个thunk生成一个子代理,所有子代理同时执行,然后将结果数组返回给你。
有两个细节使它非常稳健。先parallel() 是一个屏障——它会等待它前面的每个 thunk 完成后才返回,因此下一阶段可以看到完整的集合。接着,一个抛出异常的 thunk 会解析为 null,而不是拒绝整个批次,因此一个不稳定的代理无法导致整个运行失败。
始终对结果使用 .filter(Boolean)。并发量会根据你的核心数量限制,多余的会排队,所以你可以传入一百个 thunks,它们都会完成——每次只处理少量。
扇出存在于 Claude 编写的代码中,而不是在模型对话中。Claude 本身的上下文从不同时保留九个来源——每个子代理都有自己的上下文,只有最终答案会返回。
这就是让Claude能够将一个工作流程扩展到数十个或数百个子代理而不会让会话不堪重负的原因。编排层不消耗任何令牌,因为它不是Claude的另一次思考回合。
06 。在屏障处汇聚
只有当某物收集它时,扇出才有用。扇入是边缘汇聚的节点——一个代理(或一段代码)在这里一次性看到所有上游结果,并执行需要整个集合的操作:跨来源去重、按影响排名、如果总结果为空则提前退出。这是障碍才值得其实际时钟开销的唯一地方。
保持图表快速的规则:只有当某个阶段真正需要所有先前的结果一起使用时,才使用屏障。在所有来源中去重?使用屏障——正确。
只是展平一个列表?那是一个极端情况,直接在行内处理吧。味道测试非常简单而严厉:如果你写了 parallel → transform → parallel,并且中间的 transform 没有跨项依赖,你本应该使用管道并完全跳过这个屏障。
07 钻石:分裂 → 工作 → 合并
把分出(fan-out)和汇入(fan-in)放在一起,你就得到了每个严肃的代理图的主力拓扑结构:钻石。
一个节点拆分任务,多个节点并行工作,一个节点合并。这是市场扫描、依赖审计、代码审查、研究报告背后的模式——更换来源和提示,同样的框架也能适应。
标准形式有一个值得记住的名字:展开 → 缩减 → 综合。展开以收集广度,用简单代码缩减以压缩它,最后用一个代理综合以写出答案。
一旦你看到了钻石,你就会停止问「我怎样让我的代理多做几步」,而开始问「分支在哪里,合并在哪里」——这才是实际上可以扩展的问题。
08 在运行时用条件来路由边
并不是每个图都是固定的。有时选择哪条边取决于节点发现了什么。路由节点检查结果并决定下游路径的触发——先分类票据,然后分支到正确的处理程序;检查差异大小,然后要么进行快速审核,要么启动完整审计。
在工作流中,这只是节点验证输出上的 JavaScript if 或 switch,因为控制流存在于代码中。
这是决定论成为特性而非限制的地方。路由器的决策可以由 Claude 提供支持(子代理进行分类),但路由是 Claude 编写的代码 —— 因此对于相同的分类,它每次的运行方式都是相同的。
你在节点上得到Claude的判断,在边上得到脚本的可靠性。不会出现「Claude决定跳过审计」的意外情况——因为跳过必须写入图中,而它没有。
09 在边上放一个验证器
图表的真正杠杆不在于更多的代理,而在于你可以围绕它们构建的结构以产生信心。
一个验证节点位于结果下游之前,它的唯一任务是尝试终止该发现。如果它存活,它就会通过。如果不行,它永远无法到达答案。
有三种模式值得掌握。
-
对抗性验证:对于每个发现,生成 N 个独立的怀疑者,提示他们去反驳;仅在大多数存活时保留该发现。
-
多角度验证:为每个验证者提供不同的视角——正确性、安全性、能否复现——因为多样性可以发现 N 次相同检查永远无法发现的失败模式。
-
法官小组:从不同角度生成N次尝试,用平行评审打分,从获胜者中综合,同时汲取亚军的最佳部分。
这正是让一个真正的团队在循环中加入对抗性代码审查的情况下移植 Bun 运行时的模式。
10 隔离节点,这样一次故障不能破坏整个图
在链式结构中,故障会级联——C 死了,D 根本不会运行,整个系统停止。在图结构中,故障应仅限于其节点。
这在某种程度上已经部分正确:在 parallel() 内抛出错误的 thunk 会解析为 null,因此八个好的 agent 仍然会返回,而一个坏的 agent 会掉出。你的 .filter(Boolean) 就是这个约束。
设计每个 fan-in 时要容忍缺失的输入,而不是假设输入是完整的。
更微妙的失败是节点互相干扰。当代理并行写入文件时,它们可能会发生冲突。
解决方法是隔离:「工作树」 —— 每个代理在自己的 Git 工作树中运行,在沙箱中完成工作,并干净地合并。
只有在节点实际上并行写入时才去使用它。它是为需要它的特定拓扑设计的安全带,而不是每次运行的默认负担。
11 添加一个循环——但要让它收敛
有时直到你身处其中,才会知道工作的规模:未知大小的发现,一个错误排查,当找到一个错误时会发现另外三个。这需要一个周期——一个受控的回到先前节点的回退。
危险是显而易见的:一个不收敛的循环就是一个无限循环,它会不断生成代理,直到你的预算耗尽。
收敛的模式是循环直到干涸:不断生成查找器,直到连续 K 轮没有发现新内容,然后停止。决定成败的一个细节——几乎每个人第一次都会犯的错误——是你去重的对象。
对所有已见内容进行去重,而不仅仅是对已确认的结果进行去重。否则,被拒绝的发现每一轮都会重新出现,循环永远不会结束,你就建造了一台永远为重新发现相同死胡同而付费的机器。
12 在各节点之间对模型进行分层
并非每个节点都需要使用你最好的模型。图形以单个智能体永远无法做到的方式让这一点显而易见:有些节点是有限且重复的(提取此字段,分类此工单),而有些则承载真正的判断(综合报告,裁定发现)。
在更便宜的模型上运行枯燥的节点,把你昂贵的代币用在真正需要判断的地方。
在工作流中,每个 Claude 生成的子代理都会继承你的会话模型,除非脚本重写它——所以默认情况下,一次大的运行完全按照你的会话层计费。单个 agent() 调用中的模型选项会告诉 Claude 仅将该节点路由到其他地方。
在进行大规模运行之前检查 /model,然后让 Claude 将分支的重复节点引导到成本更低的模型,同时保持合并节点。这就是将一个消耗大量 token 的图从昂贵变为经济而不改变其结构的杠杆。
13 . 拓扑就是你的成本和延迟
图形的形状不是装饰性的——它是墙钟时间上最大的杠杆。让所有人都绊倒的选择是:parallel() 与 pipeline()。parallel() 屏障会让所有节点在下一阶段开始之前等待最慢的节点。
pipeline() 会让每个项目独立地通过所有阶段,没有障碍——项目 A 可以处于阶段 3 而项目 B 仍然在阶段 1 。快速的项目会提前完成,而不是在慢的项目后面等待。
默认使用 pipeline()。只有当某个阶段确实需要一次性获取所有前置结果时才使用 barrier —— 比如跨集合去重、对总数进行提前退出、或与「其他发现」进行比较的提示。「代码更干净」和「阶段感觉独立」不是理由; barrier 的延迟是真实存在的、可测量的、浪费的时间。独立并不等于同步。
14 让克劳德绘制图表——自我路由
最后一步是停止手工绘制那些你无法提前规划的工作的图表。
通过动态工作流,你描述目标,Claude 会自己编写编排脚本——分解任务、选择分流方式、生成协调的子代理群,并综合结果。你得到的是一个针对本次运行量身定制的图,而不是你希望适合的固定图。
有三种进入方式。在你的提示中说出「workflow」这个词,Claude 就会为该任务生成一个工作流。运行已保存或捆绑的工作流——/deep-research 是一个在生产环境中真正运行的图:范围 → 并行搜索 → 获取 → 对抗性验证 → 合成,这正是本课程的骨架。
或者开启 ultracode,Claude 会为会话中的每个重要任务规划工作流程。当一次运行效果良好时,按下 s 键将其脚本保存到 .claude/workflows/ —— 采用版本控制,可以根据名称重新运行,任何克隆该仓库的人都可以启动这个图。
本周与Claude一起构建的六个图表
-
对每条路径进行安全扫描。Claude为每个路径文件生成一个子代理,每个子代理都在寻找缺失的授权检查,然后验证器会确认每个发现,才会进入报告。没有任何单一上下文可以涵盖的广度。
-
引用了带有/deep-research的报告。一个已经在Claude Code中提供的图表。Claude将你的问题分解为不同角度,进行并行搜索,去重来源,然后通过三票怀疑者对每个声明进行对抗性验证,再进行撰写。
-
按文件逐个移植模块。Bun 的上限根据你的仓库进行缩放。Claude 将翻译分散到各个文件,运行测试套件作为每个文件的门槛,并将失败情况反馈回去——对抗性审查捕捉到单次通过可能会出错的部分。
-
对差异的对抗性审查。Claude 针对不同大小的变更采取不同策略:小的变更进行一次快速评审,大的变更会触发一次完整的并行审核,由针对不同视角(正确性、安全性、性能)的审阅者进行评审,然后由一个裁判小组进行综合。
-
按计划进行生态系统扫描。保存一次,然后可以永远重新运行。Claude 并行检查多个来源——发布、博客、讨论——按影响力在一个门槛上排名,并编写摘要。在 .claude/workflows/ 中进行版本控制,可按名称启动。
-
发现未知的规模。你不知道有多少错误存在。Claude 并行运行查找器,对每个新发现与已知的一切进行去重,验证存活的对象,并持续循环,直到连续两轮没有发现新内容——然后停止。
结论:
提词员提出一个问题。建筑师画一张图。
线性代理从来不是上限——它只是第一个形状,每个人都会去使用它,因为它符合我们的打字方式。一行,一个头,一次一件事。
一旦你能看到节点和边,你就会停止要求代理做更多的事情,而是开始要求图来做更广的事情:在工作独立的地方扩展,在信心重要的地方控制边,在判断不重要的地方分层模型。
大多数人会排队进行步骤。那些学会绘制图表的人将会运营一个舰队——而永远不会注意到其他人被困在下面的天花板。
把路线图落到一个最小可验证图
先不要搭一个「全能多代理平台」。挑一项存在真实分支的工作:研究可以并行,写作要等待资料,事实核查失败后需要返回修订,最终发布前必须人工确认。
路由工作流适合先分类输入,再把任务发送给对应处理路径。 独立子任务可以并行,同一输出也可以交给不同检查器从多个角度复核。 当子任务无法预先枚举时,再考虑 orchestrator-worker,让编排器动态拆解并汇总结果。
无论采用哪一种结构,节点都要有明确输入、输出和失败状态。Claude Code 的代理循环本身仍是收集上下文、采取行动、验证结果;图只是把多个这样的循环按依赖关系组织起来。 给每个节点配置测试、截图或期望输出,并在复杂任务中先探索、计划,再实现和验证。
最小验收清单:
- 每条边都能解释为什么存在;
- 并行节点之间没有隐藏的写入冲突;
- 重试有预算和停止条件;
- 状态可以持久化并支持从检查点恢复;
- 高风险动作前保留人工批准;
- 失败时能指出具体节点和证据,而不是只返回「代理失败」。