サブエージェントのトークン、4割はCLAUDE.mdだった

サブエージェントのトークン、4割はCLAUDE.mdだった

毎日動かしている Claude Code の定期ルーティンの使用量を減らしたくて、サブエージェントのモデルを Opus から Sonnet に切り替える検討をしました。

切り替える前に過去の実行記録を集計すると、消費の 98% は入力で、モデルを替えても減るのは約 5% だと分かりました。

代わりに見つかったのは、規約ファイルの変更履歴が毎ターン読み込まれ、全入力の約 4 割を占めていたことです。

構成は、Claude Code(Anthropic の CLI 型コーディングエージェント)の親セッション 1 本から、7〜9 本のサブエージェントを並行に起動する作りです。

CLAUDE.md はプロジェクトの規約ファイルで、親にもサブエージェントにも文脈として読み込まれ、毎ターンの入力に含まれます。

サブエージェントのモデルは呼び出しごとに選べる

Claude Code では、サブエージェントを起動するたびに使うモデルを指定できます。

公式ドキュメントによると、サブエージェントのモデルは次の順で決まります。

  1. 呼び出し時に渡す model パラメータ
  2. サブエージェント定義(frontmatter)の model
  3. 環境変数 CLAUDE_CODE_SUBAGENT_MODEL
  4. メイン会話のモデル

opus や sonnet のようなエイリアスでも指定でき、メイン会話と同じ系列のエイリアスを渡すと、メイン会話とまったく同じモデルで動きます。

順番の最後が「メイン会話のモデル」なので、入れ子のサブエージェントで指定を省くと、起動元ではなくメイン会話のモデルになります。

入れ子は既定でメイン会話から 3 層下までで、定義の effort を使えばセッションとは別の effort レベルも指定できます。

仕様の一次情報は、公式ドキュメントの Create custom subagents です。

Create custom subagents - Claude Code Docs

仕組みの上では、工程ごとに安いモデルへ振り分けられることを確かめました。

残る問題は、振り分けたらどれだけ減るのかでした。

実行記録の usage から内訳を出す

手元の Claude Code では、セッションごとのフォルダの下に、サブエージェントの記録が 1 本ずつ JSONL ファイルで残っています。

同じ名前の meta.json には、起動時に付けた説明と入れ子の深さ、起動元の ID が入っていました。

JSONL の assistant の行には、そのターンの入力・キャッシュ読み取り・キャッシュ書き込み・出力のトークン数が usage として記録されます。

1 つの応答は複数の行に分けて記録されるので、同じ message id が何度も出てきます。

単純に足すと二重に数えてしまうため、id ごとに各項目の最大値を採りました。

集計に使ったコードからの抜粋です。

# path は 1 本の JSONL(import と集計結果の出力は省略)
per = {}
for line in open(path, encoding="utf-8"):
    rec = json.loads(line)
    msg = rec.get("message")
    if not isinstance(msg, dict) or msg.get("role") != "assistant":
        continue
    u = msg.get("usage") or {}
    cur = per.setdefault(msg["id"], {"in": 0, "out": 0, "cr": 0, "cw": 0})
    cur["in"] = max(cur["in"], u.get("input_tokens", 0))
    cur["out"] = max(cur["out"], u.get("output_tokens", 0))
    cur["cr"] = max(cur["cr"], u.get("cache_read_input_tokens", 0))
    cur["cw"] = max(cur["cw"], u.get("cache_creation_input_tokens", 0))

説明と入れ子の深さで工程ごとに束ねると、使用量の割合は次のとおりでした。

工程使用量の割合
情報の収集と反証(中でさらにサブエージェントを起動)29%(うち入れ子の分 21%)
プルリクエストの作成とレビュー29%
記事の執筆と投稿18%
別系統の記事作業8%
親セッション7%
その他8%

消費の 98% は入力だった

直近 5 回(9 月末〜10 月 1 日)の実行を API の単価で換算すると、1 回あたり約 119 ドルでした。

私は Max プランで動かしているので、これは実際の支払いではなく、使用量の大きさを比べるための目安です。

内訳はキャッシュ読み取りが約 66%、キャッシュ書き込みが約 31%、出力が約 2% でした。

1 回の実行で親とサブエージェントを合わせて約 1,440 ターンあり、キャッシュ読み取りだけで約 4 億トークンに達していました。

エージェントは 1 ターンごとに、それまでの文脈を丸ごと読み直してから次の 1 手を決めます。

出力が少なくても、文脈が大きくターン数が多ければ、読み直しだけで使用量は膨らみます。

サブエージェントの出力は 1〜3% にとどまり、出力が 15% あったのは親セッションだけでした。

次に、モデルを替えたときの単価を公式の価格表で比べました。

100 万トークンあたり(2026 年 10 月時点)Opus 5.5Sonnet 5.5
入力$4$2
キャッシュ書き込み(5 分)$5$2.50
キャッシュ書き込み(1 時間)$8$4
キャッシュ読み取り$0.20$0.20
出力$20$10

入力と出力は Sonnet 5.5 が半額ですが、キャッシュ読み取りの単価はどちらも 100 万トークンあたり 0.20 ドルです。

Opus 5.5 はキャッシュ読み取りが入力単価の 5% に設定されていて、Sonnet 5.5 は通常どおり 1 割なので、結果として同じ額になるためです。

価格は Anthropic の公式の価格表で、2026 年 10 月時点の値を確かめました。

Pricing

消費の 3 分の 2 を占めるキャッシュ読み取りが同額なので、Sonnet に替えても、そのサブエージェントの換算額は 15〜30% しか下がりません。

収集や検証の工程と Python 主体の工程を全部 Sonnet にした場合の試算でも、減るのは 1 回あたり約 6 ドル、全体の約 5% でした。

キャッシュの読み取りと書き込みで単価が変わる仕組みは、別の記事で整理しています。

プロンプトキャッシュとは?API料金を下げる仕組みを整理

起動した時点で 15 万トークンを抱えていた

入力が支配的なら、次に見るべきは、毎ターン読み直している文脈に何が入っているかです。

そこで、サブエージェントが最初に応答したターンの文脈量(入力・キャッシュ読み取り・キャッシュ書き込みの合計)を数えました。

# 最初の assistant の行だけを見る(前後は省略)
for line in open(path, encoding="utf-8"):
    msg = json.loads(line).get("message")
    if isinstance(msg, dict) and msg.get("role") == "assistant":
        u = msg["usage"]
        first = (u.get("input_tokens", 0)
                 + u.get("cache_read_input_tokens", 0)
                 + u.get("cache_creation_input_tokens", 0))
        break

5 回分 111 本の中央値は約 14.9 万トークンで、どれも 14.5 万〜15.4 万の狭い範囲に収まっていました。

反証を 1 件だけ頼むような小さなサブエージェントでも、起動時の量は変わりませんでした。

仕事に手を付ける前の時点で、すでに 15 万トークン近くを抱えていたことになります。

公式ドキュメントにも、サブエージェントの初期の文脈には、メイン会話が読み込む CLAUDE.md の各階層と AGENTS.md が含まれると書かれています。

次に、8 月 15 日から 10 月 1 日までの 48 回分の起動時の文脈量を、その日に CLAUDE.md から読み込まれていた規約ファイルの文字数で回帰しました。

結果は 1 文字あたり 0.99 トークン、切片が 3.9 万トークンで、規約ファイルが 1 文字増えると起動時の文脈もほぼ 1 トークン増える関係でした。

起動時の文脈は 8 月 15 日の 3.7 万トークンから 10 月 1 日の 15.1 万トークンまで、規約ファイルの変更履歴の伸びとほぼ比例して増えていました。

中身を見ると、規約ファイル 11.7 万字のうち 11.0 万字が変更履歴の表でした。

変更履歴だけで毎ターン約 10.8 万トークンを読み込み、全入力の約 4 割を占めていたことになります。

変更履歴は経緯を調べるときに読めば足りるもので、毎ターンの文脈に載せておく必要はありませんでした。

サブエージェントを並べる構成では、規約ファイルに足した 1 行が、本数とターン数の分だけ掛け算で読み直されます。

毎ターン何を文脈に載せるかを設計する考え方は、コンテキストエンジニアリングの記事にまとめています。

コンテキストエンジニアリングとは?AIに何を渡すかを設計する

変更履歴を外したら起動時の文脈は半分以下になった

対策として、変更履歴を自動では読み込まれない別のファイルへ移し、規約ファイルの本体は 0.75 万字になりました。

回帰の式に当てはめると、起動時の文脈は約 4.7 万トークンまで下がる見込みでした。

移した後の実行で、同じ数え方をした結果が次の表です。

実行日サブエージェント数最小中央値最大
10 月 1 日(移す前)23 本150,285151,112154,304
10 月 2 日(移した後)27 本67,75268,72873,391
10 月 3 日(移した後)20 本69,04869,81970,788

単位はトークンで、10 月 3 日は実行中に数えた時点までの本数です。

起動時の文脈は約 15.1 万トークンから約 6.9 万トークンへ、半分以下になりました。

ただし、予測の 4.7 万トークンよりは 2 万トークンほど多く、予測どおりにはなりませんでした。

移したのと同じ日にモデルの割り当てなど別の変更も入れていて、10 月 3 日の実行には手順書の分割も入っています。

このため、下がった分のすべてを変更履歴の移動のせいにはできず、予測との差の内訳もまだ切り分けていません。

10 月 3 日の実行では、9 本が Sonnet、11 本が Opus で動いていました。

起動時の文脈量の中央値は Sonnet が 69,866、Opus が 69,773 トークンで、ほぼ同じでした。

モデルを替えても、読み込む文脈の量そのものは変わらないことが数字でも見えました。

1 回の実行全体の使用量がどれだけ減ったかは、まだ集計していません。

Max プランの利用枠でモデル別・キャッシュ別にどう数えられるかは公開されていないので、利用枠への効き方も未検証です。

公式には、サブエージェントの定義に omitClaudeMd: true を書くと、ユーザー・プロジェクト・ローカルの CLAUDE.md を読まずに起動できる設定もあります。

組み込みの Explore と Plan は、調査を速く安く済ませるため、もともと CLAUDE.md を読み込みません。

私の構成ではすべて汎用のサブエージェントを使っていて、この設定はまだ試していません。

モデルは後から確かめられる工程だけ替えた

モデルの使い分けは、結果を後から確かめられる工程に絞りました。

Sonnet にしたのは、情報の収集と、実行して確かめられるコードの作業と、Python のスクリプトが主体の工程です。

記事を書く・直す工程と、レビューや公開の判断、反証と裏取りは Opus のままです。

減るのが全体の 5% なら、品質が落ちても気づきにくい工程まで広げる理由はないと判断しました。

割り当ては 1 つの設定ファイルにまとめ、サブエージェントを起動するときは model を省略せずに必ず渡すようにしています。

省略すると、先ほどの解決順どおり、入れ子の中でもメイン会話のモデルになってしまうからです。

新しいモデルが出たことは、毎日の起動時に検知して通知するようにしました。

選べるモデルは対話式の /model ピッカーで見られますが、スクリプトから一覧を取る方法は公式ドキュメントで見つけられませんでした。

そこで、セッションのシステムプロンプトに書かれている最新モデルの行を、確認済みの一覧と突き合わせています。

運用を始めたのは 10 月 2 日で、新しいモデルが出たときに通知が実際に出るかはまだ確かめていません。

最後に

定期ルーティンの使用量は、出力ではなく、毎ターン読み直す文脈の大きさとターン数の掛け算で決まっていました。

モデルの単価を比べても、キャッシュ読み取りが同額だったので、差は 5% ほどにとどまりました。

一方で、規約ファイルの変更履歴を外すだけで、起動時の文脈は半分以下になりました。

モデルを選ぶ前に、セッションを始めた時点の文脈に何が何トークン入っているかを測ると、もっと大きく効く削り先が見つかります。

以上です。

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です

CAPTCHA