AI 工程

把驗證寫進程式碼:拆解 Anthropic 官方 Dynamic Workflows cookbook,$3.29 跑完一場多 agent 事實查核

你一定遇過這個場景:你叫 coding agent「把 src/routes/ 底下每個 handler 都檢查一遍有沒有漏掉權限驗證,每個發現都要再驗證一次才寫進報告」。它很努力。前五個檔案認真讀,第十二個開始摘要,到第二十個時 context 已經被前面的檔案內容塞滿,那句「每個發現都要再驗證一次」早就被擠出視窗外了。你拿到一份看起來很完整的報告,但你其實不知道它到底掃了幾個檔案、驗證那一步有沒有真的跑過。

問題不在模型不夠聰明。問題在於計畫住在 context window 裡。只要計畫是「模型記得要做的事」,它在壓力下就會被犧牲——而且犧牲得無聲無息。

2026 年 8 月 3 日,Anthropic 官方 cookbook 併入了 PR #806(作者 mattmccarley-ant,標題 "Add the dynamic workflows cookbook to the Agent SDK series"),新增 claude_agent_sdk/08_Dynamic_workflows.ipynb。它示範的解法很直接:把計畫搬進程式碼

順帶一個查證時發現的細節:這個 repo 已經從 anthropics/anthropic-cookbook 改名成 anthropics/claude-cookbooks(舊網址仍會 301 轉址,所以舊連結不會壞),但 claude_agent_sdk/README.md 裡的 git clone 指令目前還寫著舊名。抓取當下 star 數為 50,925。

Dynamic Workflow 是什麼:Claude 幫你寫編排腳本

一句話講完機制:你用自然語言描述任務,Claude 寫出一支 JavaScript 編排腳本,透過 Workflow 工具交給 runtime,runtime 在背景執行它。

腳本負責三件事:派 agent(平行或分階段)、把結果存進變數、用純 JavaScript 做中間的接合邏輯(過濾、迴圈、驗證分流)。而每一個被派出去的 subagent,都是一個完整的 Claude Code agent——乾淨 context 起步,只看得到腳本餵給它的那段 prompt,在你 session 的工作目錄下、用你設定的工具 allowlist 幹活。

這跟 subagent 的差別,cookbook 用一個問題就講清楚了:誰拿著計畫?

用 subagent 時,Claude 是編排者,它一回合一回合決定要委派什麼,而且沒有任何機制保證它委派了每一塊、最後真的合併了結果、或真的做了驗證。每個 worker 的結果都會回到主 agent 的 context——四個委派沒問題,四百個就爆了。

用 workflow 時,編排者是腳本。迴圈、分支、中間結果都由腳本自己拿著,Claude 的 context 只留最後那份答案。

運作原理:cookbook 的事實查核範例

Dynamic Workflow 四階段運作流程:Extract → Verify 平行 → Skeptic → Report

cookbook 用一個虛構的自行車電商 OrbitCart 當例子(notebook 明講公司名稱、指標、客戶資料全是為了示範產生的假資料)。工作目錄裡有一份 investor_update.md 草稿,裡面 10 條編號宣稱,還有 sources/ 目錄放四份原始資料:月銷售 CSV、客戶調查、稼動率報告、媒體報導。

草稿裡刻意埋了雷:第 1、4、5、6 條跟原始資料矛盾,第 7、8 條根本無從查證,其餘有來源支持。

工作流被要求長成這樣(這也是 fan-out + 對抗式驗證 這個 pattern 的標準形狀):

  1. EXTRACT:一個 agent 讀草稿,把 10 條宣稱抽成結構化輸出
  2. VERIFY一條宣稱派一個 agent,平行跑,每個都用乾淨 context 重新讀四份來源,回傳 confirmed / contradicted / unverifiable,而且必須引用原句
  3. SKEPTIC只對 confirmed 的判決再派一個「唱反調」的 agent,重讀被引用的檔案,專門找反證。找到了,判決直接降級成 contradicted
  4. REPORT:一個 agent 把所有判決編成 markdown 表格 + 待修清單,return 出去

關鍵在第 3 步的分流:只有 confirmed 的才進 skeptic 階段,這個過濾是腳本裡一行純 JavaScript,精確、即時、零 token 成本。如果讓模型自己記著「只對確認過的做反駁」,你既不能保證它會做,也不能保證它不會多做。

而且,「別驚訝 workflow 會比你的標準答案更嚴格」——cookbook 自己這樣提醒,實際跑出來也印證了:標準答案認為第 10 條(「短暫的五月服務中斷期間零資料遺失」)是有來源支持的,但實跑結果把它標成 Contradicted (framing),理由是稼動率報告寫的是 5 小時 58 分的中斷,稱不上「短暫」——零資料遺失那半句才是準確的。

第 6 條更值得玩味:草稿引用媒體說 OrbitCart 是「北美成長最快的自行車零售商」,原文其實是「北美成長最快的線上自行車零售商『之一』」。一個趕時間的單一 agent 很容易滑過「the fastest-growing」跟「one of the fastest-growing」的差別。一個 context 裡沒別的東西的專職驗證 agent、再被一個唱反調的 agent 挑戰過,就很難混過去。這份精準來自工作流的結構,不是來自更聰明的模型。

手把手:從 Agent SDK 觸發一個 workflow

先講版本門檻,這是最容易卡住的地方:dynamic workflows 需要 Claude Code CLI v2.1.154 以上,而 claude-agent-sdk 0.2.90 之後的版本綁的 CLI 才滿足。cookbook 裡執行輸出顯示的是 0.2.125。

pip install -U "claude-agent-sdk>=0.2.90" python-dotenv

從 SDK 觸發只需要兩件事:

第一,把 Workflow 放進 allowed_tools SDK 會自動核准清單上的工具:

from claude_agent_sdk import ClaudeAgentOptions

def workflow_options() -> ClaudeAgentOptions:
    return ClaudeAgentOptions(
        cwd=str(WORKSPACE),
        model="claude-sonnet-5",
        allowed_tools=["Read", "Write", "Edit", "Glob", "Grep", "Workflow"],
        permission_mode="acceptEdits",
        max_turns=40,
    )

第二,在 prompt 裡用白話要求一個 workflow:「use a workflow to…」。Claude 會把這種直接請求當成「改用腳本編排、而非一回合一回合硬幹」的 opt-in。

在互動式 CLI 裡還多兩個觸發方式:prompt 裡打關鍵字 ultracode,或者 /effort ultracode(需 v2.1.203+)讓 Claude 在這個 session 的每個實質任務都自動規劃 workflow。但從 SDK 進來,白話講清楚就夠了。

這裡有個安全設計值得注意:ultracode 關鍵字只在你親手輸入的 prompt 裡算 opt-in。用 -p 傳進去的 prompt、SDK 沒標記為 human origin 的 prompt、排程任務、webhook 或 PR comment 轉進來的內容,都不會觸發(v2.1.210 之前這些路徑都會觸發,等於是個補起來的洞)。

真正的招式在 prompt 裡

這是我認為整份 cookbook 最值得抄的部分:在 workflow 模式下,你的 prompt 大部分篇幅描述的是「工作的形狀」,而不是工作本身。 你描述什麼樣的 harness,就得到什麼樣的 harness。

cookbook 的 FACT_CHECK_PROMPT 有幾個具體技巧直接可以照抄:

  • 明確編號分階段,並且點名每階段的 agent 數量關係:「one agent per claim, running in parallel」「for every 'confirmed' verdict, one skeptic agent」
  • 明確要求 structured output,並把 enum 值直接寫出來(confirmed / contradicted / unverifiable
  • 明確要求引用證據:「Verifiers must quote the exact lines they relied on」
  • 預先點出容易滑過的陷阱:「Pay attention to subtle differences between what a source says and what the draft claims it says」
  • 明確交代路徑慣例:「Refer to every file by relative path… do not embed absolute paths」——這條很實際,因為腳本和 agent prompt 裡塞絕對路徑會讓工作流不可攜

實跑結果:Claude 產出一支 152 行的編排腳本,跑完花 2.5 分鐘、$3.29,過程中約 549,412 tokens、56 次以上的 agent 工具呼叫。(這些數字來自 notebook 內附的已執行輸出,不是我自己跑的;notebook 自己標示跑完整本大約 $2–$4。)

Claude 寫出來的腳本長什麼樣

腳本會被 runtime 寫成一個實體檔案,放在 ~/.claude/projects/ 底下你這個 session 的目錄裡,路徑會出現在 Workflow 工具的回傳裡。你可以讀它、diff 它、改它、再叫 Claude 用改過的版本重跑。

腳本的積木就這幾個:

積木 作用
export const meta = {name, description, phases} 宣告名稱與階段;進度 UI 會依 phase 分組
agent(prompt, options) 派一個乾淨 context 的 subagent;options 可設 labelphase、JSON schemamodel
parallel([...]) 一批 agent 並行,等全部跑完才往下(barrier)
pipeline(items, stage1, stage2, ...) 每個項目獨立走完所有階段——A 可以在 stage 2,B 還在 stage 1
phase("...") 標記接下來的 agent 屬於哪個階段
中間的純 JavaScript 過濾、去重、合併、迴圈——精確、即時、零 token
return {...} 腳本回傳什麼,就是回到你 session 的東西

實跑產出的腳本裡,事實查核那段是這樣接的(節錄):

const results = await pipeline(
  claims,
  (claim) => agent(`...Claim #${claim.number}: "${claim.text}"...`,
    { label: `verify-${claim.number}`, phase: 'Verify', schema: VERIFY_SCHEMA }),
  (verifyResult, claim) => {
    if (!verifyResult || verifyResult.verdict !== 'confirmed') {
      return Promise.resolve(verifyResult ? { ...verifyResult, skeptic_reviewed: false } : null)
    }
    return agent(`...try hard to REFUTE this confirmation...`,
      { label: `skeptic-${claim.number}`, phase: 'Skeptic', schema: SKEPTIC_SCHEMA })
      .then((skepticResult) => { /* 反駁成功就把 verdict 降級 */ })
  }
)
const finalResults = results.filter(Boolean).sort((a, b) => a.number - b.number)

注意兩個細節。第一,它用的是 pipeline 不是 parallel:第 3 條宣稱可以已經在被反駁,第 7 條還在驗證,不必等所有驗證都跑完才開始反駁階段。第二,.filter(Boolean)——agent() 在你中途停掉它、或它撞上不可恢復的 API 錯誤時會 resolve 成 nullpipeline 會把 null 留在結果陣列裡,所以幾乎每個腳本結尾都得濾一次。這是官方文件明確點名的坑。

數據與限制:誠實的部分

runtime 有幾條硬限制,都寫在官方文件裡:

  • 同時最多 16 個 agent(CPU 核心少的機器會更少),超出的排隊等空位
  • 單次 run 上限 1,000 個 agent——這是防呆用的失控迴圈上限,不是給你當設計目標
  • 腳本本身碰不到檔案系統和 shell,只有它派出去的 agent 可以;腳本只負責協調
  • 不能跑到一半要求使用者輸入。要階段之間簽核,就把每個階段拆成獨立的 workflow

Resume 的行為有個反直覺的規則,值得先知道:重播是按 agent 的啟動順序走的,快取結果只算到「第一個沒跑完的 agent」為止,在它之後啟動的全部重跑——即使那些已經跑完了。 假設腳本依序啟動 A、B、C、D,你在 B 還在跑時停掉:resume 時 A 讀快取,B 重跑(它沒完成),C 和 D 也重跑(它們在 B 之後啟動)。推論很實用:把工作切成很多小 agent 的 workflow,比一個長 agent 的 workflow 保住更多進度。

另外 v2.1.219 起有個預設值會影響你拿到的工作流規模:size guideline 預設是 medium(Claude 會朝「少於 15 個 agent」設計)small 是少於 5、large 少於 50、unrestricted 不給建議。這是建議不是上限,prompt 講明要更大規模還是會覆蓋它。用 /config/config workflowSizeGuideline=small 調整。

還有一個成本護欄:v2.1.203 起,當一個工作流排定超過 25 個 agent,或預估 token 總量超過 150 萬時,任務面板會顯示 Large workflow 警告。但它只是提醒,不會暫停或限制執行;開了 ultracode 的 session 則不顯示(因為你已經主動選擇了大規模執行)。

我沒有實際跑過這份 notebook,所以上面所有數字都標明出處:$3.29 / 2.5 分鐘 / 152 行 / ~549K tokens 來自 cookbook 內附的執行輸出,限制與版本號來自 code.claude.com 官方文件。

什麼時候該用、什麼時候別用

單一 Agent / Subagents / Dynamic Workflow 三者對比

cookbook 給的判斷準則很克制,我覺得比它推銷 workflow 的部分更有價值:

  • 單一 agent:任務塞得進一個 context window、不需要獨立驗證。大多數任務住在這裡。
  • Subagents:你需要幾個專職 worker(研究員、審查員),而且主 agent 應該隨著結果進來調整計畫
  • Dynamic workflow:任務長到超出一個 context window(項目多、來源多)、需要不能被跳過的驗證、或者這是一個你會重複跑的流程

反過來說,別用的訊號也很清楚:任務只有三五步、你需要中途插手決定方向、或者你只是覺得「多派幾個 agent 應該比較厲害」。workflow 派很多 agent,token 就是實實在在多燒。cookbook 的說法很直白:「驗證每一項的工作流,就是比什麼都不驗的工作流貴,而那個旋鈕是在 prompt 裡轉的。」

三個實際的成本控制習慣:

  1. 先在小切片上跑。一個目錄而不是整個 repo,一個窄問題而不是大題目,看完 /workflows 裡的 token 用量再決定要不要放大
  2. 明確指定便宜模型跑機械性階段。所有 agent 預設用 session 的模型;腳本可以逐階段指定 model,或用 CLAUDE_CODE_SUBAGENT_MODEL 環境變數一次覆蓋全部(這個環境變數優先權最高)。跑大工作流前先確認 /model 是你想要的那個
  3. 在 prompt 裡就把驗證強度講清楚,因為那正是成本的主要來源

對工程團隊的意義:腳本是可以 commit 的資產

這是我認為最被低估的一點。編排腳本是一個檔案。

在 CLI 裡跑 /workflows,選中那次執行,按 s,就能把腳本存成一個命令:

  • .claude/workflows/(專案內)——commit 進 repo,所有 clone 的人共用
  • ~/.claude/workflows/(個人)——每個專案都能用,只有你看得到

存完之後它就是 /<name>,會出現在 / 自動補全裡。同名時專案版優先。monorepo 裡(v2.1.178 起)會寫進工作目錄到 repo root 之間最近的既有 .claude/workflows/,載入時也會沿路全部載入,同名以最靠近工作目錄的為準。

saved workflow 還能吃參數:腳本讀取名為 args 的全域變數,Claude 會把結構化資料直接傳進去,所以腳本可以直接對 args 呼叫陣列/物件方法,不必先 parse。

Run /triage-issues on issues 1024, 1025, and 1030

要跨團隊散佈,就包成 plugin:腳本放在 plugin root 的 workflows/ 目錄,會被 namespace 成 /acme-tools:release-audit

換句話說,「我們團隊怎麼審 PR」這件事,可以從一段每次都要重講的 prompt,變成一支版本控管的腳本。 這比多派幾個 agent 有價值得多。

幾個可以直接照抄的起手 prompt(來自官方文件的範例形狀):

use a workflow to audit every route handler under src/routes/ for missing
authentication checks, and adversarially verify each finding before reporting it

use a workflow to run npx tsc --noEmit and keep fixing the reported errors
until the type check passes or two rounds in a row make no progress

use a workflow to migrate every component under src/components/ from
styled-components to Tailwind, working on each file in its own isolated copy

use a workflow to review every file changed in this PR for correctness issues,
then merge the per-file findings into one ranked summary

最後兩個實務提醒。第一,subagent 一律以 acceptEdits 模式執行,並繼承你的工具 allowlist,不管你 session 是什麼權限模式——所以 allowlist 該在開跑前就設好,不然不在清單裡的 shell 指令、web fetch、MCP 工具還是會在中途跳出來問你。第二,notebook 觀察到一個行為:workflow agent 想寫 SUMMARY.md 這種報告型檔案時會被要求「改成用回傳值交出來」,因為結果要進腳本變數;真正的工作產物(轉換後的文件、程式碼、資料檔)則正常寫進磁碟。

想關掉整個功能:/config 切掉、~/.claude/settings.json"disableWorkflows": true、或 CLAUDE_CODE_DISABLE_WORKFLOWS=1;組織層級可用 managed settings。

如果你想先感受一下而不想自己寫 prompt,Claude Code 內建了 /deep-research 這個 workflow——它把網路搜尋依不同角度扇出、交叉比對來源、對每條宣稱投票,最後回傳一份沒通過交叉比對的宣稱已被濾掉的引用報告。跑一次,然後 /workflows 進去看每個 phase 的 agent 在幹嘛,比讀十篇文章都快。

來源

整理:DataAgent · Coding Agent 實戰教學

發表迴響

%d 位部落客按了讚: