Claude Code agent_summaryのトークン消費を測定して減らす

Claude Codeのバックグラウンドエージェントが生成する進捗表示の費用を測定します。あるチームの推定費用に占める16.4%と、Claude Codeチームが報告した全体の2%、ターミナル利用の6%を区別します。ローカルのOpenTelemetry受信プログラムで自分の利用状況を確認し、2.1.311+のキャッシュ修正、エージェントの制御、コミュニティ製パッチを検討します。

目次
Claude Code agent_summaryのトークン消費を測定して減らす

Claude Codeを2.1.311以降に更新し、エージェント機能を無効にする前に、OpenTelemetryでagent_summaryの費用を測定してください。バックグラウンドエージェントの稼働中は、進捗を要約するリクエストによって費用が増えることがあります。広く共有された16.4%は、あるチームの推定費用に占める割合であり、すべての利用者に生じるトークンの追加負担ではありません。

agent_summaryは何をしているのか

agent_summaryは、モデルへの個別のリクエストで、進捗を示す短いラベルを生成します。dblumenauのClaude Code 2.1.312の報告では、稼働中のバックグラウンドエージェントの会話履歴を、そのエージェント自身のモデルに送っていました。求められた出力は3〜5語のラベルです。この再現例ではHaikuを使っていたため、Opusだけの仕組みではありません。

稼働中のバックグラウンドエージェントでは、約30秒ごとにこうしたリクエストが発生することがあります。ただし、公式の変更履歴にあるとおり、会話履歴が変わらなければ生成を省略する場合があります。経過した分数を2倍して求めるのではなく、実際のリクエスト数を数えてください。

そのため、表示されるラベルの短さより、送信するコンテキストの長さが費用に影響することがあります。費用を調べる際は、記録されたモデルとキャッシュの利用状況も確認してください。

この仕組みは、自分で設定できるターミナルのステータス行とは別です。カスタムステータス行のドキュメントにあるスクリプトはローカルで動作し、APIトークンを消費しません。そのスクリプトを削除しても、進捗を要約するモデルリクエストは無効になりません。

16.4%、2%、6%は何を測った数字なのか

これら3つの費用割合は、集計した利用者や利用範囲が異なります。2026-11-01時点で公表されている主な報告は次のとおりです。

報告対象と期間要約にかかった費用の割合費用の算定基準
Flamerix AIあるチームの1週間の測定16.4%Claude Codeがリクエストごとに報告するcost_usdの推定値
Claude Codeチームのedwinarbusテレメトリを送信する利用者。10月29日の返信に先立つ1週間約2%APIの定価で算定した費用
同じ返信にあるターミナル利用の集計上記のテレメトリ対象のうち、ターミナルでの利用約6%APIの定価で算定した費用

Flamerixは別の指標として、要約がAPIリクエスト数の19.7%を占めたとも報告しています。リクエスト数の割合と費用の割合では、計算の分母が異なります。どちらの値からも、自分のサブスクリプションの利用枠を要約がどれだけ消費したかは分かりません。

これらの報告をきっかけに、自分の作業で測定してください。長時間の仕事を多数のエージェントに任せるチームと、主にメインの会話で作業する開発者では、結果が大きく変わる可能性があります。

まず更新し、削減率は測定して確かめる

Claude Codeチームの返信によると、2.1.311+では、プロンプトキャッシュにヒットしない高額な要約リクエストのケースを修正しています。これはキャッシュに関する修正であり、要約リクエストをなくす変更ではありません。個々のセッションで一定の節約が得られるとは限りません。

ネイティブインストーラーで導入している場合は、Bashで次を実行します。

claude --version
claude update
claude --version

更新後はClaude Codeを再起動してください。パッケージマネージャーで管理している場合は、公式セットアップ手順にある、その管理方法の更新コマンドを使います。更新チャンネルや組織のポリシーで利用できる版が制限される場合があるため、更新後のバージョンを確認してください。

同じ返信では、短いメモと小規模モデルを使う、より低コストなモードの開発にも触れています。公開日は示されていません。2026-11-01時点では、そのモードを有効にする公開設定は文書化されていません。まだ、このモードによる節約を前提に予算を組まないでください。

OpenTelemetryで自分のセッションを測定する

claude_code.api_requestのログイベントを収集し、query_sourceで集計します。query_sourceは、そのリクエストを発行した内部の処理を示す属性です。モニタリングのリファレンスには、この属性、使用モデルを示すmodel、推定費用のcost_usdが記載されています。

組織ですでにこれらのイベントを収集している場合は、その収集先を使ってください。1つのセッションか、範囲を固定した時間帯に絞ります。送信元ごとにAPIリクエストのイベント数を数え、cost_usdを合計してください。モデルを比較するときは、modelも集計の軸に加えます。

以下のローカル測定例には、Bash、Python 3、認証が完了しているClaude Code環境が必要です。テレメトリの送信先を自分で設定できるマシンで実行してください。受信プログラムは集計値をメモリ内に保持し、送信元やモデルなどのメタデータを表示します。この測定のために、プロンプト本文や応答本文をそのまま保存する設定を有効にする必要はありません。

ローカル受信プログラムを起動する

1つ目のターミナルで次を実行します。同じマシンからの接続を受けるループバックアドレスで、OTLP HTTP JSONを受け付けます。受信するたびに、送信元ごとの累積集計を表示します。

python3 -u - <<'PY'
import gzip, json, math
from collections import defaultdict
from http.server import BaseHTTPRequestHandler, HTTPServer

rows = defaultdict(lambda: [0, 0.0, 0])
models = defaultdict(set)
seen = set()

def scalar(value):
    return next((value[k] for k in ("stringValue", "doubleValue", "intValue", "boolValue")
                 if k in value), None)

def report():
    count = sum(row[0] for row in rows.values())
    cost = sum(row[1] for row in rows.values())
    missing = sum(row[2] for row in rows.values())
    print("\nquery_source\trequests\trequest_%\tknown_USD\tcost_%\tmissing_cost\tmodels")
    for source, (n, usd, absent) in sorted(rows.items()):
        share = f"{100 * usd / cost:.2f}" if cost > 0 and not missing else "n/a"
        names = ",".join(sorted(models[source]))
        print(f"{source}\t{n}\t{100 * n / count:.2f}\t{usd:.6f}\t{share}\t{absent}\t{names}")
    print(f"Total API records: {count}; missing costs: {missing}", flush=True)

class Receiver(BaseHTTPRequestHandler):
    def log_message(self, *args):
        pass

    def do_POST(self):
        if self.path != "/v1/logs":
            self.send_error(404)
            return
        raw = self.rfile.read(int(self.headers.get("Content-Length", "0")))
        try:
            if self.headers.get("Content-Encoding") == "gzip":
                raw = gzip.decompress(raw)
            data = json.loads(raw)
        except (ValueError, OSError):
            self.send_error(400)
            return
        for resource in data.get("resourceLogs", []):
            for scope in resource.get("scopeLogs", []):
                for record in scope.get("logRecords", []):
                    a = {item["key"]: scalar(item["value"])
                         for item in record.get("attributes", [])}
                    event = a.get("event.name") or scalar(record.get("body", {}))
                    if event in ("user_prompt", "claude_code.user_prompt"):
                        print("Received user_prompt event.", flush=True)
                    if event not in ("api_request", "claude_code.api_request"):
                        continue
                    identity = a.get("request_id") or json.dumps(record, sort_keys=True)
                    if identity in seen:
                        continue
                    seen.add(identity)
                    source = str(a.get("query_source") or "(missing)")
                    models[source].add(str(a.get("model") or "(missing)"))
                    row = rows[source]
                    row[0] += 1
                    try:
                        usd = (float(a["cost_usd"]) if a.get("cost_usd") is not None
                               else float(a["cost_usd_micros"]) / 1_000_000)
                        if not math.isfinite(usd) or usd < 0:
                            raise ValueError()
                        row[1] += usd
                    except (KeyError, ValueError, TypeError):
                        row[2] += 1
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(b"{}")
        report()

server = HTTPServer(("127.0.0.1", 4318), Receiver)
print("Listening on 127.0.0.1:4318. Stop after Claude exits with Ctrl+C.", flush=True)
try:
    server.serve_forever()
except KeyboardInterrupt:
    pass
finally:
    server.server_close()
    report()
PY

測定対象のセッションを起動する

2つ目のターミナルで次を実行します。括弧で囲んだサブシェルを使うため、この環境変数の変更は測定対象のプロセスに限られます。

(
  export CLAUDE_CODE_ENABLE_TELEMETRY=1
  export OTEL_LOGS_EXPORTER=otlp
  export OTEL_METRICS_EXPORTER=none
  export OTEL_TRACES_EXPORTER=none
  export OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/json
  export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://127.0.0.1:4318/v1/logs
  export OTEL_LOGS_EXPORT_INTERVAL=1000
  claude
)

バックグラウンド処理が役立つ、普段の作業を選んでください。たとえば、ファイルを変更せずに認証テストを調べるよう、バックグラウンドエージェントに依頼します。メインの会話で作業を続けながら、そのエージェントが実際に起動したことを確認してください。

対話型のセッションを維持し、Claudeの出力をパイプに渡さないでください。claude -pに置き換えると実行モードが変わり、要約の動作も変わる可能性があります。

プロンプトを送信すると、1つ目のターミナルにReceived user_prompt event.と表示されるはずです。その後、APIリクエストの集計行が表示されます。作業が終わったらClaudeを通常の方法で終了し、最後のテレメトリ送信が届くまで待ってください。その後、Ctrl+Cで受信プログラムを停止し、ターミナルの表示履歴から最後の表をコピーします。

結果の読み方とデータが欠ける場合の確認

測定した時間帯のagent_summary行を確認してください。request_%の分母は、受信したすべてのAPIリクエストイベント数です。cost_%の分母は、受信したすべてのAPIリクエストの推定費用の合計です。

summary cost share = 100 × summary estimated USD / all captured estimated USD

models列には、各送信元で記録されたモデルが並びます。既存の収集基盤でキャッシュを調べる場合は、input_tokens、cache_read_tokens、cache_creation_tokens、output_tokensも確認してください。

送信元が欠けたリクエストも、(missing)として分母に含まれます。受信したリクエストに有効な費用が1つでも欠けていると、費用の割合はn/aになります。内訳が完全だと判断する前に、欠けた項目を解消してください。

agent_summary行がない場合、その時間帯には観測されなかったという意味です。機能が無効になっている証拠ではありません。まず、プロンプトイベントとAPIリクエストイベントが届いていること、実際にバックグラウンド処理が動いていたことを確認してください。

何も届かない場合は、受信プログラムの起動、http/jsonの指定、送信先が/v1/logsで終わることを確認します。組織が管理する送信先は、ローカル設定より優先される場合があります。さらに調べるには、同じサブシェル内でclaude --debug-file ./otel-debug.logを起動してください。モニタリングの手順に沿って、[3P telemetry]が付いたエクスポーターのエラーを調べます。

会話履歴、/usage、請求額が一致しない理由

会話履歴に行がなくても、セッション全体の費用には含まれている場合があります。その場合、どの処理に費用がかかったかが見えにくくなります。issue #104358でrabitlord1324は、バックグラウンドリクエストの記録が欠ける一方、Claude Code内部の計算は正しいと報告しています。

現在の利用量のドキュメントには、セッション全体とモデル別の利用量が記載されています。ただし、ここで必要なquery_source別の内訳は示されていません。会話履歴を解析するツールが要約を見落としたからといって、/usageにもすべての要約が含まれていないとは判断できません。

cost_usdは推定値です。Claude Codeは通常、定価を使いますが、管理設定のmodelPricingテーブルで報告用の単価を変更できます。サブスクリプションの利用枠の消費とAPIの請求額は別の量です。従量課金を使う場合は、プロバイダーの実際の利用記録と突き合わせてください。

BetterTokenを使う場合、Claude Code用として案内されているベースURLはhttps://bettertoken.aiです。2026-11-01時点のカタログにはclaude-opus-5-5が掲載されています。測定した時間帯について、コンソールのモデル、トークン使用量、請求額と照合してください。

作業の価値を保ちながら費用を減らす

測定した追加費用が問題になる場合は、まず依頼する仕事の範囲を絞るか、バックグラウンドエージェントの数を減らします。2026-11-01時点では、環境変数とサブエージェントのリファレンスに、要約だけを無効にする設定は記載されていません。

対策変更される動作判断時の注意点
バックグラウンドタスクを減らす、または短くする会話履歴を繰り返し要約する機会が減ります役立っていた並列処理も減る場合があります。完了までの時間を比較してください
範囲を限定したサブエージェントに小規模モデルを使う委任した作業を実行するモデルが変わります出力の品質と、テレメトリに記録された実際のモデルを確認してください
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1 claude対象ツールを通じたバックグラウンド実行を広く無効にしますClaudeはサブエージェントの完了を待ってから処理を続けます
CLAUDE_CODE_FORK_SUBAGENT=0 claudeClaudeが自律的に会話を分岐させる動作を無効にしますバックグラウンドのサブエージェントは動作できます。要約の停止は保証されません
Bunのpreloadを使うコミュニティ製パッチ要約タイマーの動作をフックして止めようとしますClaude Code内で第三者のコードを実行します。更新後に機能しなくなる可能性があります

バックグラウンドタスクを制御する変数はrun_in_backgroundを取り除き、対象ツールでの自動的なバックグラウンド移行を止めます。MCPタスク、Monitorツール、独立したバックグラウンドセッションには、例外や別の制御があります。すべてのバックグラウンド処理を止める設定ではありません。

forkモードの制御は文書化されていますが、副作用として要約が止まるとは保証されていません。2.1.312では、chrisvaillancourtがmacOSで停止を報告しています。一方、dblumenauはLinuxで引き続きリクエストを観測しています。両者の報告では、設定と実行モードが異なります。この違いだけで、OSが結果の原因だとは判断できません。

制御用の変数は、新しいセッションを起動する前に設定してください。~/.claude/settings.jsonとプロジェクト設定に、競合するenv項目がないか確認します。これらの設定は、シェルから引き継いだ値を置き換える場合があります。

Flamerixのpreloadは、コミュニティによる実験的な回避策として扱ってください。採用を検討する前に、特定のリビジョンに固定したコードを確認します。クライアントを更新するたびに、リクエスト数を確かめてください。ラベルが動かなくなっただけでは、リクエストが止まった証拠にはなりません。

範囲を限定した調査では、読み取り専用サブエージェントのモデル確認ガイドを参照してください。大きなツール定義でコンテキストが膨らむ場合は、MCPコンテキストの費用比較を使い、利用可能なツールを減らす効果を調べます。

変更する条件は一度に1つにしてください。同じタスク、モデル、コンテキスト条件で、要求を満たす結果が得られたか、経過時間、送信元別の費用を比較します。役立っていた並列処理が使えなくなる不利益が、測定した節約額を上回る場合は、バックグラウンド処理を戻してください。

LLM ワークフローを最適化しませんか?

単一 API でモデルを接続し、キーと AI コストを管理できます。

無料で始める