0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

サブエージェントを5階層までネストする — 使いどころと破綻点【Claude Code】

0
Posted at

はじめに:1人会社で「部長の部下の部下」まで作った話

当社(合同会社ジョインクラス)は、社員が僕1人です。その代わりに Claude Code のエージェントを17体(CTO、CMO、出版部長、税理士…)置いて、launchd で17本以上のジョブを回しています。

組織図を描くと、つい階層を深くしたくなります。「CEO → 部長 → 担当 → 作業者 → 検証者」の5階層です。実際に作って動かしてみました。結論から書きます。

  • Claude Code の組み込みサブエージェント(Agent/Task ツール)は入れ子にできない。サブエージェントの中からさらにサブエージェントは呼べない
  • 5階層にするなら、claude -p をプロセスとして呼ぶしかない
  • 1階層増えるごとに固定費が約 $0.3 かかる。5階層の1本道だけで $1.5 前後になる
  • 実用になるのは3階層まで。4階層目からは、コストと失敗の伝わり方で割に合わなくなる

この記事では、当社で実際に動いているスクリプトを使って、組み方、深さを制限するガード、コストの測り方、破綻するポイントを順番に説明します。

前提:Claude Code の「階層」は2種類ある

種類 呼び方 コンテキスト 入れ子
組み込みサブエージェント 親が Agent(Task) ツールで .claude/agents/*.md を起動 親とは別だが、同じセッションの中 不可(サブエージェントは Agent ツールを持てない)
プロセス分離 Bash から claude -p を起動 完全に別のプロセス・別のセッション 可(Bash が使えれば何段でも)

つまり5階層は、この2種類を交互に重ねて作ります。

L1 launchd(cron 相当)
 └ L2 claude -p  … 部門オーケストレーター(例: 出版部長)
     └ L3 組み込みサブエージェント(Agent ツール)… 担当者
         └ L4 Bash → claude -p  … 作業者(別プロセス)
             └ L5 組み込みサブエージェント … 検証者

L3 → L4 の段だけは、サブエージェントに Bash を許可して claude -p を叩かせるという「抜け道」です。この記事の破綻点の大半はこの段で起きます。

Step 1:全階層で使う共通ラッパー(実物)

当社では、無人実行の claude はすべてこのラッパーを経由させています。.company/scripts/lib/claude-run.sh そのままです。

.company/scripts/lib/claude-run.sh
#!/bin/bash
# claude を無人実行するときの共通ラッパー。実コストを記録する。
#
# 実測すると「2+2」を聞くだけの1回でも $0.27〜0.47 かかっていた。
# 内訳はキャッシュ作成の約41,000トークンで、
# そのうち自社ファイル(CLAUDE.md・エージェント・スキル)は5,800トークンしかない。
# 残りはClaude Code側の固定オーバーヘッドなので、**削れるのは呼び出し回数だけ**。
#
# 注意: --bare は認証を読まないため "Not logged in" で失敗する。使わない。

_CLAUDE_RUN_LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

claude_run() {
  local label="$1"; shift
  claude --output-format json \
         --fallback-model claude-sonnet-5 \
         --max-budget-usd 3 \
         "$@" 2>/dev/null \
    | python3 "$_CLAUDE_RUN_LIB/record-cost.py" "$label"
}

ポイントは3つあります。

  1. --output-format json で total_cost_usd を取り出し、record-cost.py が cost-tracker.json にラベルごとに記録する。階層ごとにラベルを分けておけば、どの段がいくら使ったかが後から分かる
  2. --max-budget-usd 3 で1プロセスあたりの上限を決める。ネストするとこの上限は段ごとに独立してかかるので、5階層なら最悪 $15 になる
  3. 標準出力には応答テキストだけを流す。親は RESULT=$(...) で受け取れる

Step 2:L1→L2 部門オーケストレーター(実物)

launchd から毎週水曜7:00に起動している出版部門のジョブです(抜粋)。

.company/scripts/auto-dept-publishing.sh
source "/Users/kyoagun/workspace/one-ceo/.company/scripts/lib/claude-run.sh"

cd "$PROJECT_ROOT"
RESULT=$(claude_run dept-publishing --effort low -p "
あなたは出版部門のPublisherエージェントです。以下のタスクを実行してください。
1. 全書籍の状態を確認
2. 各書籍のZenn上の情報を確認(公開状態、章数)
3. Q2目標に対する進捗を評価
4. 改善提案があれば1-2個提示
" --allowedTools "Read,Bash" 2>/dev/null | tail -20)

echo "$(date): 結果: $RESULT" >> "$LOG_FILE"
[ -n "$RESULT" ] && notify_slack "📚 [出版部門 週次レポート]\n${RESULT}"

ここで大事なのは --allowedTools "Read,Bash" です。Bash を渡した時点で、この L2 は claude -p を呼んで L3、L4…と下に伸ばせます。週次レポートには本来不要な権限なので、ネストさせたくないジョブでは Bash を外すか、次の深さガードを入れてください。

Step 3:深さガードを入れる(これが無いと暴走する)

ネストで一番怖いのは、子が親と同じプロンプトで自分自身を呼んでしまうことです。止める仕組みがないと、--max-budget-usd の上限まで段を増やしながらお金を使い続けます。

環境変数は子プロセスに引き継がれるので、それで深さを数えます。上のラッパーを次のように拡張します(当社の現行版からの差分です)。

claude-run.sh(深さガード付き)
CLAUDE_MAX_DEPTH="${CLAUDE_MAX_DEPTH:-3}"

claude_run() {
  local label="$1"; shift
  local depth="${CLAUDE_NEST_DEPTH:-0}"

  if [ "$depth" -ge "$CLAUDE_MAX_DEPTH" ]; then
    echo "[claude_run] depth=$depth >= max=$CLAUDE_MAX_DEPTH, refused: $label" >&2
    return 75   # EX_TEMPFAIL。親側で「これ以上委任するな」と判断できる
  fi

  CLAUDE_NEST_DEPTH=$((depth + 1)) \
  claude --output-format json \
         --fallback-model claude-sonnet-5 \
         --max-budget-usd "$(budget_for_depth "$depth")" \
         "$@" 2>/dev/null \
    | python3 "$_CLAUDE_RUN_LIB/record-cost.py" "${label}@L$((depth + 1))"
}

# 深い段ほど予算を絞る(3 → 1 → 0.5)
budget_for_depth() {
  case "$1" in
    0) echo 3 ;;
    1) echo 1 ;;
    *) echo 0.5 ;;
  esac
}

変更点は次の3つです。

  • CLAUDE_NEST_DEPTH を1ずつ増やして子プロセスに渡す
  • 上限に達したら claude を起動せず、終了コード75を返す
  • コストのラベルに @L2 のように段を付ける。どの段が高いかが cost-tracker.json を見るだけで分かる

検証方法

わざと再帰させて、止まることを確認します。

source .company/scripts/lib/claude-run.sh
CLAUDE_MAX_DEPTH=2 claude_run nest-test -p '
次のコマンドをそのまま1回だけ実行し、出力をそのまま返して:
source .company/scripts/lib/claude-run.sh && claude_run nest-test -p "2+2は?" --allowedTools Bash
' --allowedTools "Bash"
echo "exit=$?"

# 段ごとのコストを確認
jq '.' .company/scripts/cost-tracker.json | grep -A2 'nest-test'

CLAUDE_MAX_DEPTH=2 なら L2 と L3 までは起動し、その下は refused が stderr に出れば成功です。

Step 4:L3 サブエージェント側の書き方

組み込みサブエージェントは .claude/agents/*.md のフロントマターでツールを絞ります。当社の CTO エージェントはこうなっています。

.claude/agents/ai-ceo-cto.md
---
name: ai-ceo-cto
description: CTO/開発部長エージェント。プロダクト開発全般を統括し、dev-architect, dev-coder, dev-reviewerのタスクを管理する。
tools:
  - Read
  - Write
  - Edit
  - Bash
---

description に「dev-coder を管理する」と書いてありますが、組み込みサブエージェントからは dev-coder を Agent ツールで呼べません。Bash が許可されているので claude -p 経由で L4 を作ることはできますが、それをやらせるかどうかは本文で明示しておく必要があります。僕は次の1行を本文に入れています。

## 委任ルール
- 下位エージェントの起動は `claude_run` 経由に限る。`claude` を直接呼ばない(深さガードを通すため)
- 委任してよいのは「独立して検証できる成果物」があるタスクだけ。調査の丸投げは禁止

破綻点:4階層目から何が起きるか

5階層の構成を組んで検証したとき、問題になったのは次の4つです。

1. 固定費が段数に比例して積み上がる

ラッパーのコメントにある通り、claude -p は中身が「2+2」でも1回 $0.27〜0.47 かかります。キャッシュ作成の約41,000トークンのうち、自社の CLAUDE.md やエージェント定義は5,800トークンしかありません。残りは Claude Code 側の固定オーバーヘッドなので、プロンプトを削っても下がりません。

構成 プロセス数 固定費の目安
2階層(L2のみ) 1 約 $0.3
3階層(L2 + 組み込みL3) 1 約 $0.3 + サブエージェント分
5階層(L2, L4 が別プロセス) 2以上 約 $0.6〜 ※L4がファンアウトすると×N

L4 を「章ごと」「ファイルごと」にファンアウトさせると、10章の本で $3〜5 が一瞬で消えます。削れるのは呼び出し回数だけなので、段を増やすほど不利になります。

2. エラーが上に届く前に「要約」されて消える

L5 の検証者が「テストが3件落ちた」と返しても、L4 がそれを要約し、L3 がさらに要約すると、L2 の Slack 通知では「概ね問題なし」になってしまいます。各段が tail -20 や「結果を簡潔に報告」で出力を削るので、悪い知らせほど先に消えます。

対策として、失敗はテキストではなく終了コードとファイルで返すようにしました。

# L4 側:失敗は exit code と専用ファイルで返す
if ! npm test > "$WORK/test.log" 2>&1; then
  echo "FAILED" > "$WORK/STATUS"
  exit 1
fi
echo "OK" > "$WORK/STATUS"

L2 は要約文ではなく STATUS ファイルを読んで判断します。

3. 権限が段を下るほど広がる

L2 に --allowedTools "Read,Bash" しか渡していなくても、L4 の claude -p に --allowedTools を付け忘れると、L4 はプロジェクトの settings.json の設定で動きます。当社では以前、AI が git push --force を自動で実行してブランチが消えたことがあり、それ以来「絶対禁止リスト」を作っています。ネストするとこのリストを全段で守らせる必要があります。ラッパーで --allowedTools を必須にするのが確実です。

4. デバッグが事実上できない

5段目で何が起きたかを追うには、5個のセッションログを時系列でつなぎ直す必要があります。現行のラッパーはラベルが部門名だけなので、ネストすると、どのプロセスがどの段かも分かりません。Step 3 でラベルに @L4 のような段番号を付けているのはこのためです。

使いどころ:深くしていいのはこの3パターンだけ

以上を踏まえて、当社のルールはこうしています。

階層 使ってよいケース 例
L2 定期ジョブの本体 出版部門の週次レポート
L3(組み込み) 親のコンテキストを汚したくない読み取り・調査 全書籍の config.yaml 走査
L4(別プロセス) 成果物が独立していて、機械的に検証できるもの EPUB生成 → check-kindle-epub.py で合否判定
L5 原則禁止。使うなら L4 の検証専任のみ —

判断基準は1つだけです。「その段の出力を、LLM ではなくスクリプトで合否判定できるか」。できないなら、その段は親が自分でやった方が安く、壊れにくくなります。

まとめ

  • 組み込みサブエージェントは入れ子にできない。5階層は claude -p のプロセスと交互に重ねて作る
  • claude -p 1回の固定費は約 $0.3。削れるのは呼び出し回数だけ
  • CLAUDE_NEST_DEPTH 環境変数で深さガードを入れ、コストのラベルに段番号を付ける
  • 失敗はテキストではなく終了コードとファイルで返す
  • 実用上の上限は3階層。4階層目は「スクリプトで検証できる成果物」があるときだけ

経営の視点で言うと、組織図をそのままエージェント階層に写すのは、人間の会社で中間管理職を増やすのと同じで、伝言ゲームとコストが増えるだけでした。当社が CLAUDE.md を1,000行超えで破綻させ、200行に削って .claude/agents/ に分けたときと同じ教訓です。階層は浅く、責務ははっきりさせる。

もっと深く知りたい方へ

オーケストレーターとサブエージェントの役割分担、コンテキストの受け渡し、失敗時のリトライ設計は、拙著『Claude Codeマルチエージェント開発』で、当社の17エージェント構成をもとに体系的にまとめています。

書籍一覧はこちら → https://zenn.dev/joinclass?tab=books

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?