「Issue 番号だけ渡したら、あとは PR まで勝手に進んでほしい」
開発者なら一度は夢見るやつです。私たちはこれを、Claude Code(Pro $20/月、Max $100-200/月)に追加された Workflow 機能 で実際に動かしています。GitHub の Issue 番号をひとつ渡すと、要件分析、実装計画、コーディング、テスト、品質評価、PR 作成までが無人で進む。多い日には 1 日で 50 commit 分の issue 処理がこのフローで流れます。
Workflow 機能は .claude/workflows/ に置いた JavaScript ファイルで、複数の subagent をオーケストレーションする仕組みです。リリースから日が浅く、「skill や subagent と何が違うの?で、実際どうなの?」という段階の方が多いはず。この記事では、実際に Issue→PR の自動化を運用しているスクリプトを例に、この機能で何ができるか、どこに発想の転換があるかを紹介します。
先に結論を言うと、Workflow 機能の本質は 複数エージェントの段取りを、AI の自律判断ではなく、ただの JavaScript として書ける ことです。地味に聞こえますが、マルチエージェント自動化の信頼性はここで決まります(JavaScript は気分でフェーズを飛ばしません)。
この記事で学べること
- Workflow 機能(
.claude/workflows/)の基本構造と、skill / subagent との役割の違い agent()/parallel()/workflow()という 3 つの API で何ができるか- 「判断は LLM、進行はスクリプト」という設計が効く理由
- モデル一括切替・テレメトリ記録・人間への差し戻しなど、実運用で効いたパターン
- workflow runtime の制約(ESM import 不可など)とその回避策
Workflow 機能とは — skill・subagent と何が違うか
Claude Code には、エージェントに仕事をさせる仕組みがすでに複数あります。位置づけを整理するとこうなります。
| 仕組み | 実体 | フローを制御するのは誰か |
|---|---|---|
| skill | Markdown の手順書(SKILL.md) | LLM(手順書を読んで解釈・実行) |
| subagent | 単機能の作業者定義(.claude/agents/*.md) | 呼び出し側の LLM |
| workflow | JavaScript スクリプト(.claude/workflows/*.js) | スクリプト(決定論) |
skill はよくできた仕組みですが、手順書をどの順で・何回・どこまで実行するかは LLM の解釈に委ねられます。10 回中 9 回は正しく動いても、1 回は「テストが通らなかったので評価は省略しました」のような創造的な手抜きが起きる。フェーズが多く、エージェントを何体も束ねる長いフローほど、この揺らぎが致命傷になります。
Workflow はこの「段取り」の部分をコードに移します。ファイルは meta を export してフェーズを宣言するところから始まります。実物の冒頭はこんな形です。
export const meta = {
name: 'dev-flow',
description: 'Issue から LGTM まで: 分析 → 計画 → 実装 → test green → 評価 → PR',
phases: [
{ title: 'Setup' }, { title: 'Analyze' }, { title: 'Plan' },
{ title: 'Implement' }, { title: 'Validate' }, { title: 'Evaluate' },
{ title: 'PR' },
],
}
これで /dev-flow 123 のようにスラッシュコマンドとして起動でき、args 経由でパラメータを受け取れます。進捗はフェーズ単位で可視化され、log() で任意のログも出せます。
中核 API は agent() — LLM の判断を JSON で受け取る
workflow script の中心は agent() です。プロンプト・agent 種別・そして 返り値の JSON Schema を渡して subagent を起動すると、schema に沿った JSON が返ってきます。
phase('Analyze')
const req = await agent(
`Issue の要件・受入条件を抽出し、実装規模を micro / standard / complex で評価して返せ`,
{ agentType: 'dev-runner', schema: REQ, label: 'analyze' },
)
// req.acceptance_criteria や req.shape を、ただの JS 変数として扱える
ここが発想の転換点です。エージェントの出力が 型のあるデータ として JavaScript 側に着地するので、その後の分岐・ループ・集計はぜんぶ普通のプログラミングになります。中間結果を外部の状態ファイルに書き出す必要もなく、スクリプトの変数に持つだけです。
たとえば実運用のスクリプトでは、抽出した受入条件が空だったり、曖昧な点が閾値を超えたりしたら、その場で構造化した値を返して止まります。
if ((req.acceptance_criteria ?? []).length === 0 || ambiguities.length > AMBIGUITY_MAX) {
return {
status: 'needs_clarification',
missing_context: ambiguities,
// 呼び出し元が人間に確認し、Issue を更新して再起動する
}
}
「曖昧なまま実装に突っ込んで、出来上がってから人間が泣く」コースを、if 文 1 つで止められるわけです。
設計思想: 判断は LLM、進行は JavaScript
実運用している Issue→PR の workflow は、おおまかに次のフェーズで進みます(実物にはこの間にセキュリティ検査や merge 判定のフェーズも挟まっています)。
| フェーズ | やること | 呼ぶエージェント |
|---|---|---|
| Setup | git worktree とブランチの作成 | 軽量 runner(haiku 固定の別定義) |
| Analyze | Issue 分析・実装規模の判定 | runner(分析 skill を実行) |
| Plan | 実装計画の作成 ⇄ 計画レビュー | planner / plan-reviewer |
| Implement | 計画タスクを直列・並列で実装 | implementer |
| Validate | テスト実行 → green になるまで修正 | runner / implementer |
| Evaluate | 独立した評価者による品質判定 | evaluator |
| PR | commit と PR 作成(既存 skill を実行) | runner |
流れを図にするとこうなります。表では見えない 差し戻しループ と 人間への出口 が、実はこの workflow の本体です。
計画の質を判断する、コードを書く、品質を評価する——ここは LLM の仕事です。一方で「計画レビューを最大何回まで回すか」「評価が fail なら計画と実装のどちらに差し戻すか」「どの規模ならレビューを省略してよいか」はスクリプトの仕事。ループ上限はプロンプトの口約束ではなく定数です。
const PLAN_MAX = 8 // 計画レビュー上限
const EVAL_MAX = 10 // 評価差し戻し上限
const GREEN_MAX = 3 // test green までの実装差し戻し上限
「最大 3 回まで再試行してください」とプロンプトに書いて祈る方式との違いは、運用すると体感できます(for 文は 7 回目あたりで飽きたりしません)。
実装規模も micro / standard / complex の 3 段階に分類し、micro なら計画レビューと評価をスキップ、complex ならフルのレビュー反復を回す、という深さの切替を if 文で行っています。全部の Issue に最重装備のゲートを通すと時間もコストも無駄になるので、この出し分けが決定論でできるのは大きいです。
parallel() — 独立タスクだけを並列にする
計画フェーズの返り値は、タスクを serial / parallel の 2 レーンに分解した JSON です。依存のないタスクは parallel() で同時に実装させます。
for (const t of plan.serial) {
results.push(await agent(implPrompt(t), { agentType: 'implementer', schema: IMPL }))
}
const par = plan.parallel.map((t) => () =>
agent(implPrompt(t), { agentType: 'implementer', schema: IMPL }))
const parResults = await parallel(par)
ポイントは、並列の安全条件をスクリプト側で強制していることです。並列レーンに置けるのは変更ファイルが互いに重ならないタスクだけで、ファイルが衝突するタスクを計画が並列に置いてきたら、orchestrator 側が検出して直列レーンへ降格させます。LLM に「衝突しないように分解してね」とお願いするだけでなく、信用せずに検査するのが安全です。
また、worktree は 1 つを全エージェントで共有します。エージェントごとに worktree を分けると成果物が分散して統合作業が発生するためで、「並列にするのはタスク、分離するのはファイル」という整理です。worktree を使った並列開発そのものはworktree 並列開発の記事に書きました。
workflow() — 反復ループをサブワークフローに部品化する
PR 作成後の「レビュー ⇄ 修正を LGTM まで反復する」処理は、別ファイルの workflow に切り出して呼んでいます。
const iterate = await workflow('pr-iterate', { pr: pr.pr_number })
呼ばれる側は単体でも /pr-iterate <PR番号> として起動できるので、手で作った PR にだけ自動レビューを回す、という使い方も成立します。なおネストは 1 段までで、サブワークフローの中でさらに workflow() を呼ぶとエラーになります(実物のコードには「ここに足すと 2 段になるので入れないこと」というコメントが置いてあります。未来の自分への牽制です)。
実運用で効いた Workflow ならではのパターン
モデルの一括切替が定数 1 行
agent() は呼び出しごとに opts.model でモデルを上書きできます。私たちは品質ゲート系の 4 エージェント(計画・計画レビュー・評価・PR レビュー)のモデルをスクリプト冒頭の定数で一括管理しています。
const QUALITY_MODEL = 'fable' // 戻すときはこの 1 行を 'opus' にする
const plan = await agent(prompt,
{ agentType: 'dev-planner', model: QUALITY_MODEL, schema: PLAN })
直近では、品質ゲート系エージェントを新モデルへ切り替える試験運用をこの形で始めました。レビュー反復の削減効果をテレメトリで実測し、ダメなら定数を書き戻すだけで完全に元へ戻る——A/B 実験のスイッチが定数 2 行(メインとサブの workflow に 1 行ずつ)で済むのは、段取りがコードである恩恵そのものです。
テレメトリの記録もコードで確定実行
「完走したらログを記録してね」とエージェントに頼む方式は、たまに忘れられます(正直、人間に頼んでも忘れるので責められません)。workflow なら完走時の処理もただのコードパスです。実運用では、実装規模の判定結果・計画と評価のイテレーション数・評価 verdict などを完走時に journal スクリプトへ記録しています。ループ上限のような決め打ちパラメータを、勘ではなく実測で調整するための原資料になります(記録に失敗しても警告ログだけ出して正常終了させる、という匙加減も if 文で書けます)。
行き止まりは throw ではなく構造化 return
workflow の返り値も JSON です。要件が曖昧で進めないときは例外で死ぬのではなく、status: 'needs_clarification' と「何が分からなかったか」のリストを返します。呼び出し元のセッションがそれを受けて人間に質問し、Issue を更新してから再起動する。worktree は保持されるので、途中までの作業は無駄になりません。自動化の失敗を「人間が次に何をすべきか分かる形」で返せるのは、返り値に型がある設計の効用です。
ハマりどころ: workflow runtime の制約
ここまで「ただの JavaScript」と書いてきましたが、白状すると実行環境は Node.js そのものではありません。ただの JavaScript のはずが、import すら書けない。運用で踏んだ制約を 3 つ挙げます。
1. ESM import が使えない。 workflow loader はモジュール import を解決しないため、複数の workflow で共有したい純関数は各ファイルへインラインコピーする必要があります。手作業のコピーは必ず腐るので、私たちはマーカー区間を canonical ファイルから自動再生成する generator を作り、生成区間が canonical と全文一致することを CI のテストで保証しています。直近の改修でこの同期を generator 化したのは、関数単位の正規表現抽出が template literal で誤抽出する、手書きの関数名リストに追加し忘れると新規関数が無監視になる、といった手動同期の弱点をまとめて潰すためでした。
2. fs も Date.now() も無い。 ファイル書き出し API が無いので、生成した長文の PR コメントを投稿したいときは、本文を delimiter 付きでプロンプトへ埋め込み、agent 側に Write tool で一時ファイルへ保存させてから --body-file で投稿させる、という間接手順になります。shell の echo や heredoc を経由させないのは、コードフェンスやバッククォートを含む本文がそこで壊れるからです。
3. effort は指定できない。 agent() の opts には model はあっても推論の深さ(effort)はありません。深さを変えたいエージェントは frontmatter 側で固定します。モデルを恒久的に固定したい用途も、定数 override ではなく専用のエージェント定義を作って agentType を切り替える方が素直です。私たちは worktree 作成のような機械的ステップ用に、軽量モデル固定の runner を別定義しています。
まとめ
Workflow 機能は、マルチエージェント自動化の主導権を LLM からスクリプトへ取り戻すための仕組みです。
- 段取り(フェーズ遷移・ループ・分岐・並列)は JavaScript が決定論で実行する
- 判断(計画・実装・評価)は
agent()で LLM に委譲し、返り値は JSON Schema で受け取る - 反復処理は
workflow()のサブワークフロー(ネスト 1 段まで)に部品化する
LLM に任せる範囲が狭くなるほど自動化は退屈になり、退屈になるほど信頼できるようになります。このパイプラインの品質ゲートをエージェントがすり抜けようとした攻防は品質ゲートの抜け道の記事に書きましたが、その攻防が成立する土台も「ゲートが必ず実行される」という workflow の決定論性です。複数エージェントの自動化が「うまく動くときはすごいが、たまに脱線する」段階で止まっている方は、段取りをスクリプトへ移すこの機能から試してみてください。



