Claude Code 最近多了一個功能叫動態工作流(dynamic workflows):讓主代理在執行時,當場寫一支 JavaScript,生成並協調一群子代理——每個子代理有自己獨立的 context window 和一個聚焦的小目標。
我前幾天用它做了件很實際的雜活:評估四個候選部落格選題,看哪個跟我既有文章庫重複、哪個值得寫。這篇把那支 script 整個攤開,講三件事——怎麼寫、parallel 和 pipeline 怎麼選、跑一次燒多少 token。
為什麼不是「開更多分頁」那麼簡單
你可能會想,並行做事,開幾個對話視窗不就好了?
差別在 context。Claude Code 過去是「一個對話、一條 context」,所有東西擠在同一個上下文視窗。長任務這個模式有三個老毛病,官方發布時直接點名:智能惰性(做到一半宣布完工)、自我偏好偏差(驗證自己的產出時護短)、目標漂移(對話太長、尤其壓縮過後忘了最初目標)。
動態工作流的解法不是把單一 context 養得更肥,而是把活切開:每個子代理拿一塊乾淨的上下文,做一件聚焦的事,彼此不互相汙染。並行只是順帶的好處,真正的價值是這個隔離。
核心就五個函數
整個 API 你先記五個就能動:
agent(prompt, opts):派一個子代理,回傳它的最終輸出。opts裡給schema,它就被強制用結構化格式回傳。parallel(thunks):一批任務同時跑,全部跑完才往下走。這是一道柵欄(barrier)。pipeline(items, ...stages):每個項目各自流過所有階段,項目之間不設柵欄——A 還在第一階段,B 可能已經到第三階段。phase(title):把進度分組顯示,純粹方便你看跑到哪。schema:不是函數,是你丟給agent的一個 JSON Schema,決定它回什麼結構。
兩個一定要知道的限制,都寫在官方文件裡:meta 區塊必須是純字面值(不能放變數或函數呼叫);腳本裡不能用 Date.now() 和 Math.random()(它們會破壞工作流的可重播性,直接呼叫會丟錯)。
把 script 攤開
先是宣告區。meta 在最前面,名字、描述、階段,全是死的字面值:
1 | export const meta = { |
接著定 schema。這是整支 script 我覺得最關鍵的一塊——它決定子代理回給我的是一個能直接用的物件,而不是一段要我自己 parse 的中文:
1 | const EVAL_SCHEMA = { |
然後是主體——散出去四個子代理並行評估:
1 | phase('Evaluate') |
注意 CANDIDATES.map(... () => agent(...))——parallel 收的是一陣列「拿來就能呼叫的函數」(thunk),不是已經啟動的 Promise。這個細節錯了會變成全部序列跑,並行就沒了。
最後收齊四份評估,丟給一個綜合代理排序:
1 | phase('Synthesize') |
SYNTH_SCHEMA 我就不整段貼了,重點是它要求一個 ranking 陣列加一個 vaultGap 字串——同樣靠 schema 把「主編的判斷」框成結構化資料。
設計決策一:parallel 還是 pipeline
這是寫 workflow 最先要決定的事,我一開始也猶豫。
官方文件的預設建議是 pipeline,因為它不設柵欄、整體更快——項目 A 走完第一階段就能直接進第二階段,不用等 B。大多數多階段的活都該用它。
但我這個案子用了 parallel,因為綜合那一步必須等四份評估全部到齊才能排序。少一份,排出來的東西就是殘缺的。這正是少數真的需要 barrier 的場景:下游要的是「全部結果的總和」,而不是「單一項目流過管線」。
一句話判準:如果你的下一步只需要單一項目的前一步結果,用 pipeline;如果它需要前一階段所有項目的結果,才用 parallel 這道柵欄。
設計決策二:schema 把散文變物件
沒有 schema,子代理回你一段話,你還得自己從裡面挖出「價值幾分、重不重複」。給了 schema,驗證在工具呼叫那一層就做掉——格式不對它自己重試,回到你手上已經是乾淨的物件,可以直接 valid.filter(e => e.valueForReaders >= 7) 這樣用。
舉個實際的——這是其中一個子代理回來的東西,原封不動(reason 我截短了):
1 | { |
你看到的不是一段「我覺得這題還行因為⋯⋯」的散文,而是欄位齊全、型別正確的物件——valueForReaders 真的是個 7 不是字串,coveredInVault 真的是個布林。把判斷框成 schema,下游的 filter、sort 才接得住。
那個 evals.filter(Boolean) 也不是裝飾。依 runtime 規格,子代理中途被略過、或它自己出錯時會回傳 null,過濾掉才不會把 null 餵進下一步。我這次五個全活,但生產環境你得假設它會掉。
跑一次多少錢
講完怎麼寫,講代價。這趟的真實數字:
| 指標 | 數字 |
|---|---|
| 子代理數 | 5(4 評估 + 1 綜合) |
| 工具呼叫 | 31 次 |
| 耗時 | 133 秒 |
| token | 375,328 |
兩分鐘換一份選題排序,代價 37 萬 token。這不便宜——而且要講清楚,37 萬是 token 量,不是帳單金額;你實際付多少,看你的訂閱方案、以及子代理跑在哪個模型上。官方自己也講白了:「大多數傳統 coding 任務不需要五個審稿員。」
所以我的判準是這樣:
- 該用:深度研究、跨檔案大遷移、根因調查、大規模去重——這些單一 context 撐不住、會惰性或漂移的活。
- 不該用:單檔編輯、你心裡已經有答案、或一個子代理就能搞定的事。
補一個跟隔離有關的點:如果你的子代理會並行改同一批檔案,記得讓它們各跑一個 worktree(agent(prompt, { isolation: 'worktree' })),否則它們會互相蓋掉對方的修改。並行很爽,但前提是任務之間真的不共享狀態。
寫在最後
動態工作流不難寫,難的是判斷什麼時候值得動用它。五個函數、一個 schema,半小時就能跑起來;但 37 萬 token 的帳單會逼你誠實面對「這活到底要不要五個腦袋」。
最後說個題外話。我用這支 workflow 取材時,還順手踩了一個關於我自己的坑——我一度咬定其中一個子代理在幻覺,寫成文章、還過了兩輪 AI 審稿,最後翻開 transcript 才發現它老老實實做了查證,幻覺的是我。那是另一篇文章了。
工具值得學。只是學會之後,別像我一樣,只看它最終吐了什麼就下結論。







