ども!サブエージェントを43本まで増やして、メンテが終わらなくなった龍ちゃんです。
エージェント、増えますよね。観点を思いつくたびに1本作れるので、止まらないんですよ。
結論から言うと、agent から観点を抜きました。何を見るかは別のファイルに置いて、呼ぶ側からそのファイルの場所を渡す。agent 側には道具と権限とモデルだけを残しました。今日はその話をします。
サブエージェントが大量に繁殖、管理が大変に
観点を思いつくたびに1本作る、をやっていました。技術的な正確さ、文体、SEO、企業リスク、論理、読者目線。増えるのは自然なんですよね。観点は次々思いつくし、1本作るコストは低いので。1体に欲張って失敗して3エージェントに分けた話も書いてます。分けたのは正解でした。今日の話は、分ける先が agent から観点ファイルに変わったという続きです。
先に断っておくと、ここで言う「増える」は実行中にサブエージェントが再帰的に呼ばれる方の話ではないです。.claude/agents/ に定義ファイルが積み上がる方ですね。
しんどいのはその後です。40本を超えたあたりから、どれがどういう状態なのか分からなくなりました。直さないといけない箇所に見当はついているのに、1本ずつ開いて直す作業が誰にも回されないまま残るわけですね。
で、6月にClaude Code スキルの誤発火を防ぐ「ルーター集約」設計という記事を書いています。スキルを個別に発火させるのをやめて、1つのルーターに束ねて発火条件を揃えた話です。あれは効きました。狙ったものがちゃんと発火するようになったし、記事の中で「メンテも楽になりました」とも書いてます。関連ファイルが文脈で1か所にまとまったので、どれを見ればいいか迷わなくなったのは今でも事実です。
ただ、当時こうも書いていました。
観点を足したいときは references を 1 ファイル直すだけです。SKILL.md にも agent 本体にもベタ書きしないから、常時のコンテキストは増えないし、ナレッジの管理も一箇所で済むんですよね。
書いた本人としては本気だったんですけど、実態はそうなりませんでした。監査したら、references に置いたはずの語彙リストが agent 本体にもほぼ逐語で35行コピーされていたんですよね。
理由は、どっちに何を書くかを決めていなかったことです。「ベタ書きしない」は方針ではあるけど、境界ではないんですよ。書くたびに「これは agent の本文なのか、references なのか」を考えることになって、判断がぶれた分だけ両方に書かれる。そして直すときにも同じ判断が発生します。観点を1つ直したいだけなのに、agent を開くのか references を開くのかを先に決めないといけない。この判断がずっと乗っかっているのが、メンテが終わらない理由でした。
しかも本数は減らないんですよ。観点を1つ足すには、ルーターの振り分け表に1行足して、agent を1本作って、references も直す。観点1つの追加で3ファイル編集でした。ルーターは呼び出し口を1つにしてくれますが、呼ばれる側の数はそのままなんですよね。
観点を尖らせたいのに、下手に手を入れることができない
文脈で束ねた結果、同じ agent が別の場所でも活躍するようになったんですよね。
たとえばセキュリティ観点のレビュー agent を1体作って、API の実装レビューで使い始めたとします。動くと分かったら、インフラの設定レビューでも呼びたくなるんですよ。観点は同じなので当然そうなるし、この時点では嬉しいだけです。
うちでそうなったのが論理チェックの1体でした。ブログでもプレゼンでもセミナーでも提案書でも「論理」は要るので、この1体が持ち場を増やしていく。git を遡って数えたら、こうなってました。
| 時点 | 論理チェックを呼んでいたルーターの数 |
|---|---|
| ルーター集約の直前 | 1対1(観点ごとにスキルがあった) |
| 集約した直後 | 3 |
| レビュー系を整理した後 | 5 |
| 移行の直前 | 6 |
2つ以上のルーターから呼ばれている agent は、43本中14本ありました。使い回せる状態になったのは、ある意味では成果です。ただ、そこで観点を尖らせようとするとルーター側で修正を入れても、agent側の定義と喧嘩したりとにかく大変なんですね。

プレゼンのレビューを鋭くしたくて「スライド1枚に主張が1つか」を1行足すと、ブログのレビューでスライドを探すみたいな感じです。ブログには要らない観点が同居する。隣に余計なものが入った状態ですね。じゃあ混ざらないように、どっちでも通る書き方に薄めると、今度は指摘が当たり障りのないものになります。尖らせると混ざるし、混ざらないようにすると鈍る。
コードの共通化とまったく同じ形です。共通関数にまとめると呼び出し元を全部気にしないといけなくて、コピペすると直し漏れる。あれですね。結局やりたかったのは、agent は使い回したまま観点だけ分けることでした。
ちなみにこれ、43本ないと起きない話じゃないです。2つ目の呼び出し元が同じ観点を呼びたくなった時点で始まります。うちは43本まで放置したから派手に見えてますけど、5本でも同じ形で詰まりますね。
agent から観点を抜いて、ファイルに追い出しました
やったことは1つです。agent の責務と観点ファイルの責務を切り分けました。
この agent は10本のルーターから呼ばれるので、持たせたのは実行環境と返し方だけにしました。どのモデルで動くか、どの道具を持つか、どの権限か、結果をどう返すか。観点ファイルが持つのは何を見るか、どう掃くか、どう深刻度をつけるか。呼ぶ側は観点ファイルのパスを渡すだけで、中身は渡しません。
観点を持たないので、こうなった agent のことを僕は器と呼んでます。中身が空の筐体だけ残した、という意味ですね。以下そう書きますね。

境界を決めたので、書くときも直すときも迷わなくなりました。観点を直したいなら観点ファイルを開く。道具や権限を変えたいなら agent を開く。それだけです。一般化するのは器だけにして、観点は尖らせたまま外に置く、という形ですね。
器の実物がこれです。全部で42行で、要点だけ抜くとこうなります。
---
name: ro-sonnet
description: >-
router skill から「lens/persona ファイルのパス + 対象ファイルのパス」を委譲プロンプトで
受け取り、その定義だけに従って読み取り専用で文書を検査する汎用ワーカー(sonnet)。
tools: [Read, Grep, Glob]
model: sonnet
---
あなたは渡された lens / persona ファイルの定義だけに従って動く汎用ワーカーです。
自分の判断で観点を足さない。定義に無いことは見ない。
## 手順
1. 委譲プロンプトから lens/persona ファイルのパスを取得して Read する
2. lens の `## ナレッジベース` に参照パスがあれば、それらを Read する
3. lens の `## 入力` に従って対象ファイルを Read する
4. lens の `## 走査手順` に従って掃く。判定は `## 検出対象` のみで行う
5. lens の `## 出力形式` で返す
観点はどこにも書いてません。「論理」も「文体」も「SEO」も出てこない。中身は別のファイルが持っていて、ルーターが起動時にそのパスを渡します。
ちなみに名前も実行環境だけでできてます。ro- は read only、rw- は Write を持つ側で、後ろはモデルか足した道具です(ro-sonnet は読み取りだけの sonnet)。logic-reviewer だった頃と違って、観点は名前にも入ってません。
コードに出てくる lens が、その観点ファイルです。うちのリポでファイル名をそう付けているだけなので、以下は観点ファイルと書きますね。人格を持つものだけ persona- を頭に付けて分けていて、厳しめレビューの persona-harsh.md がそれです。作りは同じで、名前だけ変えてます。
観点ファイルの中身は、こういう節でできてます。
## 入力 ← 何を読むか
## 検出対象 ← 何を見つけたら指摘するか
## 走査手順 ← どういう順で掃くか
## 判定軸 ← 過剰検出をどこで止めるか
## 出力形式 ← どう返すか
「論理だけ見て」と言われたらルーターが lens-logic.md のパスを渡すし、「厳しめで」なら persona-harsh.md を渡す。器は同じです。ここはルータの定義としてSKILLの中で管理することができます。
面白いのは、使い回すのをやめたわけじゃないところですね。いまこの器は10本のルーターから呼ばれてます。前は6本で混ざっていたのに、10本に増えたのに混ざらない。使い回していたものが「観点を持つ1体」から「観点を持たない器」に変わっただけです。(なのでリモート端末みたいな使い方をしていますね。その都度インストールするみたいな)
観点そのものの中身は記事を分けて書いていて、書く前レビューのエージェントとか文体を直すエージェントあたりに置いてあります。今日は中身ではなく置き場の話ですね。
何が良くなったか
3つあります。
いちばん効いたのは、agent をメンテしなくなったことです。移行してから18日で .claude/agents/ を触ったコミットは5つだけでした。しかもどれも、観点を足したからではないんですよね。観点は増えても agent は増えない。
次に、新しい観点の試し方が変わりました。前は「この観点いいな」と思ったら agent を新設するところから始まっていて、それが地味に重い。いまはチャットでそのまま観点を投げて試して、使えそうなら観点ファイルに上げるだけです。ファイルは移行した時点で41枚、いまは46枚に増えました。増やす方向が安くなったのがうれしいところですね。
3つめは budget です。agent の description は起動時に常時載るので、本数がそのまま重さになるんですよね。実測では 7,318字 が 1,604字 になりました。1本あたりの長さはほとんど変えていないので、減ったぶんは全部「本数が消えたこと」から来ています。無駄にトークンを食い散らかしている感覚もなくなりました。ただ実行時のトークンは測っていないので、そこは体感ですね。言い切れるのは起動時に常時載る量の話だけにしておきます。
その分 SKILL ファイルは大きくなりましたが、段階的開示(progressive disclosure)が効きます。常時載るのは description だけで、SKILL.md の本体は発火したとき、references/ は必要になったときにしか読まれないんですよね。
この形を採用するなら1つ気をつけてください。器は使い回す前提なので、器を1本直すと、その器を呼んでいる全部のルーターに同時に響きます。 model を変える、道具を1つ足す、手順を1行直す。どれも全部に効きます。観点が混ざる問題が消えたわけじゃなくて、響く範囲を「観点を持たない部分」に狭めただけなんですよね。
移行が本当に劣化していないかの検証は、これだけで記事1本になる分量なので別で書きます。
公式はSKILLとsubAgentをどう分けているのか、読み直しました
ここまで書いて、公式のドキュメントと食い違ってないか不安になったので読み直しました。Claude Code のサブエージェントは、こう説明されています。
The body becomes the system prompt that guides the subagent’s behavior. (本文がサブエージェントの振る舞いを導く system prompt になる)
The body should contain the persona and workflow instructions. (本文には人格と手順を書くべき)
人格は agent の本文に書け、と明記されてます。じゃあ再利用したい知識はどこに置くのかというと、そっちにも仕組みがあって、skills フィールドでスキルの内容をサブエージェントの起動時に注入できます。公式の説明では「reusable domain knowledge を注入する」用途ですね。人格は agent、再利用する知識は skill、という分け方に見えます。
で、僕はここで一度「自分は逸脱してたのか」と思ったんですけど、公式のサンプルを見て止まりました。
---
name: code-reviewer
description: Reviews code for quality and best practices
tools: Read, Glob, Grep
model: sonnet
---
You are a code reviewer. When invoked, analyze the code and provide
specific, actionable feedback on quality, security, and best practices.
これ、レビュー観点が本文に書かれてるんですよね。「品質・セキュリティ・ベストプラクティスを見ろ」は人格でもあり観点でもある。つまり内容では割れません。観点は agent 側なのか skill 側なのか、という問いには答えが出ないんです。
割れる軸は別のところにありました。固定なのか、差し替えなのかです。
呼び出し元が1つで、その観点がずっと変わらないなら、公式のサンプルどおり本文に書いていい。困らないです。破れるのは2つ目の呼び出し元が同じ観点を欲しがった瞬間で、そこから先は人格ではなく引数になる。人格を使い回すと混ざって、混ざらないように一般化すると鈍る。公式の書き方は「1つの agent に1つの固定人格」を前提にしていて、人格が引数になる場合を想定していないんですよね。

具体で言うとこうです。レビューの器を1本作るとして、「あなたはレビューアーです」までは焼き込んでいい。呼ばれ方が変わっても、そこは動かないからです。変わるのはフロントを見るのかバックエンドを見るのかで、そこを lens-frontend.md と lens-backend.md に出す。どっちのパスを渡すかは、呼ぶ側のレビュースキルが決めます。
僕は器を機能で切ったので(読み取りだけ、書き込みあり、Bash あり)レビュー専用の器は持ってません。でもレビュー専用に1本立てて人格を焼き込むのは、この形と矛盾しません。基準は「呼ばれ方が変わっても動かないか」だけで、使い回す範囲が広いほど焼き込めるものが減る、という関係なんですよね。
なお公式には「サブエージェントは1つのタスクに集中させろ」というガイドもあります。字面だけ見ると器は反しているように見えますが、これは守ってます。1回の起動は器1本と観点ファイル1枚の組み合わせで、走っている実行体は1つの観点しか見ません。焦点を与えるタイミングがファイルの中か起動時かの違いだけですね。
公式には、サブエージェントに知識を渡す skills フィールドもあります。うちでは使っていません。あれは列挙したスキルを、サブエージェントが立ち上がるたびに全文注入する仕組みで、列挙するのは agent の定義なので呼び出しごとに変えられないんですよね。
つまり skills に書くのは「呼ばれ方が変わってもこの知識は要る」と宣言することです。さっきの基準がそのまま当たります。レビューの器にレビュー規約を1つ載せるなら妥当だし、公式の例も API 開発のエージェントに規約とエラー処理のパターンを載せる形です。でも観点46枚を載せると、1枚しか要らない起動で45枚ぶん払うことになる。だからパスを渡して、必要な1枚だけ読ませています。
知識を references 側に構造化する話はClaude Code に専門知識を仕込む記事の続きですね。
自分のリポで何から始めるか
持ち帰りとしては、判断はこの1問で足ります。
その観点、2つ目の呼び出し元が欲しがりますか?
欲しがらないなら agent の本文に書いたままでいいです。欲しがったら、それはもう人格じゃなくて引数なので外に出す。手を動かす順番はこうしました。
まず model と tools と権限で並べて数える
これが効きました。43本を「どのモデルか・どの道具を持っているか・どの権限か」だけで並べたら、8種類しかなかったんですよね。

18本が同じ1マスに入ってます。43本それぞれに個性があると思っていたのに、実行環境としては18本が完全に同じだったわけですね。違っていたのは観点だけでした。ここで「じゃあ観点を外に出せば、器はこの数でいい」が見えます。Read だけの7本は Read / Grep / Glob と実質差がないので上に寄せて、器は7種類になりました。
モデルの割り当てはOpus で考え、Sonnet で動かすで決めたものがそのまま残っています。器はモデルを軸に分けているので、あの配分が8種類の骨になってますね。
この7種類は移行した時点の分類で、そのあと1本増えて8本になっています。深い一次ソース追跡をやりたくなって、子のワーカーを起こせる道具を足したやつですね。道具や権限が変わるものが出てきたときだけ増えるので、観点をいくら足しても本数は動きません。
2つ目の呼び出し元が欲しがる観点だけ、ファイルに出す
全部を一気に出さなくていいです。うちは最初に表現チェックの3枚だけ切り出して、検出が落ちていないか確かめてから残りを移しました。
出すときに1つだけ注意があって、掃き方も一緒に出さないと拾えなくなります。最初にやったとき、「何を見るか」だけを観点ファイルに書いて、「どういう順で走査するか」は器の共通手順に畳んだんですよ。そうしたら同じパターンが3件あるうちの1件しか拾えませんでした。上から順に読んで気づいたものを挙げる、で済ませてしまうんですね。走査の手順は観点ごとに固有なので、観点ファイル側に必須の節として持たせました。
どこからでも呼ばれるものは、特化のまま置く
そして全部を器にしなくていいです。うちにも特化のまま残しているエージェントが3本あって、どれも検索用です。ブログ記事の検索、リサーチの検索、過去の教訓の検索ですね。検索はどんな作業をしていても発生するので、どの文脈からでも呼ばれます。つまり呼ぶ側が特定のルーターに決まっていない。器は「ルーターが観点のパスを渡す」前提の作りなので、渡す人がいない呼び方とは噛み合わないんですよ。だから観点を自分で持ったまま置いてます。
いまは11本です。器が8本と、特化が3本ですね。43本あったときと比べて、観点の数はむしろ増えました。まとめるか分けるかで悩んでいたところが、器は使い回して観点は分ける、という形で終わりました。
まず .claude/agents/ を model と tools と権限だけで並べてみてください。同じマスに何本入っているかを見るのが、いちばん早いと思います。
コラム:公式のやり方でよくないですか?
たぶんここが気になった人がいると思うので、書いておきます。context: fork を使っていない人は、前半は飛ばして大丈夫です。
Claude Code のスキルには context: fork という書き方があって(2.1.0・2026年1月から)、スキルの本文を別のコンテキストで走らせられます。しかも agent: を付けると実行体を選べて、公式ドキュメントにはこう書いてあります。
Options include built-in agents (
Explore,Plan,general-purpose) or any custom subagent from.claude/agents/. (組み込みエージェント、または.claude/agents/の任意のカスタムサブエージェントを指定できる)
つまり context: fork に agent: ro-sonnet と書けば、公式の機能だけで器に載ります。ここは認めます。
ただ、それをやると観点が SKILL.md の本文になるんですよね。タスクがファイルに固定される仕組みなので、観点を1つ足すたびにスキルが1つ増えます。そしてスキルの一覧は context: fork かどうかに関係なく起動時に全部載って、character budget を食う。budget を超えると description が切り詰められて、マッチに必要なキーワードごと落ちる。これ、僕がルーター集約の記事で書いた症状そのものです。観点46枚を全部スキルにしたら、束ねる前の状態に逆走します。
あと公式ドキュメント自体がこう警告してます。
context: forkonly makes sense for skills with explicit instructions. If your skill contains guidelines like “use these API conventions” without a task, the subagent receives the guidelines but no actionable prompt, and returns without meaningful output. (明示的な指示を持つスキルにしか意味がない。タスクのないガイドラインだけだと、受け取っても実行可能なプロンプトがなく、意味のある出力を返さずに終わる)
うちの観点ファイルはまさにガイドライン側なので、そのまま載せても動かないんですね。
「じゃあ組み込みの general-purpose を呼べばいいのでは」も同じ形です。あれはサブエージェントが使える道具を全部持っているので、読み取りだけに絞る、ができないんですよね。モデルは呼び出しごとに上書きできますが、道具は agent 定義で固定なので、呼ぶ側から渡す口がない。結果、観点も道具の指定も SKILL 側に書くことになって、器に書いてあったことを書き写すだけになります。
逆に言うと、観点が少ないなら公式の書き方で足ります。知識を SKILL.md の本文に畳めるなら、わざわざパスを渡す二段構えにする必要はないです。個数が問題になってくるのは観点が数十枚に育ってからで、そこまで来た人だけが今日の話を使えばいいと思ってます。
ほなまた〜


