大多數嘗試建立多步驟 Agent 的人,最終都只畫出一條直線。第一步、第二步、第三步——每一步都乖乖等待上一步完成才開始。
10 個人裡有 9 個會發現,那些步驟裡有一半根本不需要等待。
它們不會路由。不會分支。不會平行化。只是排隊——一個腦袋、一個上下文、一次一件事,直到視窗塞滿,Agent 忘記自己正在做什麼。
追蹤我的 Substack 以獲取最新的 AI 資訊:
這是一份 14 步驟的路線圖,能將那條單一檔案排隊線轉變為一個圖形:一個能分散到整個 Agent 群、驗證自身發現、並收斂出單一 Agent 永遠無法掌握的結果的圖形。

這裡有一個沒人明說的轉變。提示詞是一個句子。迴圈是一個週期。框架是 Agent 立足的地板。
但工作本身的形狀——什麼先執行、什麼可以同時執行、什麼必須等待其他所有事情——這個形狀就是一個圖形。節點負責思考。邊負責傳遞結果。
Claude Code 提供了直接建立這些圖形的工具:動態工作流程。
Claude 會撰寫一個純 JavaScript 的編排腳本,然後生成一個協調的子 Agent 群來執行它——而協調本身不消耗任何模型 token,因為它是程式碼,而不是對話。
01. 節點是任務。邊是 流動 的東西。
一個圖形只有兩樣東西,搞清楚它們就能解決大部分困惑。一個節點是一個工作單元——一個 Agent、一個有邊界的任務、一個輸入和一個輸出。
一條邊是一個依賴關係:它表示這個節點的輸出會餵給那個節點的輸入。僅此而已。

常見的錯誤是把「然後」當作一條邊。「總結這個檔案,然後告訴我天氣」這兩者之間沒有邊——天氣並不會消費那個摘要。
那是兩個不相關的節點,被一個線性腳本不必要地串聯起來。邊只存在於資料實際流經的地方。
學會對 Agent 中的每個「然後」提問:下一步驟會讀取上一步驟的輸出嗎? 如果不會,就沒有邊,等待就是浪費。
1把它畫成方塊和箭頭。一個方塊就是一個 agent() 呼叫。2一條箭頭就是從一個呼叫的返回值傳遞到另一個呼叫的3提示詞中的變數。如果你畫不出那條箭頭——如果沒有變數跨越——4那兩個方塊就是獨立的,而獨立性正是你在本課程後續部分5將要利用的東西。
02. 你的線性腳本是一個 退化 的圖形
當你把一個 Agent 寫成「做 A,然後 B,然後 C,然後 D」時,你已經畫出了一個圖形——一條單一、不分枝的鏈條。每個節點恰好有一條邊進入和一條邊出去。
它執行起來是正確的。但它也執行得緩慢且脆弱,因為鏈條沒有冗餘:如果 C 停滯了,D 永遠不會發生,A 的工作就被困在上游,無處可去。

圖形工程的第一個真正技能是重新繪製這條鏈條。拿起你的線性 Agent,對每一條箭頭,問第 1 步的那個問題。
大多數鏈條都有兩三條不攜帶資料的箭頭——它們只是你碰巧輸入事物的順序。
砍掉那些箭頭,鏈條就會崩解成更寬廣的東西:幾個可以同時執行的獨立節點,餵給一個需要它們全部結果的單一節點。
03. 給每個節點一個 合約
一個你無法推理的節點,就是一個你無法平行化的節點。解決方案是合約:有邊界的輸入、有邊界的輸出、剛好一個任務。
輸入是節點讀取的任何東西——明確地傳入,絕不從共享的視窗中假設。輸出是一個定義好的形狀,最好經過驗證,這樣下一個節點就可以直接消費它,無需猜測。

在工作流程中,這個合約透過一個結構描述來強制執行。當你交給 Claude 一個帶有 JSON 結構描述的 agent() 呼叫時,它所生成的子 Agent Claude 會被強制回傳經過驗證的結構化資料——驗證發生在工具呼叫層,所以 Claude 會在資料不符時重試,而不是交給你需要解析和祈禱的自由文字。
這就是一個 Claude 可以連接到圖形中的節點,與一個只有人類閱讀其輸出才能運作的節點之間的區別。
1// 一個有真正合約的節點:有邊界的輸入、驗證過的輸出、單一任務。2const ITEM = {3 type: 'object', additionalProperties: false,4 properties: {5 title: { type: 'string' },6 url: { type: 'string' },7 impact: { type: 'string', enum: ['high', 'medium', 'low'] },8 },9 required: ['title', 'url', 'impact'],10};1112const result = await agent(source.prompt, {13 label: `research:${source.key}`,14 schema: ITEM, // 強制輸出經過驗證的結構化資料15 agentType: 'general-purpose',16});17// result 現在是下一個節點可以信任的形狀——不再是自由文字。
04. 將 邊視為資料合約
一條邊不僅僅是「B 在 A 之後」。它是一個關於什麼東西會跨越的承諾:A 產生這個形狀,而 B 被設計來消費這個形狀。當你用它的資料——而不是它的順序——來命名邊時,兩件事會變得更容易。

你可以立刻看出這條邊是否真實存在(資料真的有移動嗎?),而且只要形狀保持不變,你就可以交換邊任一端的節點,而不會破壞圖形。
在實務上,邊存在於純 JavaScript 中。在扇出和綜合之間的 reduce 步驟——扁平化、去重、過濾——只是對你的節點回傳的形狀進行操作的程式碼。
不需要 Agent。 圖形思維的一個低調勝利:人們花費大量模型 token 處理的事情,實際上常常只是一條邊,而邊是免費的。
1誘惑是生成一個 Agent 來「合併結果」。抵制它。2如果合併意味著扁平化和去重,那就是 results.flatMap(...)3和一個 Set —— 確定性、即時、零 token。把 Agent 留給4判斷,而不是管線。一個每一條邊都是一個 Agent 的圖形,5是一個在為自己的佈線支付租金的圖形。
05. 使用 parallel() 進行扇出
這是能回報所有投資的關鍵動作。當你有 N 個獨立節點時——N 個要檢查的來源、N 個要審查的檔案、N 條要稽核的路徑——你不會將它們串聯起來。
你告訴 Claude 將它們扇出並同時執行。在工作流程中,這就是 parallel():Claude 接收一個 thunk 陣列,為每個 thunk 生成一個子 Agent,全部同時執行,然後將結果陣列交還給你。

兩個細節讓它變得穩健。首先,parallel() 是一個屏障——它會等待每個 thunk 完成才回傳,所以下一個階段會看到完整的集合。其次,一個拋出錯誤的 thunk 會解析為 null,而不是拒絕整個批次,所以一個不穩定的 Agent 不會導致整個執行失敗。
記得總是 .filter(Boolean) 過濾結果。並行數會根據你的核心數量和超額佇列進行限制,所以你可以傳入一百個 thunk,它們最終都會完成——只是一次處理少數幾個。
1phase('Research');23// 九個來源,九個 Agent,全部同時進行。4const raw = await parallel(5 SOURCES.map((s) => () =>6 agent(s.prompt, {7 label: `research:${s.key}`,8 phase: 'Research',9 schema: ITEM_SCHEMA, // 每個節點回傳經過驗證的 JSON10 agentType: 'general-purpose',11 }),12 ),13);1415const collected = raw.filter(Boolean); // 丟棄失敗 Agent 的 null 結果
扇出存在於 Claude 編寫的程式碼中,而不是模型對話中。Claude 自己的上下文永遠不會同時容納九個來源——每個子 Agent 攜帶自己的來源,只有最終答案會回傳。
這就是讓 Claude 能將工作流程擴展到數十或數百個子 Agent 而不淹沒會話的原因。編排層花費零 token,因為它不是 Claude 思考的另一輪。
06. 在 屏障 處扇入
扇出只有在有東西能匯集它時才有用。扇入是邊收斂的節點——一個 Agent(或一段程式碼)同時看到所有上游結果,並執行需要整個集合才能完成的事情:跨來源去重、按影響力排序、如果總數為空則提前退出。這是屏障值得花費其牆上時鐘時間的唯一地方。

保持圖形快速的規則是:只在一個階段真正需要所有先前的結果在一起時才使用屏障。 跨所有來源去重?屏障——正確。
1// 邊:純 JS,沒有 Agent,零 token。2const flat = collected.flatMap((c) => c.items);3log(`Collected ${flat.length} items`);45phase('Curate');6// 屏障節點:需要整個集合來進行去重 + 排序。7const curated = await agent(8 `Dedupe and rank these by impact:\n${JSON.stringify(flat)}`,9 { phase: 'Curate', schema: CURATED_SCHEMA },10);
只是扁平化一個列表?那是一條邊,直接內聯處理。嗅探測試既殘酷又簡單:如果你寫了 parallel → transform → parallel,而中間的 transform 沒有跨項目的依賴關係,你應該使用管線並完全跳過屏障。
07. 菱形:拆分 → 工作 → 合併
將扇出和扇結合在一起,你就得到了每個嚴肅 Agent 圖形的主力拓撲結構:菱形。
一個節點拆分任務,多個節點平行工作,一個節點合併。它是市場掃描、依賴稽核、程式碼審查、研究報告背後的形狀——交換來源和提示詞,同樣的骨架就能適應。

其標準形式有一個值得記住的名稱:扇出 → 縮減 → 綜合。 扇出以收集廣度,用純程式碼縮減以壓縮,最後用一個最終 Agent 綜合以撰寫答案。
一旦你看到了菱形,你就不再問「如何讓我的 Agent 做更多步驟」,而是開始問「拆分點在哪裡,合併點在哪裡」——這才是真正能擴展的問題。
08. 使用 條件 在執行時路由邊
並非每個圖形都是固定的。有時要採取的邊取決於一個節點發現了什麼。一個路由器節點會檢查結果,並決定觸發哪個下游路徑——對工單進行分類,然後分支到正確的處理程序;檢查 diff 大小,然後要嘛進行快速審查,要嘛啟動完整的稽核。
在工作流程中,這只是一個基於節點驗證輸出的 JavaScript if 或 switch,因為控制流程存在於程式碼中。

這就是確定性成為一個功能,而不是限制的地方。路由器的決策可以由 Claude 驅動(一個子 Agent 進行分類),但路由是 Claude 編寫的程式碼——所以對於相同的分類,它每次都會以相同的方式執行。
你在節點獲得 Claude 的判斷力,在邊獲得腳本的可靠性。不會有突然出現的「Claude 決定跳過稽核」的驚喜——因為跳過必須被寫入圖形中,而它沒有。
1// 路由器節點:一個 Agent 進行分類,程式碼選擇邊。2const { severity } = await agent(3 `Classify this diff's risk:\n${diff}`,4 { schema: { type: 'object',5 properties: { severity: { enum: ['low', 'high'] } },6 required: ['severity'] } },7);89let review;10if (severity === 'high') {11 // 重路徑:完整的平行稽核12 review = await parallel(FILES.map((f) => () => agent(`Audit ${f}`)));13} else {14 // 輕路徑:一次快速檢查15 review = await agent(`Quick review of ${diff}`);16}
09. 在邊上放置一個 驗證器
圖形的真正槓桿作用不在於更多的 Agent——而在於你可以圍繞它們構建以產生信心的結構。
一個驗證器節點位於結果被允許向下游傳遞之前的邊上,它的唯一任務是嘗試推翻這個發現。如果它存活下來,就通過。如果沒有,它就永遠不會到達答案。

有三種模式值得掌握。
- 對抗性驗證: 對於每個發現,生成 N 個被提示要反駁它的獨立懷疑論者;只有在多數存活時才保留它。
- 多視角驗證: 給每個驗證器一個不同的鏡頭——正確性、安全性、是否可重現——因為多樣效能捕捉到 N 個相同檢查永遠無法發現的失敗模式。
- 評審團: 從不同角度生成 N 個嘗試,用平行評審員打分,從勝者綜合,同時嫁接亞軍的最佳部分。
這正是讓一個真實團隊在循環中內建對抗性程式碼審查來移植 Bun 執行環境的模式。
10. 隔離節點,使單一失敗無法 汙染 圖形
在鏈條中,失敗會級聯——C 死了,D 永遠不會執行,整個事情停擺。在圖形中,失敗應該被限制在其節點內。
這已經部分成立:在 parallel() 內部拋出錯誤的 thunk 會解析為 null,所以八個好的 Agent 仍然會回傳,而一個壞的 Agent 會脫落。你的 .filter(Boolean) 就是這個限制。
設計每個扇入節點時,要讓它能容忍缺失的輸入,而不是假設一個完整的集合。

更微妙的失敗是節點互相干擾。當 Agent 平行寫入檔案時,它們可能會衝突。
解決方案是隔離:「工作樹」——每個 Agent 在自己的 git 工作樹中執行,在沙盒中完成工作,然後乾淨地合併。
只在節點實際平行寫入時才使用它。它是針對需要它的特定拓撲結構的安全帶,而不是每個執行的預設稅。
11. 加入一個循環——但要讓它 收斂
有時你直到身在其中才知道任務有多大:未知大小的探索,一個錯誤掃描中發現一個錯誤又揭示了三個。這需要一個循環——一條回到較早節點的可控邊。
危險顯而易見:不收斂的循環就是一個無限迴圈,它會不斷生成 Agent,直到你的預算耗盡。

能夠收斂的模式是循環直到乾涸: 持續生成尋找器,直到連續 K 輪沒有發現新東西,然後停止。一個決定成敗的細節——也是幾乎每個人在第一次嘗試時都會犯的錯誤——是你用來去重的對象。
要針對所有見過的東西去重,而不僅僅是針對已確認的結果。否則,被拒絕的發現會在每一輪重新出現,循環永遠不會乾涸,你就建造了一台不斷花錢重新發現相同死胡同的機器。
1const seen = new Set(); const confirmed = []; let dry = 0;23while (dry < 2) { // 在 2 輪空結果後停止4 const found = (await parallel(5 FINDERS.map((f) => () => agent(f.prompt, { schema: BUGS }))6 )).filter(Boolean).flatMap((r) => r.bugs);78 const fresh = found.filter((b) => !seen.has(key(b)));9 if (!fresh.length) { dry++; continue; } // 沒有新東西 → 趨向乾涸10 dry = 0;11 fresh.forEach((b) => seen.add(key(b))); // 針對 SEEN 去重,而非 confirmed1213 // 在計入之前,用多視角驗證每個新發現14 const judged = await parallel(fresh.map((b) => () =>15 parallel(['correctness', 'security', 'repro'].map((lens) => () =>16 agent(`Judge "${b.desc}" via ${lens} — real?`, { schema: VERDICT })))17 .then((v) => ({ b, real: v.filter(Boolean).filter((x) => x.real).length >= 2 }))));1819 confirmed.push(...judged.filter((v) => v.real).map((v) => v.b));20}
12. 跨 節點 分層使用模型
並非每個節點都需要你最好的模型。圖形讓這一點變得顯而易見,這是單一 Agent 永遠做不到的:有些節點是有邊界且重複性的(提取這個欄位、分類這個工單),而有些則承載著真正的判斷力(綜合報告、裁定發現)。
在較便宜的模型上執行無聊的節點,並將你昂貴的 token 花在判斷力真正所在的地方。

在工作流程中,Claude 生成的每個子 Agent 都會繼承你的會話模型,除非腳本覆蓋它——所以預設情況下,一個大型執行會完全按照你的會話層級計費。單一 agent() 呼叫上的 model 選項告訴 Claude 將該節點路由到別處。
在大型執行前檢查 /model,然後讓 Claude 將扇出的重複性節點路由到較便宜的模型,並將合併節點保留在高階模型。這是一個槓桿,可以在不觸及其形狀的情況下,將一個消耗大量 token 的圖形從昂貴變為經濟。
13. 拓撲結構 就是 你的成本和延遲
圖形的形狀不僅僅是裝飾性的——它是影響牆上時鐘時間的最大單一槓桿。一個讓每個人都困擾的選擇:parallel() 與 pipeline()。一個 parallel() 屏障會讓所有東西等待最慢的節點完成,然後下一個階段才能開始。
一個 pipeline() 則讓每個項目獨立地流經所有階段,沒有屏障——項目 A 可以在階段 3,而項目 B 仍在階段 1。快的項目會提前完成,而不是在慢的項目後面閒置。

預設使用 pipeline()。 只在一個階段真正需要所有先前的結果在一起時才使用屏障——跨集合去重、基於總數的提前退出、一個比較「其他發現」的提示詞。「程式碼更乾淨」和「階段感覺是分開的」不是理由;屏障延遲是真實的、可測量的、浪費的時間。分開不等於同步。
14. 讓 Claude 繪製圖形—— 自我路由
最後一步是停止為你無法預先計劃的任務手動繪製圖形。
使用動態工作流程,你描述目標,Claude 自己編寫編排腳本——分解任務、選擇扇出、生成一個協調的子 Agent 群、並綜合結果。你得到一個為這次執行量身定制的圖形,而不是一個你希望適用的固定圖形。

有三種入門方式。在你的提示詞中說出「workflow」這個詞,Claude 就會為該任務編寫一個。執行一個已儲存或捆綁的——/deep-research 是一個在生產中運作的真實圖形:範圍界定 → 平行搜尋 → 擷取 → 對抗性驗證 → 綜合,正是本課程中的骨架。
或者開啟 ultracode 模式,Claude 會為會話中的每個重要任務規劃一個工作流程。當一次執行結果良好時,按 s 將其腳本儲存到 .claude/workflows/ 中——受版本控制、可按名稱重新執行、任何克隆此儲存庫的人都可以啟動的圖形。
1› 執行一個工作流程來稽核 src/routes/ 下每個路由的遺漏2驗證。為每個路由檔案生成一個 Agent,然後在報告前驗證每個發現。3● Claude 編寫了一個編排腳本 · 在背景啟動中…4/workflows — auth-audit · 執行中 ✓ 範圍界定 1/1 2.1k token ·54s ✓ 扇出 18/18 每個路由檔案一個 Agent ◯ 驗證 11/18 每個發現 3 票懷疑論者…6○ 綜合 0/1 等待驗證 會話保持回應 — 在 Agent 群執行時繼續工作
本週用 Claude 建立的六個圖形

- 跨每個路由的安全掃描。 Claude 為每個路由檔案生成一個子 Agent,每個 Agent 尋找遺漏的驗證檢查,然後一個驗證器階段在每個發現到達報告前進行確認。單一上下文無法容納的廣度。
- 使用 /deep-research 生成附有引用的報告。 Claude Code 中已內建的一個圖形。Claude 將你的問題分解成不同的角度,執行平行搜尋,對來源去重,然後在撰寫前用三票懷疑論者對抗性地驗證每個聲明。
- 逐個檔案移植一個模組。 Bun 天花板,擴展到你的儲存庫。Claude 將翻譯工作扇出到各個檔案,將測試套件作為每個檔案的閘門執行,並將失敗的循環回去——對抗性審查捕捉單次傳遞會遺漏的錯誤。
- 對 diff 進行對抗性審查。 Claude 根據 diff 大小進行路由:小變更進行一次快速檢查,大變更觸發一個完整的平行稽核,審查員從不同的角度——正確性、安全性、效能——進行審查,然後由評審團綜合。
- 按排程進行生態系統掃描。 儲存一次,永久重新執行。Claude 平行檢查多個來源——發布、部落格、討論——在屏障處按影響力排序,並撰寫摘要。在 .claude/workflows/ 中受版本控制,可按名稱啟動。
- 未知大小的探索。 你不知道有多少錯誤存在。Claude 平行執行尋找器,將每個新發現針對所有見過的東西去重,驗證倖存者,並持續循環直到兩輪沒有發現新東西——然後停止。
結論:
一個提示詞工程師提出一個問題。一個架構師繪製一個圖形。
線性 Agent 從來就不是天花板——它只是第一種形狀,每個人都因為它符合我們打字的方式而使用它。一行、一個腦袋、一次一件事。
一旦你能看到節點和邊,你就不再要求 Agent 做得更多,而是開始要求圖形做得更廣:在工作獨立的地方扇出,在信心重要的地方設置邊閘門,在判斷力不關鍵的地方分層使用模型。
大多數人會繼續在一條線上排隊步驟。那些學會繪製圖形的人將運行一個 Agent 群——並且永遠不會注意到其他人被卡住的那個天花板。





