Claude Code 2.1.147 から、サブエージェントを JavaScript のスクリプトで束ねる仕組みが入りました。公式ドキュメントには載っていない機能で、手がかりは changelog と同梱コードだけ。現行の 2.1.220(2026年7月25日)で実際に動かして分かったことを、スクリプトの書き方に寄せて残します。
モデルにファンアウトを任せると再現しない
サブエージェント自体は前からある機能です。困るのは、それを「いつ・何体・どの順で」呼ぶかをモデルが会話ごとに決めてしまう点。ここを固定するのが Workflow です。
サブエージェントは独自のコンテキストで動く
サブエージェントは、メインの会話とは別のコンテキストで作業する仕組み。公式ドキュメント “Create custom subagents” はこう書いています。
“Each subagent runs in its own context window with a custom system prompt, specific tool access, and independent permissions.”
独自のコンテキストとツール権限を持ち、Haiku のような速いモデルへ振ることもできる。この基本は カスタムサブエージェントの作り方 で触れたとおりです。
再現しないのは制御フローの側
ふつうにサブエージェントを投げると、何体を並列で走らせるか、どこでまとめるかはモデル任せ。同じ指示でも、3ファイルのうち1つを飛ばしたり、待ち合わせの順が変わったり。ファンアウトの形が毎回ぶれます。
Workflow はこの制御フローをコードに固定します。ループ・分岐・並列の形をスクリプトに書き、モデルは各 agent() 呼び出しの中身だけを担当する。要は、段取りはコード、考える所だけモデル。
Workflowスクリプトのプリミティブ
Workflow は export const meta = {...} で始まる JavaScript ファイル。/workflows から起動すると、ランタイムがバックグラウンドでスクリプトを走らせます。
最小構成はmetaとagent
export const meta = {
name: 'summarize-modules',
description: 'Summarize each source module',
phases: [{ title: 'Summarize' }],
}
phase('Summarize')
const files = ['auth.ts', 'billing.ts', 'search.ts']
const summaries = await parallel(
files.map(f => () => agent(`Summarize ${f} in 3 bullets.`))
)
return summaries
起動すると、進捗ツリーにこう出ます。
Summarize ▸ 3 agents
✓ agent#1 auth.ts
✓ agent#2 billing.ts
✓ agent#3 search.ts
→ 3 件の要約を返却
meta は変数も関数呼び出しも挟めない純粋なリテラル。name と description が必須で、description は起動前の確認ダイアログにそのまま表示されます。
agentは1体、parallelとpipelineは複数
agent(prompt) がサブエージェント1体。返り値はそのエージェントの最終テキストです。schema を渡すと構造化出力を強制でき、検証済みのオブジェクトが返る。複数を回すのが parallel() と pipeline()。
log・phase・args・budget
補助のプリミティブもあります。
phase(title)– 進捗表示のグループを切る。以降のagent()がそのグループにまとまるlog(message)– 進捗ツリーの上にナレーター行を出すargs– 起動時に渡した JSON 入力がそのまま入るグローバルbudget– トークン予算。budget.remaining()でループの深さを調整する
pipelineとparallelの使い分け
どちらも複数エージェントを回しますが、待ち合わせ(バリア)の有無で挙動が分かれます。ここを取り違えると、待たなくていい所で待つ遅いワークフローになります。
parallelは全員を待つバリア
parallel(thunks) は渡した全タスクを同時に走らせ、全部終わるまで次に進みません。5体走らせて1体が遅ければ、残り4体が終わっていても待たされる。全員の結果を揃えてから次に渡したいときの形です。
const all = await parallel(
DIMENSIONS.map(d => () => agent(d.prompt, { schema: FINDINGS }))
)
const deduped = dedupeByFileAndLine(all.flatMap(r => r.findings))
全 DIMENSIONS の結果が揃った後で deduped を計算する。重複排除は全件が要るので、ここはバリアが正しい。
pipelineはアイテムごとに流す
pipeline(items, stage1, stage2, ...) は各アイテムを独立してステージへ流します。ステージ間にバリアが無い。アイテムAが stage3 にいる間、アイテムBはまだ stage1、が許されます。全体の所要時間は「一番遅い1本の鎖」であって「各ステージの最遅の合計」ではない。
バリアが本当に要る場面だけparallel
やりがちなのが、ステージ間に毎回 parallel() を挟む書き方。
// アンチパターン: 中間の変換のためにバリアを入れる
const reviews = await parallel(DIMS.map(d => () => agent(d.prompt)))
const flat = reviews.flat() // 集約はしていない
const verified = await parallel(flat.map(f => () => agent(verify(f))))
この flat() はアイテムをまたいだ集約をしていない。ならステージの中に畳み込めます。
// OK: バリアなしで流す
const verified = await pipeline(
DIMS,
d => agent(d.prompt, { schema: FINDINGS }),
review => parallel(review.findings.map(f => () => agent(verify(f))))
)
進捗を見ると、ステージがまたがって走っているのが分かります。
Review ▸ bugs ✓
Verify ▸ bugs ✓ 2件検証
Review ▸ perf ✓ ← bugs の検証中に perf のレビューが動く
| parallel() | pipeline() | |
|---|---|---|
| ステージ間の待ち合わせ | あり(バリア) | なし |
| 次ステージの開始 | 全アイテム完了後 | そのアイテムが終わり次第 |
| 所要時間 | 各ステージの最遅の和 | 一番遅い1本の鎖 |
| 向く用途 | 全結果を集約・重複排除してから次へ | 独立した多段処理を流す |
| 失敗時 | その枠が null | そのアイテムが以降のステージを飛ばす |
バリアが正しいのは、次のステージが「全アイテムの結果」を要るとき。全件からの重複排除、合計0件なら以降を丸ごと飛ばす早期終了、他の結果と突き合わせる比較。それ以外は pipeline で待ち時間を削れます。
決定論はどこまで保証されるか
決定論的と言っても、エージェントの返答が毎回同じになるわけではありません。決まっているのは制御フローの形。ループ・分岐・ファンアウトの構造がコードに書かれているので、同じスクリプトと同じ入力なら段取りは再現します。
再現性を守るため、ランタイムは Date.now()・Math.random()・引数なしの new Date() を封じています。静的な正規表現チェックと実行時スタブの二段構え。時刻や乱数でフローが枝分かれすると再開時に形が変わるためで、タイムスタンプは args で外から渡す設計になっています。
中断して再開する
Workflow は同じセッション内なら途中から再開できます。完了済みの agent() 呼び出しは、(prompt, opts) が変わっていなければキャッシュ結果を即座に返す。編集した最初の呼び出しと、それ以降だけがライブで走ります。スクリプトを手直ししながら回すとき、前半の重い調査を作り直さずに済む。
ただしセッションをまたぐと再開はできません。Claude Code を閉じれば次回はまっさらから。長時間の探索を挟むなら、途中結果をファイルへ落とす前提で組みます。
組み込みワークフローと進捗の追い方
自作しなくても同梱されている
自作は必須ではありません。組み込みのワークフローが最初から同梱されています。
autopilot/bugfix/bughunt/bughunt-lite/dashboarddocs/investigate/deep-research/plan-hunter/review-branch
review-branch は現在のブランチの差分をレビューし、deep-research は複数の調査エージェントをファンアウトさせて1つの回答へまとめます。まずは組み込みの review-branch を1回流すと、pipeline と parallel の動きが実際に見える。
/workflowsで進捗を追う
実行中の様子は /workflows ビューで見られます。2.1.202(2026年7月6日)で付いたのが、このエージェント詳細ビューの f キーによるステータスフィルタ。同じ更新で workflow.run_id と workflow.name が OpenTelemetry 属性に加わり、OpenTelemetry 設定を入れていればどのワークフローがトークンを食ったかを実行単位で追えます。
サイズの上限は/configで決める
2.1.219(2026年7月24日)で workflowSizeGuideline という設定キーが入りました。動的ワークフローのサイズ目安は既定で「中」(エージェント15体未満を狙う)。/config から上限を変えたり無制限にできます。並行実行そのものは既定で最大20エージェント(CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS で変更可)、超えた分はキューで順番待ちになります。
まとめ
Workflow は、サブエージェントの段取りをコードに落とす仕組みです。Agent Teams がエージェント同士を会話させる方向なのに対し、Workflow は制御フローを1人の書き手が固定します。
- 制御フローはコードで決定論的。考える部分だけ
agent()がモデルに任せる parallel()はバリアあり、pipeline()はバリアなし。集約が要るときだけ parallel- 再現性のため
Date.now()・Math.random()は封じられている - 同一セッション内なら
(prompt, opts)一致で再開時にキャッシュが効く - 組み込み10種類と自作の両対応。進捗は
/workflows、上限はworkflowSizeGuideline
公式ドキュメントにはまだ無い機能。現行 2.1.220 で挙動を確かめながら、組み込みの review-branch から入ると形がつかめます。

