第9章 — Workflow: オーケストラの指揮者
60ファイルのリファクタリング
「全 API ルートの認証方式を JWT から OAuth に移行して」
CTO からの指示は明快だったが、影響範囲は広大だった。60ファイル、200箇所以上の変更。一つの Claude Code セッションで処理するにはコンテキストが足りない。一つずつ手動で指示するのは現実的ではない。
「これは Workflow の出番だ」とミサキ。
Workflow とは
Workflow は、複数のサブエージェントの実行をスクリプトで制御する仕組みだ。JavaScript で書かれた台本に従い、エージェントを並列に走らせたり、順序立てて連鎖させたりできる。
Workflow の4つの構成要素
| 要素 | 説明 | 用途 |
|---|---|---|
agent() | サブエージェントを1体起動 | 調査、変換、検証 |
parallel() | 複数タスクを同時実行(バリア付き) | 全結果が必要なとき |
pipeline() | 各アイテムを段階的に処理 | ファイルごとの変換 |
phase() | 進捗グループの切り替え | UI表示と整理 |
pipeline vs parallel: 正しい選択
Workflow 設計で最も重要な判断が「pipeline を使うか、parallel を使うか」だ。
pipeline: デフォルトの選択
各アイテムが独立に処理できるなら pipeline が最適。アイテム A がステージ3にいる間に、アイテム B はまだステージ1にいてよい。
// 60ファイルを並列にリファクタリング
const results = await pipeline(
files,
// ステージ1: 各ファイルを分析
(file) => agent(`Analyze ${file} for auth patterns`, {
label: `analyze:${file}`,
phase: 'Analyze'
}),
// ステージ2: 変換
(analysis, file) => agent(`Refactor ${file}: ${analysis}`, {
label: `refactor:${file}`,
phase: 'Refactor',
isolation: 'worktree'
})
)parallel: バリアが必要なとき
全結果を集めてから次の処理をしたい場合のみ parallel を使う。
// 全レビューを集めてから重複排除
const allFindings = await parallel(
dimensions.map(d => () =>
agent(d.prompt, { schema: FINDINGS_SCHEMA })
)
)
const deduped = dedup(allFindings.flat())WARNING
parallel はバリア(全完了を待つ)のため、最も遅いタスクに全体が引きずられる。pipeline なら各アイテムが独立に進むので、壁時計時間が最短になる。迷ったら pipeline を選べ。
実践: 大規模リファクタリング Workflow
ユウキが JWT → OAuth 移行のために書いた Workflow:
export const meta = {
name: 'auth-migration',
description: 'JWT 認証を OAuth に移行する',
phases: [
{ title: 'Discover', detail: '影響ファイルを特定' },
{ title: 'Migrate', detail: '各ファイルを変換' },
{ title: 'Verify', detail: 'テストと監査' },
],
}
// Phase 1: 影響範囲の特定
phase('Discover')
const discovery = await agent(
'grep で JWT 関連のパターンを検索し、影響ファイルの一覧を返せ',
{ schema: { type: 'object', properties: {
files: { type: 'array', items: { type: 'string' } }
}}}
)
log(`${discovery.files.length} ファイルを発見`)
// Phase 2: 各ファイルを並列に変換
phase('Migrate')
const results = await pipeline(
discovery.files,
// 分析
(file) => agent(
`${file} の JWT 認証パターンを分析し、OAuth への変換計画を立てよ`,
{ label: `plan:${file}`, phase: 'Migrate', effort: 'low' }
),
// 変換(worktree で隔離)
(plan, file) => agent(
`${file} を以下の計画に従って変換せよ: ${plan}`,
{ label: `migrate:${file}`, phase: 'Migrate', isolation: 'worktree' }
)
)
// Phase 3: 検証
phase('Verify')
const testResult = await agent('pnpm test を実行し、結果を報告せよ')
const auditResult = await agent(
'変更されたファイルのセキュリティ監査を実施せよ',
{ agentType: 'security-auditor' }
)
return { migrated: results.filter(Boolean).length, testResult, auditResult }Workflow の品質パターン
敵対的検証(Adversarial Verify)
各発見事項を独立した「反論者」に検証させる。
// 3人の独立した検証者で多数決
const votes = await parallel(
Array.from({ length: 3 }, () => () =>
agent(
`この発見を反駁せよ: ${finding.description}。
不確かなら refuted=true とせよ`,
{ schema: VERDICT_SCHEMA }
)
)
)
const confirmed = votes.filter(Boolean)
.filter(v => !v.refuted).length >= 2Loop-until-dry
未知の数の問題を発見するとき、何も見つからないラウンドが K 回続くまで繰り返す。
const seen = new Set()
let dryRounds = 0
while (dryRounds < 2) {
const found = await agent(
'まだ見つかっていないバグを探せ',
{ schema: BUGS_SCHEMA }
)
const fresh = found.bugs.filter(b => !seen.has(b.id))
if (fresh.length === 0) {
dryRounds++
continue
}
dryRounds = 0
fresh.forEach(b => seen.add(b.id))
log(`${seen.size} 件のバグを発見`)
}完全性批評(Completeness Critic)
最後に「何が見落とされているか」を問う専門エージェントを走らせる。
const critic = await agent(
`以下の分析結果を見て、欠けている観点を指摘せよ:
${JSON.stringify(allResults)}
- 調査されていないファイル種別はないか
- 検証されていない主張はないか
- 見落とされているエッジケースはないか`,
{ label: 'completeness-critic' }
)Effort の段階的制御
Workflow 内では、各エージェントの effort(推論の深さ)を段階的に制御できる。
// 探索は安く
agent('ファイルを探せ', { effort: 'low' })
// 分析は中程度
agent('コードを分析せよ', { effort: 'medium' })
// 実装は深く
agent('リファクタリングせよ', { effort: 'high' })
// 検証は最も深く
agent('セキュリティ監査せよ', { effort: 'xhigh' })ユウキの振り返り
60ファイルの移行は、Workflow なしなら3日かかると見積もられていた。Workflow を使って4時間で完了した。
「Workflow は"オーケストラの指揮者"だ。各楽器(エージェント)は優秀だけど、指揮者がいなければ交響曲にはならない。スクリプトという楽譜があって初めて、複雑な仕事が調和する」
ミサキは最後にこう付け加えた。
「でもね、最も大事なハーネスがまだ残ってる。セキュリティ——すべての仕組みの土台になるものよ」
次の章では、ハーネスエンジニアリングにおけるセキュリティ設計を深掘りする。