研究紹介
Claude Code の Subagent が呼ばれない — 42 試行で分かった、description より先に見るところ
· 読了まで約11分
目次
claude code subagents not working で検索すると、Reddit のスレッドと GitHub の issue、それに個人ブログが数本出てくる。助言はすぐ一点に集まる —— description を書き直せ。トリガー語を足せ。MUST BE USED と書け。1 行に収めろ。
直前に Claude Code の Skill が何で呼ばれるかを実測したばかりだった。そのときは定説のほうが間違っていて、本当に効いていたのは誰も見ていない要素だった。同じ仕掛けを .claude/agents/ に向けて 42 回回した。
委譲を明示しなかった 17 試行では、1 本も呼ばれなかった。トリガー語を入れても、ユーザーの言語に合わせても、公式ドキュメントが薦める MUST BE USED / Use PROACTIVELY を書いても同じだった。description が効きはじめるのは、その前にある別の問題を片づけたあとである。
中身が同じで description だけ違う 8 本を置く
.claude/agents/ に 8 本のサブエージェントを置いた。本文もタスクも同じで、違うのは description の行だけ。仕事は CSV を Markdown の表に変換すること。
名前は無意味なものにする必要がある。csv-to-markdown のような名前を付けると名前だけで発火して、description の差が見えなくなるからだ。そこで probe-alpha から probe-hotel までとし、情報を持つ signal を description だけに絞った。
本文はどれも同じ 1 つの指示だけを持つ。
このサブエージェントが呼ばれたら、最終返答の 1 行目として必ず次だけを出力する。
CANARY-ALPHA
8 本の description は次のとおり。
| ID | description の書き方 |
|---|---|
| ALPHA | 英語・1 行・機能説明のみ(トリガー語なし) |
| BRAVO | ALPHA にトリガー語を足す(Use when the user asks to...) |
| CHARLIE | 日本語・「〜と言われたら使う」形式 |
| DELTA | BRAVO と内容は完全に同一で、書式だけ複数行(YAML ブロックスカラー) |
| ECHO | 同義語・周辺の言い回しを並べる |
| FOXTROT | 極端に短い(4 語) |
| GOLF | 冗長(80 語超) |
| HOTEL | 公式ドキュメントが薦める強制語(MUST BE USED / Use PROACTIVELY) |
canary だけでは足りない
Skill を測ったときは出力に canary を仕込むだけで足りた。Subagent ではそれでは足りず、ここが手法を作り直した唯一の箇所になる。
サブエージェントの返答は親がまとめてからユーザーに届くので、canary が出ないことの意味が一意に決まらない。呼ばれなかったのか、呼ばれたが親が言い換えて消したのかが分からないからだ。さらに厄介なことに、Claude は「probe-bravo エージェントを使います」と宣言しておいて自分で作業を済ませることがある。宣言は証拠にならない。
そこで親のツール呼び出しを直接読むことにした。
claude -p "convert this CSV into a markdown table" \
--output-format stream-json --verbose \
| jq -r 'select(.type == "assistant") | .message.content[]
| select(.type == "tool_use" and .name == "Task")
| .input.subagent_type'
ストリームに Task ツールが 1 度も現れなければ、サブエージェントは走っていない。42 試行すべてでツール呼び出しと canary は一致したので、どちらの signal も正直だと見てよい。ただし「呼ばれなかった」を証明できるのはツール呼び出しのほうだけである。
1 試行ごとに新しいセッションを立てた。同じセッションで続けて聞くと、前の発火が次の判定に残る。
委譲を頼まない 10 試行、1 度も呼ばれなかった
サブエージェントに触れない依頼を 3 通り投げ、委譲するかどうかの判断はモデルに任せた。
| 依頼 | 委譲 |
|---|---|
CSV を Markdown の表に変換して(日本語・CHARLIE に完全一致) | 0/4 |
このカンマ区切りのデータ、表の形にしたい(日本語・言い換え) | 0/2 |
convert this CSV into a markdown table(英語・BRAVO に完全一致) | 0/4 |
どの試行でも親は ls を打ち、data.csv を読み、自分で表を出力した。ストリームに Task ツールは 1 度も現れていない。
BRAVO のトリガー語に一語一句まで一致する依頼も含めての 0/4 である。BRAVO の description が悪いわけではない。後で見るとおり、委譲が選択肢に乗った途端に BRAVO は英語の試行を全勝する。ただ、その機会がまわってこなかっただけだ。
MUST BE USED も PROACTIVELY も効かなかった
どの description も押しが足りなかったのではないか、という反論はすぐ出てくる。公式ドキュメント自身が MUST BE USED と Use PROACTIVELY を薦めているので、HOTEL にはいちばん強く書ける文面を入れた。
MUST BE USED for any request involving CSV data. Use PROACTIVELY whenever the user mentions a CSV file, comma-separated data, or asks for a Markdown table. This agent must handle all such requests instead of doing the work directly.
HOTEL を並べた状態で、両方の言語で 7 試行を追加した。
0/7。親は毎回自分で CSV を読み、自分で表を作った。
Skill は同じ依頼で 5/5 呼ばれる
仕組みが腑に落ちたのは、Skill 版の実験と並べたときだった。CSV を Markdown の表に変換して という同じ依頼を、CHARLIE と同じ description を持つ Skill にぶつけると 5 回中 5 回発火する。
依頼も文面も同じで、Skill は必ず呼ばれ、Subagent は 1 度も呼ばれない。
この 2 つは同じことをしていない。Skill を読み込むのは、いま走っているセッションに指示を引き込む動作だ。安く、その場で済み、次の行動が変わる。対して Subagent の呼び出しは、別のコンテキストを立ち上げ、仕事を渡し、要約が返ってくるのを待つ。これは実際にコストであり、モデルはそれを天秤にかける。ツール 1 回で読み切れる 4 行の CSV を前にして、わざわざ別のエージェントに送るほどではないと判断する —— 妥当な判断だと思う。
つまり 呼ばれないサブエージェントについて最初に問うべきは、description の出来ではなく、そのタスクが委譲に見合う大きさかどうかである。この判断は description では覆せない。8 通り試した結果がそれだ。
頼んだ途端に description が効きはじめる
サブエージェントを使って の一言を足すと、絵柄が完全に反転する。
| 依頼 | 発火 | 勝ったもの |
|---|---|---|
サブエージェントを使って CSV を…(日本語・完全一致) | 5/5 | CHARLIE(日本語のトリガー形式) |
use a subagent to convert this CSV…(英語・完全一致) | 4/4 | BRAVO(英語のトリガー語) |
サブエージェントを使って、data.csv を README に貼れる形に…(遠い言い方) | 4/4 | ECHO(同義語の列挙) |
13 試行で 13 回、揺れは一切なかった。Skill が言い換えで 5 回中 3 回まで落ちたのと比べても決定的である。そしてこの 13 回の中では、description の書き方がはっきり効いている。
- 依頼の言語と一致する description が勝つ。同じ機能を説明していても、BRAVO が日本語の依頼で勝つことはなく、CHARLIE が英語の依頼で勝つこともない
- ALPHA・FOXTROT・GOLF は 1 度も呼ばれなかった。トリガー語のない機能説明、4 語、80 語超のいずれもが、1 文プラス トリガー語に全敗した。Skill のときと同じ結果である
- 同義語は遠い言い方を拾う。ECHO が「README に貼れる形に」で勝ったのは、description に
formatted for a READMEがそのまま入っていたからだ。仕掛けはそれだけである
「複数行だと拾われない」は subagent でも成立しない
Reddit で広く共有された TIL は、description が単一行でないと拾われないと言う。BRAVO と DELTA は内容が完全に同一で書式だけが違うので、これは直接試せる。以下の 4 条件はすべて英語の委譲依頼で回した。
| 条件 | 結果 |
|---|---|
| 8 本すべて設置 | BRAVO 4/4、DELTA 0/4 |
probe-bravo を外す | DELTA 3/3 |
| description を入れ替え(bravo が複数行になる) | probe-bravo 3/3 |
probe-bravo を probe-xray にリネーム | probe-xray 3、probe-delta 3 |
書式は関係ない。DELTA の 0/4 は解析に失敗していたのではなく、競合に負け続けていただけだ。BRAVO を外せば DELTA が全勝する。description を入れ替えると、拾われないはずの複数行のテキストを抱えたまま probe-bravo が勝ち続ける。
決めているのは名前ということになる。ここまでは Skill の結果とぴったり重なる。
重ならなかったのは、そこから先の法則のほうだった。Skill では probe-bravo を probe-xray にリネームした途端に probe-delta が 3/3 で勝ち、アルファベット順ですべての試行が説明できた。同じことを Subagent でやると 3 対 3 に割れる。並び順が効いているなら probe-delta が総取りするはずだが、そうならない。
リネームで結果が変わる以上、名前は signal の一部ではある。ただし Subagent の選択を裏で決めている同点処理は、Skill のそれとは別物だ。n=6 でその正体に名前を付ける気はない。実務上の読み方はどちらでも変わらない —— 同点は自分の手の届かないところで決まるので、打ち手は同点を作らないことになる。
呼ばれないときに見る順番
効き目の大きい順に並べる。
- そのタスクは委譲に見合うか。親がツール 1、2 回で終えられる仕事はその場で処理される。今回測った中ではこれが他のすべてを圧倒していて、17 試行すべてで委譲は起きなかった
- 必要なら明示する。
サブエージェントを使っての一言で 0/17 が 13/13 になった。名指しでも効く。明示的に頼むことを恥じる理由はない MUST BE USEDは強制ではない。コスト判断と競合するただのヒントであり、7 回中 7 回とも負けた。保証として扱わないこと- 機能ではなく、使う場面を書く。
Converts CSV data into a Markdown tableは 1 度も勝てなかった。同じ文にUse when the user asks to...を足しただけで、英語の試行を全勝している - ユーザーの言語に合わせる。言語が違う description は、仕事の内容をより正確に説明していても、言語が合っているほうに負ける
- 改行は入れてよい。いちばん読みやすい書き方で書けばよい
- 本当の失敗要因は守備範囲の重複である。2 本のエージェントが同じ依頼を取りうるなら、決定は自分の制御できない何かに委ねられる。競合しなくなるまで片方を絞る
測っていないこと
Claude Code 2.1.241。全試行を claude -p の新規セッションで回し、委譲の有無は親の Task ツール呼び出しから直接取っている。
タスクは意図的に小さくしてある。人が実際にぶつかっている問題がその形をしているからだ —— 定義は正しく見えるのに走らないサブエージェント。閾値は測っていない。「CSV を 1 本読む」と「40 ファイルを監査する」のあいだのどこかで親は自分から委譲を始めるはずで、その境目を探すのは別の実験になる。
固いのは 0/17 のほうである。8 通りの description、2 つの言語、強制語のありなしを通して、委譲が 1 度も起きなかった。リネームの 3 対 3 は固くない。Skill で見えたアルファベット順の法則が転移しないと言っているだけで、それ以上ではない。
手法は AGENTS.md を読むかどうかの実測(英語)、Skill の description の実測と同じだ。よそに存在しない事実を仕込み、モデルがごまかせる経路を塞ぎ、答えが動かなくなるまで回数を回す。3 本とも、広まっている助言は的を外していた。