Glean 拾遗
日刊 /2026-07-24 / Agent图工程14步:从线性脚本到并行图谱

Agent图工程14步:从线性脚本到并行图谱

原文 x.com 收录 2026-07-24 06:00 阅读 21 min
AI 解读

绝大多数多步Agent被设计成单线程线性链,每个步骤依次等待,缺乏并行与分支,导致效率低下且易丢失上下文。本文提出基于图结构的Agent工程方法,使用Claude Code的dynamic workflows将Agent组织为有向无环图:节点是带合约的工作单元,边是数据流依赖。核心模式包括扇出并行(parallel())、扇入汇聚、运行时路由、验证器防线、循环收敛等。文章提供14个具体步骤,附带代码示例和拓扑图,展示如何用图结构替代线性脚本,实现可扩展、可控的Agent协作。目标读者是构建多步骤Agent的工程师,尤其是希望提升Agent并行度和可靠性的实践者。

原文 21 分钟
原文 x.com ↗
§ 1

Graph Engineering with Claude: 14-Step roadmap from 0 to graph architect (Full Course)

Graph Engineering with Claude: 14-Step roadmap from 0 to graph architect (Full Course)

§ 2

Most people who try to build a multi-step agent end up with a straight line. Step one, step two, step three - each waiting politely for the last to finish before it starts.

9/10 notice that half those steps never needed to wait at all.

They don’t route. They don’t branch. They don’t parallelize. They just queue - one head, one context, one thing at a time, until the window fills up and the agent forgets what it was doing.

Follow my Substack to get fresh AI alpha: movez.substack.com

This is the 14-step roadmap that turns that single-file line into a graph: one that fans out across a fleet, verifies its own findings, and converges on a result a lone agent could never hold.

大多数尝试构建多步骤代理的人最终都会得到一条直线。第一步、第二步、第三步——每一步都礼貌地等待上一步完成才能开始。

十分之九的人会发现一半的步骤根本不需要等待。

它们不路由、不分支、不并行化。它们只是排队——一个头、一个上下文、一次一件事,直到窗口填满,代理忘记了自己在做什么。

关注我的Substack获取最新AI阿尔法:movez.substack.com

这就是将单文件线变成图的14步路线图:一个在集群中扇出、验证自身发现、并汇聚成一个孤代理永远无法容纳的结果的图。

§ 3

§ 4

Here’s the shift nobody spells out. A prompt is a sentence. A loop is a cycle. A harness is the floor the agent stands on.

But the shape of the work itself - what runs before what, what can run at the same time, what has to wait for everything else - that shape is a graph. Nodes do the thinking. Edges carry the results.

Claude Code shipped the tooling to build these graphs directly: dynamic workflows.

Claude writes a plain JavaScript orchestration script, then spawns a coordinated fleet of subagents to execute it - and the coordination itself costs zero model tokens, because it’s code, not a conversation.

这是没人明说的转变。提示是一句话。循环是一个周期。框架是代理所站的地板。

但工作本身的形状——什么先运行,什么可以同时运行,什么必须等待其他一切——这个形状就是一张图。节点负责思考,边承载结果。

Claude Code直接提供了构建这些图的工具:动态工作流。

Claude编写一个纯JavaScript编排脚本,然后生成一个协调的子代理集群来执行它——而协调本身不消耗模型令牌,因为它是代码,而不是对话。

§ 5

01. Nodes are jobs. Edges are what flows.

A graph has exactly two things, and getting them straight fixes most of the confusion. A node is a unit of work - one agent, one bounded job, one input in and one output out.

An edge is a dependency: it says this node’s output feeds that node’s input. Nothing more.

The mistake is treating “and then” as an edge. “Summarize the file and then tell me the weather” has no edge between the two - the weather doesn’t consume the summary.

That’s two disconnected nodes that a linear script needlessly chains. The edge only exists when data actually moves across it.

Learn to ask, for every “and then” in your agent: does the next step read the last step’s output? If not, there is no edge, and the wait is wasted.

Draw it as boxes and arrows. A box is an agent() call. 
An arrow is a variable passed from one call’s return into another’s 
prompt. If you can’t draw the arrow - if no variable crosses - the two 
boxes are independent, and independence is the thing you’ll exploit 
for the rest of this course.

01. 节点是工作,边是数据流

图只有两样东西,搞清楚了就能解决大部分困惑。节点是工作单元——一个代理、一个界限明确的任务、一个输入和一个输出。

边是依赖关系:它表示这个节点的输出供给那个节点的输入。仅此而已。

常见的错误是把“然后”当作边。“总结文件然后告诉我天气”这两个之间没有边——天气不消耗总结。

这是两个不相连的节点,线性脚本不必要地将它们链起来。只有当数据实际在两者之间移动时,边才存在。

学会问:对于代理中的每个“然后”,下一步是否读取上一步的输出?如果不是,就没有边,等待就是浪费。

把它画成方框和箭头。方框是agent()调用。
箭头是变量,从一个调用的返回值传递到另一个调用的提示中。
如果你画不出箭头——如果没有变量跨越——那么这两个方框就是独立的,
独立性正是你将在本课程剩余部分利用的东西。
§ 6

§ 7

02. Your linear script is a degenerate graph

When you write an agent as “do A, then B, then C, then D,” you’ve drawn a graph - a single unbranching chain. Every node has exactly one edge in and one edge out.

It runs correctly. It also runs slowly and fragile, because a chain has no redundancy: if C stalls, D never happens, and A’s work is trapped upstream with nowhere to go.

The first real skill of graph engineering is redrawing the chain. Take your linear agent and, for each arrow, ask the Step 1 question.

Most chains have two or three arrows that don’t carry data - they’re just the order you happened to type things in.

Cut those arrows and the chain collapses into something wider: a few independent nodes that could all run at once, feeding a single node that needs them all.

02. 你的线性脚本是退化图

当你把代理写成“做A,然后B,然后C,然后D”时,你已经画了一个图——一条单链。每个节点恰好有一条输入边和一条输出边。

它能正确运行。但它运行缓慢且脆弱,因为链没有冗余:如果C停滞,D永远不会发生,A的工作被困在上游无处可去。

图工程的第一项真正技能是重新绘制链。以你的线性代理为例,对每条箭头问第一个问题。

大多数链有两三条箭头并不携带数据——它们只是你碰巧输入的顺序。

砍掉这些箭头,链就会坍塌成更宽的东西:几个独立的节点可以同时运行,然后供应给一个需要所有结果的单个节点。

§ 8

§ 9

03. Give every node a contract

A node you can’t reason about is a node you can’t parallelize. The fix is a contract: bounded input, bounded output, exactly one job.

The input is whatever the node reads - passed in explicitly, never assumed from a shared window. The output is a defined shape, ideally validated, so the next node can consume it without guessing.

In a workflow this contract is enforced with a schema. When you hand Claude an agent() call with a JSON schema, the subagent Claude spawns is forced to return validated structured data - validation happens at the tool-call layer, so Claude retries on mismatch instead of handing you free text you have to parse and pray over.

This is the difference between a node Claude can wire into a graph and a node that only works when a human reads its output.

// A node with a real contract: bounded in, validated out, one job.
const ITEM = {
  type: 'object', additionalProperties: false,
  properties: {
    title:   { type: 'string' },
    url:     { type: 'string' },
    impact:  { type: 'string', enum: ['high', 'medium', 'low'] },
  },
  required: ['title', 'url', 'impact'],
};

const result = await agent(source.prompt, {
  label:  `research:${source.key}`,
  schema: ITEM,           // forces validated structured output
  agentType: 'general-purpose',
});
// result is now a shape the next node can trust — not free text.

03. 为每个节点设定契约

无法推理的节点是无法并行化的。解决办法是契约:有界的输入、有界的输出、恰好一个任务。

输入是节点读取的任何内容——明确传入,绝不从共享窗口假设。输出是定义好的形状,理想情况下经过验证,以便下一个节点无需猜测即可消费。

在工作流中,这个契约通过schema强制实现。当你给Claude一个带有JSON schema的agent()调用时,Claude生成的子代理被迫返回验证后的结构化数据——验证发生在工具调用层,所以Claude会在不匹配时重试,而不是给你自由文本,让你解析并祈祷。

这就是Claude可以接入图的节点与只能在人类读取其输出时才能工作的节点之间的区别。

// 一个具有真实契约的节点:有界输入、验证输出、一个任务。
const ITEM = {
  type: 'object', additionalProperties: false,
  properties: {
    title:   { type: 'string' },
    url:     { type: 'string' },
    impact:  { type: 'string', enum: ['high', 'medium', 'low'] },
  },
  required: ['title', 'url', 'impact'],
};

const result = await agent(source.prompt, {
  label:  `research:${source.key}`,
  schema: ITEM,           // 强制验证的结构化输出
  agentType: 'general-purpose',
});
// 现在result是下一个节点可以信任的形状——不是自由文本。
§ 10

§ 11

04. Treat the edge as a data contract

An edge isn’t just “B comes after A.” It’s a promise about what crosses: A produces this shape, and B is built to consume this shape. When you name the edge by its data - not its order - two things get easier.

You can see instantly whether the edge is real (does data actually move?), and you can swap the node on either end without breaking the graph, as long as the shape holds.

In practice, the edge lives in plain JavaScript. The reduce step between fan-out and synthesis - flatten, dedupe, filter - is just code operating on the shapes your nodes returned.

No agent needed. One of the quiet wins of graph thinking: a huge amount of what people burn model tokens on is really an edge, and edges are free.

The temptation is to spawn an agent to “combine the results.” Resist 
it. If combining means flatten-and-dedupe, that’s results.flatMap(...) 
and a Set — deterministic, instant, zero tokens. Save agents for 
judgment, not for plumbing. A graph where every edge is an agent is a 
graph paying rent on its own wiring.

04. 把边当作数据契约

边不仅仅是“B在A之后”。它是对传递内容的承诺:A产生这个形状,B被构建来消费这个形状。当你按数据而不是顺序命名边时,有两件事会变得更简单。

你可以立即看出边是否真实(数据是否真的移动?),并且只要形状保持不变,你就可以交换任一端节点而不破坏图。

在实践中,边存在于纯JavaScript中。扇出和合成之间的归约步骤——展平、去重、过滤——只是对节点返回的形状进行操作的代码。

不需要代理。图思维的悄悄胜利之一:人们大量消耗模型令牌的事情实际上只是边,而边是免费的。

诱惑是产生一个代理来“合并结果”。抵制它。如果合并意味着展平并去重,那就是results.flatMap(...)和一个Set——确定、即时、零令牌。把代理留给判断,而不是管道。一个每条边都是代理的图,就是在为自己的布线支付租金。
§ 12

§ 13

05. Fan out with parallel()

This is the move that pays for everything. When you have N independent nodes - N sources to check, N files to review, N routes to audit - you don’t chain them.

You tell Claude to fan them out and run them at once. In a workflow that’s parallel(): Claude takes an array of thunks and spawns one subagent per thunk, all executing concurrently, then hands you back the array of results.

Two details make it robust. First, parallel() is a barrier - it waits for every thunk before it returns, so the next stage sees the complete set. Second, a thunk that throws resolves to null instead of rejecting the whole batch, so one flaky agent can’t sink the run.

Always .filter(Boolean) the results. Concurrency is capped around your core count and the excess queues, so you can pass a hundred thunks and they’ll all finish - just a handful at a time.

phase('Research');

// Nine sources, nine agents, all at once.
const raw = await parallel(
  SOURCES.map((s) => () =>
    agent(s.prompt, {
      label: `research:${s.key}`,
      phase: 'Research',
      schema: ITEM_SCHEMA,     // each node returns validated JSON
      agentType: 'general-purpose',
    }),
  ),
);

const collected = raw.filter(Boolean);  // drop the nulls from failed agents

The fan-out lives in code Claude wrote, not in a model conversation. Claude’s own context never holds nine sources at once - each subagent carries its own, and only the final answer comes back.

That’s what lets Claude scale a workflow to dozens or hundreds of subagents without drowning the session. The orchestration layer costs zero tokens because it isn’t another turn of Claude thinking.

05. 使用parallel()并行扇出

这是回报一切的手段。当你拥有N个独立节点——N个要检查的来源、N个要审查的文件、N个要审计的路由——不要将它们链起来。

你告诉Claude扇出它们并立即运行。在工作流中,这就是parallel():Claude接受一个thunk数组,为每个thunk生成一个子代理,同时执行,然后将结果数组返回给你。

两个细节使其健壮。首先,parallel()是一个屏障——它等待每个thunk返回后才继续,因此下一阶段看到完整的集合。其次,抛出的thunk解析为null而不是拒绝整个批次,因此一个不稳定的代理不会导致运行失败。

始终对结果使用.filter(Boolean)。并发受核心数和超额队列限制,因此你可以传递一百个thunk,它们都会完成——只是每次少数几个。

phase('Research');

// 九个来源,九个代理,同时运行。
const raw = await parallel(
  SOURCES.map((s) => () =>
    agent(s.prompt, {
      label: `research:${s.key}`,
      phase: 'Research',
      schema: ITEM_SCHEMA,     // 每个节点返回验证后的JSON
      agentType: 'general-purpose',
    }),
  ),
);

const collected = raw.filter(Boolean);  // 丢弃失败代理的null

扇出存在于Claude编写的代码中,而不是模型对话中。Claude自己的上下文从不同时容纳九个来源——每个子代理携带自己的上下文,只有最终答案返回。

这就是Claude能够将工作流扩展到数十或数百个子代理而不会淹没会话的原因。编排层不消耗令牌,因为它不是Claude思考的另一个回合。

§ 14

§ 15

06. Fan in at a barrier

A fan-out is only useful if something gathers it. The fan-in is the node where edges converge - where one agent (or one piece of code) sees all the upstream results at once and does something that requires the whole set: dedupe across sources, rank by impact, early-exit if the total came back empty. This is the one place a barrier earns its wall-clock cost.

The rule that keeps graphs fast: use a barrier only when a stage genuinely needs every prior result together. Deduping across all sources? Barrier - correct.

// The edge: plain JS, no agent, zero tokens.
const flat = collected.flatMap((c) => c.items);
log(`Collected ${flat.length} items`);

phase('Curate');
// The barrier node: needs the WHOLE set to dedupe + rank.
const curated = await agent(
  `Dedupe and rank these by impact:\n${JSON.stringify(flat)}`,
  { phase: 'Curate', schema: CURATED_SCHEMA },
);

Just flattening a list? That’s an edge, do it inline. The smell test is brutal and simple: if you wrote parallel → transform → parallel, and that middle transform has no cross-item dependency, you should have used a pipeline and skipped the barrier entirely.

06. 使用屏障扇入

扇出只有在某物收集它时才有用。扇入是边汇聚的节点——一个代理(或一段代码)同时看到所有上游结果,并执行需要整个集合的操作:跨来源去重、按影响排名、如果总数返回空则提前退出。这是屏障值得其挂钟时间成本的地方。

保持图快速的规则:仅当一个阶段真的需要同时获得所有先前结果时才使用屏障。跨所有来源去重?屏障——正确。

// 边:纯JS,无代理,零令牌。
const flat = collected.flatMap((c) => c.items);
log(`收集了${flat.length}个项目`);

phase('Curate');
// 屏障节点:需要整个集合来去重和排名。
const curated = await agent(
  `去重并按影响排名:\n${JSON.stringify(flat)}`,
  { phase: 'Curate', schema: CURATED_SCHEMA },
);

只是展平列表?那是边,内联处理。气味测试残酷而简单:如果你写了parallel → transform → parallel,而中间的transform没有跨项目依赖,你应该使用管道并完全跳过屏障。

§ 16

§ 17

07. The diamond: split → work → merge

Put fan-out and fan-in together and you get the workhorse topology of every serious agent graph: the diamond.

One node splits the job, many nodes do the work in parallel, one node merges. It’s the shape behind a market scan, a dependency audit, a code review, a research report - swap the sources and prompts and the same skeleton adapts.

The canonical form has a name worth memorizing: fan out → reduce → synthesize. Fan out to gather breadth, reduce with plain code to compress it, synthesize with a final agent to write the answer.

Once you see the diamond, you stop asking “how do I make my agent do more steps” and start asking “where’s the split, where’s the merge” - which is the question that actually scales.

07. 钻石拓扑:分割-工作-合并

将扇出和扇入放在一起,你就得到了每个严肃代理图的主力拓扑:钻石。

一个节点分割任务,许多节点并行工作,一个节点合并。这是市场扫描、依赖审计、代码审查、研究报告背后的形状——交换来源和提示,同一个骨架就能适配。

规范形式有一个值得记忆的名字:扇出→归约→合成。扇出以收集广度,用纯代码归约以压缩,用最终代理合成以写出答案。

一旦你看到钻石,你就不再问“如何让我的代理做更多步骤”,而开始问“分割在哪里,合并在哪里”——这才是真正能扩展的问题。

§ 18

§ 19

08. Route the edge at runtime with a conditional

Not every graph is fixed. Sometimes the edge to take depends on what a node found. A router node inspects a result and decides which downstream path fires - classify the ticket, then branch to the right handler; check the diff size, then either do a quick review or spin up a full audit.

In a workflow this is just a JavaScript if or switch on a node’s validated output, because control flow lives in code.

This is where determinism becomes a feature, not a limitation. The router’s decision can be Claude-powered (a subagent classifies), but the routing is code Claude wrote - so it runs the same way every time for the same classification.

You get Claude’s judgment at the node and the script’s reliability at the edge. No emergent “Claude decided to skip the audit” surprises - because the skip would have to be written into the graph, and it isn’t.

// Router node: an agent classifies, code picks the edge.
const { severity } = await agent(
  `Classify this diff's risk:\n${diff}`,
  { schema: { type: 'object',
      properties: { severity: { enum: ['low', 'high'] } },
      required: ['severity'] } },
);

let review;
if (severity === 'high') {
  // heavy path: full parallel audit
  review = await parallel(FILES.map((f) => () => agent(`Audit ${f}`)));
} else {
  // light path: one quick pass
  review = await agent(`Quick review of ${diff}`);
}

08. 运行时条件路由边

并非每个图都是固定的。有时要走哪条边取决于节点发现的内容。路由器节点检查结果并决定触发哪个下游路径——对工单进行分类,然后分支到正确的处理程序;检查差异大小,然后要么快速审查,要么启动全面审计。

在工作流中,这只是对节点验证输出进行JavaScript if或switch操作,因为控制流存在于代码中。

这就是确定性成为特性而非限制的地方。路由器的决策可以由Claude驱动(子代理分类),但路由是Claude编写的代码——因此对于相同的分类,每次运行方式相同。

你得到节点处的Claude判断和边上的脚本可靠性。不会出现突发的“Claude决定跳过审计”的意外——因为跳过必须写入图,而它没有。

// 路由器节点:代理分类,代码选择边。
const { severity } = await agent(
  `分类这个diff的风险:\n${diff}`,
  { schema: { type: 'object',
      properties: { severity: { enum: ['low', 'high'] } },
      required: ['severity'] } },
);

let review;
if (severity === 'high') {
  // 重路径:全面并行审计
  review = await parallel(FILES.map((f) => () => agent(`审计 ${f}`)));
} else {
  // 轻路径:一次快速审查
  review = await agent(`快速审查 ${diff}`);
}
§ 20

§ 21

09. Put a verifier on the edge

The real leverage of a graph isn’t more agents - it’s the structure you can wrap around them to produce confidence.

A verifier node sits on the edge before a result is allowed downstream, and its only job is to try to kill the finding. If it survives, it passes. If not, it never reaches the answer.

Three patterns are worth having in your hands.

  • Adversarial verify: for each finding, spawn N independent skeptics prompted to refute it; keep it only if a majority survive.
  • Perspective-diverse verify: give each verifier a distinct lens - correctness, security, does-it-reproduce - because diversity catches failure modes that N identical checks never will.
  • Judge panel: generate N attempts from different angles, score them with parallel judges, synthesize from the winner while grafting the best of the runners-up.

This is exactly the pattern that let a real team port the Bun runtime with adversarial code review baked into the loop.

09. 在边上设置验证器

图的真正杠杆不是更多代理——而是你可以围绕它们构建以产生信心的结构。

验证器节点位于边上,在结果被允许向下游传递之前,它的唯一任务是试图推翻发现。如果它经受住了,则通过。如果没有,它永远不会到达答案。

有三种模式值得掌握。

  • 对抗性验证:对于每个发现,生成N个独立的怀疑者,提示他们反驳它;只有当大多数通过时才保留。
  • 视角多样性验证:为每个验证器提供不同的镜头——正确性、安全性、能否重现——因为多样性能够抓住N次相同检查永远无法捕获的失败模式。
  • 评审团:从不同角度生成N次尝试,用并行评审打分,从获胜者合成,同时嫁接亚军的最佳部分。

这正是让一个真实团队将Bun运行时移植到循环中的模式,其中嵌入对抗性代码审查。

§ 22

§ 23

10. Isolate nodes so one failure can’t poison the graph

In a chain, a failure cascades - C dies, D never runs, the whole thing halts. In a graph, failure should be contained to its node.

That’s already partly true: a thunk that throws inside parallel() resolves to null, so eight good agents still return while one bad one drops out. Your .filter(Boolean) is the containment.

Design every fan-in to tolerate missing inputs rather than assume a full set.

The subtler failure is nodes stepping on each other. When agents write files in parallel, they can collide.

The fix is isolation: "worktree" - each agent runs in its own git worktree, does its work in a sandbox, and merges cleanly.

Reach for it only when nodes actually write in parallel. it’s the seatbelt for the one topology that needs it, not a default tax on every run.

10. 隔离节点以防止故障扩散

在链中,故障会级联——C死了,D从不运行,整个事情停止。在图中,故障应该被限制在其节点内。

这已经部分成立:在parallel()内部抛出的thunk解析为null,因此八个好的代理仍然返回,而一个坏的代理退出。你的.filter(Boolean)就是隔离。

设计每个扇入以容忍缺失输入,而不是假设完整集合。

更微妙的故障是节点相互干扰。当代理并行写入文件时,可能会发生冲突。

解决办法是隔离:“工作树”——每个代理在其自己的git工作树中运行,在沙箱中工作,然后干净地合并。

仅在节点实际并行写入时使用它。它是需要它的拓扑的安全带,而不是每次运行的默认开销。

§ 24

§ 25

11. Add a cycle - but make it converge

Sometimes you don’t know how big the job is until you’re in it: unknown-size discovery, a bug sweep where finding one bug reveals three more. That needs a cycle - a controlled edge back to an earlier node.

The danger is obvious: a cycle that doesn’t converge is an infinite loop that spawns agents until your budget is gone.

The pattern that converges is loop-until-dry: keep spawning finders until K consecutive rounds surface nothing new, then stop. The one detail that makes or breaks it - and the mistake almost everyone makes the first time - is what you dedupe against.

Dedupe against everything seen, not just against confirmed results. Otherwise rejected findings reappear every round, the loop never runs dry, and you’ve built a machine that pays to rediscover the same dead ends forever.

const seen = new Set(); const confirmed = []; let dry = 0;

while (dry < 2) {                       // stop after 2 empty rounds
  const found = (await parallel(
    FINDERS.map((f) => () => agent(f.prompt, { schema: BUGS }))
  )).filter(Boolean).flatMap((r) => r.bugs);

  const fresh = found.filter((b) => !seen.has(key(b)));
  if (!fresh.length) { dry++; continue; } // nothing new → toward dry
  dry = 0;
  fresh.forEach((b) => seen.add(key(b))); // dedupe vs SEEN, not confirmed

  // diverse-lens verify each fresh finding before it counts
  const judged = await parallel(fresh.map((b) => () =>
    parallel(['correctness', 'security', 'repro'].map((lens) => () =>
      agent(`Judge "${b.desc}" via ${lens} — real?`, { schema: VERDICT })))
    .then((v) => ({ b, real: v.filter(Boolean).filter((x) => x.real).length >= 2 }))));

  confirmed.push(...judged.filter((v) => v.real).map((v) => v.b));
}

11. 添加循环但确保收敛

有时你直到深入任务才知道任务有多大:未知大小的发现,一个错误扫描,发现一个错误又揭示三个更多。这需要一个循环——一条返回到较早节点的受控边。

危险是显而易见的:不收敛的循环是一个无限循环,它会生成代理直到你的预算耗尽。

收敛的模式是循环直到干涸:持续生成查找器,直到连续K轮没有发现新东西,然后停止。一个成败的关键细节——几乎每个人第一次都会犯的错误——是你针对什么进行去重。

针对所有看到的东西去重,而不仅仅是针对已确认的结果。否则被拒绝的发现每轮都会重新出现,循环永远不会干涸,你就建造了一台永远为重新发现相同死胡同付费的机器。

const seen = new Set(); const confirmed = []; let dry = 0;

while (dry < 2) {                       // 2次空轮后停止
  const found = (await parallel(
    FINDERS.map((f) => () => agent(f.prompt, { schema: BUGS }))
  )).filter(Boolean).flatMap((r) => r.bugs);

  const fresh = found.filter((b) => !seen.has(key(b)));
  if (!fresh.length) { dry++; continue; } // 无新内容 → 趋向干涸
  dry = 0;
  fresh.forEach((b) => seen.add(key(b))); // 针对SEEN去重,非confirmed

  // 在每个新鲜发现被计入前,进行多视角验证
  const judged = await parallel(fresh.map((b) => () =>
    parallel(['correctness', 'security', 'repro'].map((lens) => () =>
      agent(`Judge "${b.desc}" via ${lens} — real?`, { schema: VERDICT })))
    .then((v) => ({ b, real: v.filter(Boolean).filter((x) => x.real).length >= 2 }))));

  confirmed.push(...judged.filter((v) => v.real).map((v) => v.b));
}
§ 26

§ 27

12. Tier the models across the nodes

Not every node needs your best model. A graph makes this obvious in a way a single agent never does: some nodes are bounded and repetitive (extract this field, classify this ticket), and some carry the real judgment (synthesize the report, adjudicate the finding).

Run the boring nodes on a cheaper model and spend your expensive tokens where judgment actually lives.

In a workflow every subagent Claude spawns inherits your session model unless the script overrides it - so by default a big run bills entirely at your session tier. The model option on a single agent() call tells Claude to route just that node elsewhere.

Check /model before a large run, then have Claude route the fan-out’s repetitive nodes down to a cheaper model and keep the merge node up. This is the lever that turns a token-hungry graph from expensive into economical without touching its shape.

12. 按节点分层模型

并非每个节点都需要你最好的模型。图以一种单个代理永远不会的方式让这一点变得明显:有些节点是有界且重复的(提取这个字段、分类这个工单),而有些节点承载真正的判断(合成报告、裁决发现)。

在更便宜的模型上运行无聊的节点,并在判断真正存在的地方花费昂贵的令牌。

在工作流中,Claude生成的每个子代理都会继承你的会话模型,除非脚本覆盖它——因此默认情况下,大型运行完全按你的会话层级计费。单个agent()调用上的model选项告诉Claude仅将该节点路由到别处。

在大型运行前检查/model,然后让Claude将扇出的重复节点路由到更便宜的模型,并保持合并节点在高层级。这是将饥饿令牌的图从昂贵变成经济的杠杆,无需触碰其形状。

§ 28

§ 29

13. Topology is your cost and latency

The shape of the graph isn’t cosmetic - it’s the single biggest lever on wall-clock time. The choice that trips everyone up: parallel() versus pipeline(). A parallel() barrier makes everything wait for the slowest node before the next stage starts.

A pipeline() streams each item through all stages independently, with no barrier - item A can be in stage 3 while item B is still in stage 1. Fast items finish early instead of idling behind slow ones.

Default to pipeline(). Reach for a barrier only when a stage truly needs every prior result at once - a cross-set dedupe, an early-exit on the total, a prompt that compares against “the other findings.” “It’s cleaner code” and “the stages feel separate” are not reasons; barrier latency is real, measurable, wasted time. Separate is not the same as synchronized.

13. 拓扑决定成本与延迟

图的形状不是装饰性的——它是墙上时钟时间最大的杠杆。让每个人跌倒的选择是:parallel()与pipeline()。parallel()屏障让一切等待最慢的节点完成,然后下一阶段才开始。

pipeline()将每个项目独立流经所有阶段,没有屏障——项目A可以在阶段3,而项目B仍在阶段1。快速项目提前完成,而不是在慢速项目后面闲置。

默认使用pipeline()。仅当一个阶段真正需要同时获得所有先前结果时才使用屏障——跨集去重、对总数的提前退出、比较“其他发现”的提示。“代码更干净”和“阶段感觉分离”不是理由;屏障延迟是真实的、可测量的、浪费的时间。分离不等于同步。

§ 30

§ 31

14. Let Claude draw the graph - self-routing

The final move is to stop drawing the graph by hand for jobs you can’t plan in advance.

With dynamic workflows, you describe the objective and Claude writes the orchestration script itself- decomposing the task, choosing the fan-out, spawning a coordinated fleet of subagents, and synthesizing the result. You get a graph tailored to this run instead of a fixed one you hoped would fit.

There are three ways in. Say the word “workflow” in your prompt and Claude writes one for the task. Run a saved or bundled one - /deep-research is a real graph shipping in production: scope → parallel search → fetch → adversarial verify → synthesize, the exact skeleton from this course.

Or turn on ultracode and Claude plans a workflow for every substantial task in the session. When a run is good, press s to save its script into .claude/workflows/ - version-controlled, re-runnable by name, a graph anyone who clones the repo can launch.

› Run a workflow to audit every route under src/routes/ for missing 
auth. Spawn one agent per route file, then verify each finding before 
reporting. ● Claude wrote an orchestration script · launching in
background… /workflows — auth-audit · running ✓ Scope 1/1 2.1k tok · 
4s ✓ Fan-out 18/18 one agent per route file ◯ Verify 11/18 3-vote 
skeptics per finding… ○ Synthesize 0/1 waiting on verify session stays
responsive — keep working while the fleet runs

14. 让Claude自己绘制图-自路由

最后一步是对于无法提前计划的任务,停止手动绘图。

使用动态工作流,你描述目标,Claude自己编写编排脚本——分解任务、选择扇出、生成协调的子代理集群、合成结果。你会得到一个针对本次运行定制的图,而不是一个你希望适合的固定图。

有三种方式。在你的提示中说“workflow”一词,Claude就会为任务编写一个。运行保存或捆绑的工作流——/deep-research是一个在生产中实际交付的图:范围→并行搜索→获取→对抗性验证→合成,正是本课程的骨架。

或者打开ultracode,Claude会为会话中的每个实质性任务计划一个工作流。当一次运行良好时,按s将其脚本保存到.claude/workflows/——版本控制,可重命名运行,任何克隆仓库的人都可以启动的图。

› 运行工作流以审计 src/routes/ 下的每个路由缺少的认证。
为每个路由文件生成一个代理,然后在报告之前验证每个发现。
● Claude编写了一个编排脚本 · 后台启动…
/workflows — auth-audit · 运行中 ✓ Scope 1/1 2.1k tok ·
4s ✓ Fan-out 18/18 每个路由文件一个代理 ◯ Verify 11/18 每个发现3票怀疑者…
○ Synthesize 0/1 等待验证会话保持响应 — 在集群运行时继续工作
§ 32

§ 33

Six graphs to build with Claude this week

  • Security sweep across every route. Claude spawns one subagent per route file, each hunting for missing auth checks, then a verifier pass confirms every finding before it reaches the report. Breadth no single context could hold.
  • Cited report with /deep-research. A graph that ships in Claude Code already. Claude decomposes your question into distinct angles, runs parallel searches, dedupes sources, then adversarially verifies every claim with three-vote skeptics before writing.
  • Port a module, file by file. The Bun ceiling, scaled to your repo. Claude fans out translation across files, runs the test suite as a gate on each, and loops the failures back - adversarial review catching what a single pass would ship broken.
  • Adversarial review of a diff. Claude routes on diff size: a small change gets one quick pass, a large one triggers a full parallel audit with reviewers on distinct lenses - correctness, security, performance - then a judge panel synthesizes.
  • Ecosystem scan on a schedule. Save it once, re-run it forever. Claude checks many sources in parallel - releases, blogs, discussion - ranks by impact at a barrier, and writes the digest. Version-controlled in .claude/workflows/, launchable by name.
  • Discovery of unknown size. You don’t know how many bugs are there. Claude runs finders in parallel, dedupes each new find against everything seen, verifies survivors, and keeps looping until two rounds turn up nothing new - then stops.

本周用Claude构建的六个图

  • 安全扫描每个路由。Claude为每个路由文件生成一个子代理,每个子代理寻找缺失的认证检查,然后验证器通过确认每个发现,然后才进入报告。广度是单个上下文无法容纳的。
  • 使用/deep-research的引用报告。一个已经在Claude Code中交付的图。Claude将你的问题分解成不同角度,并行搜索,去重来源,然后在写作前用三票怀疑者对抗性验证每个声明。
  • 逐个文件移植模块。Bun的天花板,扩展到你的仓库。Claude跨文件扇出翻译,将测试套件作为每个文件的关卡,并将失败循环回来——对抗性审查捕获单一传递会交付的损坏。
  • 差异的对抗性审查。Claude根据差异大小路由:小的更改有一次快速通过,大的更改触发全面并行审计,审查者从不同镜头进行——正确性、安全性、性能——然后评审团合成。
  • 按计划进行生态系统扫描。保存一次,永远重新运行。Claude并行检查许多来源——发布、博客、讨论——在屏障处按影响排名,然后撰写摘要。在.claude/workflows/中版本控制,可按名称启动。
  • 未知大小的发现。你不知道有多少错误。Claude并行运行查找器,每个新发现与所有已见去重,验证幸存者,并持续循环直到两轮没有新发现——然后停止。
§ 34

§ 35

Conclusion:

A prompter asks a question. An architect draws a graph.

The linear agent was never the ceiling - it was just the first shape, the one everyone reaches for because it matches how we type. One line, one head, one thing at a time.

Once you can see the nodes and the edges, you stop asking the agent to do more and start asking the graph to do it wider: fan out where the work is independent, gate the edges where confidence matters, tier the models where judgment doesn’t.

Most people will keep queueing steps in a line. The ones who learn to draw the graph will run a fleet - and never notice the ceiling the rest are stuck under.

结论:

提示者问问题。架构师画图。

线性代理从来不是天花板——它只是第一个形状,每个人都因为符合我们打字的方式而使用它。一条线、一个头、一次一件事。

一旦你能看到节点和边,你就不再要求代理做更多,而是开始要求图更宽地做:在独立的工作上扇出,在信心重要的边上设置门控,在判断不重要的地方分层模型。

大多数人将继续按顺序排队步骤。学会画图的人将运行一个集群——并且永远不会注意到其他人被困住的天花板。

打开原文 ↗