Yyatmita

【第7回】サブエージェントのネスト解禁——4 つの「発火モード」と、集約親で Main コンテキストを守る

Claude Code v2.1.172 でサブエージェントのネストが解禁。第5回で「ネスト禁止だからスキルでラップ」と書いた前提が崩れた。直接発火・スキル発火・ネスト発火・ワークフロー発火の 4 モードを整理し、英訳チェックの本番サイズ (8 chunks × 3 lenses = 24 子) で「集約親」を計測。Main 到達 text は -68% (38KB → 12KB)。ただし第6回の「子はファイルに書く・戻り値はパス」方式 (~1.2 KB) には負ける——集約親はパイプライン向けではなく、Main が集約結果をその場で判断材料にする「セッション内対話」用、と棲み分ける。

Claude Codeサブエージェント検証#claude-code#subagent#nesting#workflow#context-management
← 前の記事: 【第6回】Claude Code サブエージェントで並列オーケストレーション——プロセス数とメモリ増減を計測してみた

第5回 で「サブエージェントはネストできない、だからスキルでラップして公式エージェントを着替えさせる」と書いた。その前提が崩れた。

Claude Code v2.1.172 (2026-06-10) で、サブエージェントのネストが解禁された。 親エージェントの frontmatter toolsAgent を含めれば、サブエージェントから別のサブエージェントを起動できる。最大 5 段、Agent ツールを明示しない限り従来どおりネスト不可、という形での解禁。

第6回までに整理してきたサブエージェント像を、ここで一度更新する。今回はネストそのものの動作確認と、「集約親」という設計パターン——大量の生指摘を Main の context に流し込まず、親エージェントの中で吸収させる——を、英訳チェックを題材にした最小 PoC で計測する。

フック: 第5回の「スキル発火」を再評価する

第5回で書いた「スキルでラップ」は、当時こう位置づけていた。

サブエージェントから別のサブエージェントは呼べない。でも、スキルからサブエージェントは呼べる。スキルはサブエージェントではない。だからスキル本体が Task ツールを使って公式エージェントを起動するのは、Main からの直接起動と同じ扱いになる。これはネスト禁止に抵触しない。

ネストが解禁された今、これを「禁止回避の逃げ」と読むと、第5回の意味が痩せる。再評価するとこうなる。

スキル経由でサブエージェントを起動するのは 逃げではなく、Main 文脈での発火装置 だった。スキルは Main の指示書 (プロンプトの拡張) であって、独立した context を持たない。スキル本体が Agent ツールを呼ぶのは Main がそうしているのと同じことで、これは Main の判断レイヤを残したまま起動の作法をスキルに固めるという独自の用途を持つ。ネスト解禁後も、この用途は別モードとして残る。

そう見るとサブエージェントの呼び方は、もはや 1 種類ではない。

4 つの「発火モード」

整理するとこうなる。

モード構造主な用途関連回
直接発火Main → Agent1 段で済む素朴な処理第 1〜2 回
スキル発火Main → Skill → AgentMain の判断を挟む、ユーザー操作のトリガー第 5 回
ネスト発火Main → Agent → AgentMain の context 保護、動的並列 + 集約本記事 (第 7 回)
ワークフロー発火Main → Workflow → Agent × N決定論的オーケストレーション第 6 回 ほか

第5回で「スキル発火」と再定義した抽象化を引き伸ばすと、ワークフロー (Workflow ツール) も「サブエージェントの呼び方」の 1 つに位置づく。スクリプトで agent()parallel() / pipeline() で並べる、あれもこの系譜の発火モードだ。

各モードは排他ではなく、組み合わせて使う。本記事の主役は新しく加わったネスト発火で、その実用形が次の「集約親」だ。

なぜ「集約親」が要るのか

英訳チェックという題材で考える。マンガの英訳を、観点違いの複数レンズ (grammar / fidelity / localize) で並列レビューするタスクだ。manga-en-check スキルとして以前から運用していて、第6回でも触れた fan-out の典型例である。

直接発火で素朴に書くと、こうなる。

Main
  ├─ grammar 子   → JSON findings を返す
  ├─ fidelity 子  → JSON findings を返す
  └─ localize 子  → JSON findings を返す

3 子の生 JSON が Main の context に積み上がる。Main は次に dedup・severity 統合・重要度順並び替え、を自分でやることになる。問題は単発ならいいのだが、Main がこの後も長く作業を続ける場合、context が「20 件の細かい指摘」で先に埋まること。後続作業の余地が消える。

ここで親エージェントを 1 段挟む。

Main
  └─ 集約親
       ├─ grammar 子
       ├─ fidelity 子
       └─ localize 子
       └─ findings を merge → dedup → severity sort → markdown レポート 1 本にして Main に返す

子の生指摘は親の context で消化される。Main に届くのは集約済みのレポートだけ。Main の context は「レポート 1 本」分しか食わない。子の並列性は失われない。

公式 (Boris Cherny) のネスト解禁アナウンスにある「ネスト解禁の動機は並列性ではなくコンテキスト管理」とは、まさにこれだ。各サブエージェントが fresh context を持つので、子で詳細を捌いて親で要約すれば、Main の context が温存される。

最小 PoC: 直接発火 vs ネスト発火 (集約親)

意図的に各レンズで拾えそうな errors を仕込んだ JA/EN 8 ペアのサンプルを作り、3 通り計測した。

  • A: 直接発火 × 3 ― Main から 3 子を直接並列起動
  • B: 集約親 (general-purpose 流用) ― 親プロンプトに集約手順を書き込んで起動
  • B': 集約親 (専用エージェント定義)sub7-en-aggregator という frontmatter tools: ["Agent", "Read"] の親と、sub7-en-grammar / sub7-en-fidelity / sub7-en-localize の専用子定義を .claude/agents/ に置いて起動

結果がこれだ。

観点A: 直接発火 × 3B: 集約親 (GP 流用)B': 集約親 (専用定義)
構造Main → 子 × 3Main → 親(GP) → 子(GP) × 3Main → 親(専用) → 子(専用) × 3
Main wall-clock約 16.5 秒62 秒54 秒
Main に届く text生 findings 3 本 (約 4450 chars)レポート 1 本 (約 3500 chars)レポート 1 本 (約 3200 chars)
Main の後続労力dedup / 整列を自前不要不要
親トークン5165933461 (B の 65%)
Main → 親 プロンプト(3 子に個別)約 1500 字 (集約手順を都度埋め込み)パス 1 行
Main context 保護×

A は速いが Main の context が食われる。B / B' は遅いが Main の context が守られる。これは並列性とコンテキスト管理のトレードオフで、後続作業の量で選ぶ。

ただし正直に書くと、この小サイズ (8 ペア / 3 子) では Main 到達 text の差が 28% 程度しか出ていない。「集約親で Main を守る」と主張するには弱い数字だ。これは PoC サイズの問題で、子の数が増えれば差は広がるはず——本記事の後半でその確認をする。

そして B → B' でもう一段良くなる。

B → B' で得た副次知見: 集約親は専用定義のほうが圧倒的に筋がいい

B (general-purpose 流用版) では、Main から親に渡すプロンプトに「3 子をこのスキーマで並列起動して、merge → dedup → sort してレポート 1 本で返せ」と毎回 1500 字くらい書き込む必要があった。

B' (専用定義版) では、sub7-en-aggregator.md の SKILL 文に集約手順を書き込んである。だから Main からはパス 1 行を渡すだけで動く。親トークンは 51659 → 33461 (65%)、wall-clock は 62 秒 → 54 秒。生成品質も整って、(also flagged by ...) の併記もきれいに揃った。

DRY (Don't Repeat Yourself) としては当然のことなのだが、集約親のような「中で何をするかが定型化できるエージェント」は、そもそも専用定義で立てるほうが筋がいい。general-purpose にプロンプトで観点を縛る方式は、思いつきの試作には便利だが、本番運用では専用定義に格上げする。第4回 (スキル → 自作エージェント委任) で扱った発想の延長線だ。

本番サイズで再計測——スケールするほど集約親が効く

PoC 8 ペアでは差が小さすぎたので、manga-en-check の本番素材で組み直す。manginus から抽出済みの JA/EN ペア 371 件を 8 chunks に分割し、8 chunks × 3 lenses = 24 子 の本番スケールで A_24 / C_24 を比較する。

  • A_24: Main から子 24 体を 1 メッセージで並列起動 (= 直接発火 × 24)
  • C_24: Main から集約親 1 体を起動し、親が 24 子を並列起動して集約 (= ネスト発火)

結果。

観点A_24 (直接 × 24)C_24 (集約親 + 24)
wall-clock118 秒326 秒+175%
総トークン~670k~751k+12%
Main に届く text~38 KB~12 KB-68% (約 1/3)
Main の後続労力dedup・整列を自前不要
集約後 findings (C 側)64 件 (3 high / 24 med / 37 low)

これが本来見せるべきだった数字だ。PoC では Main 到達 text が 28% 減でしかなかったが、本番 24 子では 68% 減 (38KB → 12KB)。子の数で圧縮率が大きく変わる。

スケール効果を並べるとよりはっきりする。

サイズA 側 Main 到達C 側 Main 到達圧縮率
PoC (3 子)~4,450 chars~3,200 chars-28%
本番 (24 子)~38,000 chars~12,000 chars-68%

支払うコストは +12% のトークン+175% の wall-clock。A_24 と C_24 で子の合計トークンはほぼ同じだから、+12% はそのまま親の集約処理だけのコストだ。Main の後続作業が長いとき、この 26KB の context 温存は決定的に効いてくる。

公式 (Boris Cherny) が言う「ネスト解禁の動機はコンテキスト管理」は、子が多いほど効く——そう読み直したほうがいい。少ない子では効果が見えないが、本番スケールでは桁が違う。

正直な比較: 第 6 回「ファイル式」のほうが Main 保護は強い

ここで 第6回 の解と並べると、集約親パターンは Main コンテキスト保護では負ける。第6回の処方箋はこうだった。

サブエージェントの最終返信を 1 行のステータスに限定する。WROTE output/<name>.md。本文は書き出したファイルにだけ存在すればよく、親に返す必要はない。後続の工程はファイルを直接読む。

これを 24 子に当てると、各子の戻り値はパス 1 行 (~50 bytes)。24 体で 合計 1.2 KB ほど。本記事の集約親 (12 KB) より一桁少ない。

方式Main に届く text集約済み?Main が中身を読む?
A_24 直接発火~38 KB (24 件の生 JSON)×◎ (流れ込んでくる)
C_24 集約親 (ネスト)~12 KB (集約レポート)
第 6 回 ファイル式~1.2 KB (パス 24 本)×× (後続工程が開く)

数字だけ見ると、Main の context を減らしたいだけなら第6回方式が圧勝だ。

では集約親はどこで活きるのか — セッション vs パイプライン

両者の差は Main 自身がそのデータを判断材料に使うかに集約される。

第6回 ファイル式集約親 (ネスト)
想定される使われ方パイプライン (Main は取り回し役、結果は後続スクリプトやユーザーが直接読む)セッション内対話 (Main 自身が集約結果を見て次の判断をする)
集約のタイミング後続工程 (= 別セッション・別ジョブ)その場で親エージェントが
集約の柔軟性後段が固定された手続きでよいLLM 判断で dedup・優先度・観点統合が要る
Main の責務オーケストレートしてパスを渡す集約結果を見て判断し、ユーザーに返す or 次のアクションを決める

第6回の取り込み工程 (PDF 21 個 → Markdown 21 個) は典型的なパイプラインで、Main は中身を判断しなかった。だから戻り値がパス 1 行で足りた。

一方、英訳チェックを会話中に走らせるケースを考える。「この章の英訳を一度見て、致命的な誤訳だけ即修正したい」みたいな状況。Main が集約結果を読み込んで、ユーザーと「この high severity は反映、これは見送り」と相談する。このとき結果はパスじゃ困る——Main の context にちゃんと乗っていてほしい。集約親はここで初めて第6回方式より優位になる

雑に言い切るとこうだ。

  • パイプラインなら第6回方式 (子はファイルに書く、戻り値はパス)
  • セッション内で Main が判断材料に使うなら集約親 (親が中で集約して text で渡す)

両方使う手もある——親が 24 子の生 findings を集約しつつ、詳細は別ファイルに書き出してパスも一緒に Main に返す。Main は集約レポートで判断、必要なら詳細ファイルを開く。これは集約親とファイル式のハイブリッドで、たぶん実戦では一番効く形だが、本記事の最小 PoC では未検証。

副次知見その 2: general-purpose にプロンプトで観点を縛ると、ファイル参照を勝手にスキップする

これは A の実験で偶発的に観測したもので、記事の主題ではないが面白いので残しておく。

A で localize 子 (general-purpose にプロンプトで観点を縛ったもの) を起動したら、tool_uses = 0、Read を呼ばずに 10 件の架空 findings を返してきた。指摘内容自体は典型的 translationese だが、サンプルファイルとは無関係。pair id も L001L010 という存在しない ID。

なぜそうなったか想像はつく。general-purpose は汎用的に設計されているので、プロンプトに「translationese を見つけよ」とだけ書くと、「典型例で答えるのが期待値だ」と判断してファイル参照をスキップしてしまったのだろう。プロンプトに「you MUST call Read — do not answer from memory」と書き足したら止まった。

これは専用定義 (sub7-en-localize) では構造的に起こりにくい。tools: ["Read"] で道具が限定され、SKILL に「Read してから JSON 返却」と書いてあるので、エージェントは Read 以外の選択肢を持たない。プロンプトで縛るより、frontmatter と SKILL で縛るほうが安定する——これも「集約親は専用定義で」という結論を補強する。

ホットリロードの罠を、自分自身で再演した

これは記事に書くか迷ったが、第3回の続編として書いておく価値がある。

sub7-en-aggregator.md.claude/agents/ に作って、最初に Agent ツールで呼んだら Agent type 'sub7-en-aggregator' not found. Available agents: ... と返ってきた。リストには既存の article-writerneta-researcher は載っていたが、新規の sub7-* は載っていない。

ここで私は「新規エージェント定義はホットリロードされない、再起動が必要」と判断して、general-purpose に切り替えた (これが上の B)。

ところが直後にもう一度呼んだら、普通に動いた。書式問題でも再起動の問題でもなく、エラーに出てくる Available agents リストが古いセッションキャッシュだっただけ。実呼び出しは fs を読みに行く。

これは 第3回 で書いた「Write(...) は無効ルール名で、書式問題だった」と同じ構造のオチだ。当時の自分が「ホットリロードが効かないように見えた失敗は、全部書式ミスが原因だった」と結論したのに、今度は私自身が「ホットリロードが効かないように見えた失敗」を再演した。第3回が書かれた時点でなぜそれを忘れたか、というと、当時のオチは settings.local.json のホットリロードの話で、エージェント定義については「再起動が必要」と書いたまま続編で更新していなかったから——というのが正直なところだ。

ここで連載の解像度を上げておく。エージェント定義のホットリロードも効く。 効いていないように見えるときは Available agents リストを信じすぎている可能性が高い。とりあえずもう 1 回呼んでみる、が次の正解だ。

ワークフロー発火との対比 — 棲み分けの再整理

第6回で扱った fan-out (1 ファイル 1 サブエージェント) や、manga-en-check の Workflow による 24 並列 (8 chunks × 3 lenses) は、ネスト発火が解禁された今どう位置づくのか。

観点ワークフロー発火 (Workflow)ネスト発火 (集約親)
並列の決定スクリプトで決定論的 (parallel() / pipeline())親 LLM の判断 (1 message で並列 Agent 呼び出し)
集約スクリプト末尾の return親エージェントの LLM 判断
Main 返却任意の構造化値 (JS オブジェクト等)親 agent の最終 text
仕様の硬さ仕様が決まっている反復向き仕様が動的・適応的
実装コストスクリプト書く (JS)エージェント md 書く
Main context 保護◎ (workflow が返す値だけ)◎ (親が返す text だけ)
デバッグしやすさ進捗が構造化されて見える通常の agent 1 段増えるだけ

両者は競合ではなく補完。ざっくり言うとこうなる。

  • 件数・段階が固定で、決定論で並べる強さが要る → ワークフロー発火 (manga-en-check の N chunks × M lenses)
  • 件数・段階が動的で、親に LLM 判断を任せたい → ネスト発火 (今回の集約親)
  • 1 段で済む素朴な仕事 → 直接発火
  • Main の判断を挟む / ユーザー操作のトリガー → スキル発火

manga-en-check の中身を見ていくと、実は外側はワークフロー (N 並列を決定論で並べる)、各並列スロットの中で集約親 (生指摘を親で吸収)、というハイブリッドに組み直す筋が見える。これは追ってもう一度回すかもしれない。

まとめ

  • v2.1.172 でサブエージェントのネスト解禁。深さ 5、toolsAgent を入れた親のみ。動機はコンテキスト管理で、並列性ではない
  • 第5回の「スキル発火」は逃げではなく Main 文脈での発火装置。解禁後も別モードとして残る
  • サブエージェントの発火モードは 直接 / スキル / ネスト / ワークフロー の 4 種、組み合わせて使う
  • 「集約親」は、Main の context を子の生指摘で埋めたくないときに効く。並列性とトレードに wall-clock とトークンは増えるが、後続作業が長い Main では context 保護のほうが勝つ
  • 効き方は子の数次第。3 子だと Main 到達 -28% で効果が薄いが、本番サイズ (24 子) では -68% (38KB → 12KB)。+12% トークンと引き換えに Main context が 1/3 になる。少ない子では効果が見えないが、スケールするほど顕著
  • ただし、Main コンテキストを削るだけなら 第6回 の「子はファイルに書く・戻り値はパス」方式に負ける (~1.2 KB 対 12 KB)。集約親はそれ単体では既存解の下位互換
  • 棲み分けは「Main が中身を判断材料にするか」。パイプラインなら第6回方式セッション内対話で集約結果を Main が見て判断するなら集約親
  • 集約親は general-purpose 流用より専用エージェント定義のほうが圧倒的に筋がいい。SKILL に集約手順を書けば Main からはパス 1 行で起動でき、トークン 65%・wall-clock 短縮・出力品質も向上
  • 専用定義は tools: ["Read"] のような道具の限定で、general-purpose が起こしがちな「ファイル参照スキップ → 架空回答」を構造的に防ぐ
  • エージェント定義のホットリロードは効く。Available agents リストは古いことがある——もう 1 回呼んでみる、が次の正解

第5回で「ネスト禁止という制約に対して、層の選び方で逃げる」と書いた。今回ネストが解禁されて分かったのは、当時「逃げ」と呼んだスキル発火が、実は別用途を持つ独立のモードだったということだ。制約が外れたあとに残る設計は、制約への適応で生まれた本当の構造だった——第5回の結論を、私はこう書き直したい。