組み込みの /deep-research を一度動かすと、次に出てくる問いは決まっています。「同じ構造を、自分のタスクで書けるのか」です。私の場合は、個人開発で運営する 4 つの技術ブログの記事下調べと、生成した記事の品質チェックを、毎回手でサブエージェントに振り分けていました。これをスクリプトに固定したかった、というのが自作に踏み込んだ動機です。
このガイドは、/deep-research の View raw script で読めるスクリプトを下敷きに、ワークフローを構成するプリミティブを 1 つずつ分解し、最後に自分の品質チェックパイプラインへ落とし込むところまでを扱います。前提として、動かし方と保存方法はDynamic Workflow を動かして掴んだサブエージェント並列実行の勘所 で整理済みなので、ここでは「書く」側に集中します。
なぜ計画をコードに移すと信頼性が上がるのか
サブエージェントをターンごとに spawn する方式では、Claude がその場で次の手を決めます。これは柔軟ですが、同じタスクを 2 回流したときに同じ手順を踏む保証がありません。ワークフローはループ・分岐・途中結果をスクリプトに持たせるので、オーケストレーションそのものが再現可能 になります。
さらに重要なのは、独立したエージェントに互いの結果を敵対的にレビューさせたり、複数の角度から立てた計画を比較させたりできる点です。単一パスより信頼できる結果が出るのは、エージェントの数ではなく、この「突き合わせる構造」を持てるからです。私が記事の事実確認をワークフロー化したいと考えたのも、まさにこの検証構造が欲しかったからでした。
ワークフローを構成する5つのプリミティブ
スクリプトを読み解く鍵は、次の 5 つです。
meta: ワークフローの名前・説明・フェーズ一覧を宣言するエクスポート
phase("名前"): 進行表示を区切るマーカー。/workflows の各行に対応します
agent(prompt, options): サブエージェントを 1 体起動します。options に label・schema・phase を渡します
pipeline(items, stage1, stage2, ...): 前段の出力を次段の入力に渡す段階処理
parallel([fn, fn, ...]): 関数の配列を並列実行します
最小構成はこうなります。スクリプト引数は args で受け取り、log() で進行ログを残せます。
export const meta = {
name: "article-fact-check" ,
description: "記事の主張を抽出し、敵対的に検証して残す" ,
phases: [
{ title: "Extract" , detail: "本文から検証可能な主張を抽出" },
{ title: "Verify" , detail: "各主張を3票制で検証" },
{ title: "Report" , detail: "残った主張をレポート化" },
],
};
phase ( "Extract" );
const ARTICLE = ( typeof args === "string" && args. trim ()) || "" ;
if ( ! ARTICLE ) {
return { error: "記事パスを args で渡してください" };
}
meta.phases に並べた内容が、起動前の確認ダイアログに「どんな手順で動くか」として表示されます。読み手(承認する自分)への説明でもあるので、ここは丁寧に書く価値があります。
スキーマで出力を縛る
ワークフローが壊れるのは、たいてい「前段のエージェントが想定外の形で返す」ときです。これを防ぐのが schema です。各 agent 呼び出しに JSON Schema を渡すと、エージェントは構造化データで返すことを求められ、後段がそのデータを安全に扱えます。
const EXTRACT_SCHEMA = {
type: "object" ,
required: [ "claims" ],
properties: {
claims: {
type: "array" ,
maxItems: 5 ,
items: {
type: "object" ,
required: [ "claim" , "quote" , "importance" ],
properties: {
claim: { type: "string" },
quote: { type: "string" },
importance: { enum: [ "central" , "supporting" , "tangential" ] },
},
},
},
},
};
const extracted = await agent (
"次の記事から、検証可能な主張を最大5件抽出してください。" +
"各主張には本文中の直接引用を quote として添えてください。 \n\n 記事パス: " +
ARTICLE ,
{ label: "extract" , phase: "Extract" , schema: EXTRACT_SCHEMA },
);
ポイントは enum と required です。importance を自由記述にすると後段の並べ替えが破綻しますが、enum で central / supporting / tangential に縛れば、そのまま優先度ソートの鍵に使えます。私はここを最初ゆるく書いて、後段でソートキーが揃わずに落ちる、という失敗を 2 回やりました。スキーマは後段の都合から逆算して設計する のが正解です。
pipeline と parallel を実装で理解する
parallel は単純で、関数の配列を同時に走らせて結果を配列で返します。同時実行数の上限が 16 なので、それを超える数を渡しても内部でスロットリングされます。
phase ( "Verify" );
const VOTES = 3 ; // 1主張あたりの検証エージェント数
const REFUTES_TO_KILL = 2 ; // 2票以上の反証で却下
const verified = await parallel (
extracted.claims. map (( c ) => () =>
parallel (
Array. from ({ length: VOTES }, ( _ , v ) => () =>
agent (
"あなたは懐疑的な検証者です。次の主張の反証を試みてください。 \n " +
"主張: " + c.claim + " \n 引用: " + c.quote,
{ label: "vote" + v, phase: "Verify" , schema: VERDICT_SCHEMA },
),
),
). then (( verdicts ) => {
const valid = verdicts. filter (Boolean);
const refuted = valid. filter (( x ) => x.refuted). length ;
const survives = valid. length >= REFUTES_TO_KILL && refuted < REFUTES_TO_KILL ;
log ( '"' + c.claim. slice ( 0 , 40 ) + '": ' + refuted + "票反証 " + (survives ? "残" : "却下" ));
return { ... c, survives };
}),
),
);
一方 pipeline は、前段の各出力を次段に流す段階処理です。/deep-research では「角度ごとに検索 → 重複 URL を除外しつつソースを取得」という流れを pipeline で書いています。検索が全部終わるのを待たず、検索結果が出た角度から順に取得へ流せるのが利点です。全件の集約が必須な処理(検証前の主張プール確定など)には pipeline のあとにバリア(await parallel(...) の完了待ち)を置く 、という使い分けを意識すると設計が安定します。
敵対的検証パターンを自分のパイプラインに移植する
上のコードがまさにそれですが、3 票制の敵対的検証は記事の事実確認に綺麗に嵌まります。私は 4 サイトで 1 日合計 4 本を公開していて、生成記事の主張が公式ドキュメントとずれていないかを確認する工程が常に必要でした。これを article_gate.py のような機械チェックだけでなく、「複数のエージェントに反証を試みさせて、2 票以上の反証が付いた主張は落とす」という構造に置き換えると、機械ルールでは拾えない事実誤認を弾けます。
棄権の扱いには注意が必要でした。エージェントがエラーや user-skip で null を返したとき、それを「反証なし=生存」と数えると、全員棄権の主張がすり抜けます。valid.length >= REFUTES_TO_KILL のように、最低限の有効票数が揃ったことを生存条件に入れる のが落とし穴回避の肝です。
トークンコストとモデルの振り分け
ワークフローは数十から数百のエージェントを動かすため、同じタスクを会話で進めるより明確にトークンを食います。これは無視できないコストで、ローカル VLM を使って API 課金をゼロにする、という別路線(Ollama + Qwen3-VL のような構成)が注目されるのも、突き詰めればこのコスト問題が背景にあります。
ただ、私はクラウドモデルのワークフローを捨てる必要はないと考えています。鍵はステージ単位のモデル振り分け です。各エージェントは既定でセッションのモデルを使いますが、強いモデルが要らないステージ(主張の抽出やラベル付けなど)は、タスク説明の中で「このステージは小さいモデルで」と Claude に頼めます。検証や統合のように判断が効くステージにだけ上位モデルを当てる、という配分にすると、合計コストはかなり抑えられます。
実務的には次の順で確認すると安全です。
大きな実行の前に /model を確認する(普段小さいモデルに切り替えている場合の取り違え防止)
抽出・分類など機械的なステージは小さいモデルに振る指示をタスク説明に含める
同時 16・合計 1,000 の上限を意識し、maxItems や slice で扱う件数を絞る
私は壁紙アプリ(iOS / Android で累計 5,000万 DL 超)の更新時に、クラッシュレポートの分類を小さいモデル、原因の突き合わせを上位モデル、という発想で人手の調査を分けていました。ワークフローでも同じ配分の考え方がそのまま効きます。
実運用での落とし穴
スクリプトを書いて回す中で踏んだ点をまとめます。
実行中にユーザー入力を挟めません 。途中で人間の承認を入れたい設計なら、ステージごとに別ワークフローへ分割します
スクリプト自体はファイル・シェルに直接触れません 。読み書きはエージェントの仕事で、スクリプトは調整に徹します。ここを混同すると fs を呼びたくなって設計が崩れます
再開は同一セッション内のみ です。Claude Code を終了すると次回はゼロから始まるので、長時間の実行はセッションを跨がない前提で組みます
棄権・null を生存に数えない 。検証系では特に、有効票のクォーラムを生存条件に必ず入れます
まず保存して再利用する
自作ワークフローの価値は、一度書いて終わりではなく、保存して定型処理にできる点にあります。納得のいく実行ができたら /workflows で選んで s を押し、.claude/workflows/(チーム共有)か ~/.claude/workflows/(自分専用)に保存してください。次回からは /article-fact-check のようなコマンドとして呼べます。
最初の一歩としては、/deep-research の生スクリプトをコピーして、検索対象を Web から自分のリポジトリ(記事 MDX)に差し替えるところから始めるのがやりやすいはずです。スキーマと検証構造はほぼそのまま流用できます。そのスクリプトに渡す指示そのものを軽く保つ観点では、効くスキルの設計 — テンプレート固定と判断基準で出力を安定させる で扱った設計原則が、ワークフロー内のプロンプト設計にもそのまま効きます。
私自身、検証構造を持つこの仕組みが、一人で複数サイトの品質を保つうえで静かに効いてくると感じています。共に試していけたら嬉しいです。