SDK の本番運用
Agent SDK を本番で動かすための費用とトークンの追跡、OpenTelemetry での計測、ホスティングの構成、安全なデプロイ(分離・認証情報・ネットワーク)をまとめます。
Agent SDK を本番で動かすときに決めることを、4つの面でまとめたページです。費用の追跡、計測(OpenTelemetry)、ホスティング、安全なデプロイを扱います。
- 費用とトークンは結果メッセージで読めます。ただし
total_cost_usdはクライアント側の見積もりです - 計測は、SDK が起こす CLI が OpenTelemetry で直接エクスポートします。環境変数で設定します
- SDK は
claudeの CLI をサブプロセスとして起こすので、状態はローカルのディスクにあります。ホスティングはステートレスな API の置き場とは違います - 安全なデプロイは、分離・認証情報のプロキシ・ネットワーク制御・ファイルシステムの設定を重ねます
- セッションの保存は SDK のセッションと入出力、オプションの基本は Agent SDK の基本、利用状況の計測(CLI 側)は 利用状況の計測 にあります
費用とトークンの追跡#
注意
total_cost_usd と costUSD は、クライアント側の見積もりで、請求の正式なデータではありません。SDK は、ビルド時に同梱した価格表(modelPricing の表が有効ならそちら)からローカルで計算します。価格の改定・SDK が知らないモデル・クライアントが再現できない請求の規則があると、実際の請求とずれます。SDK が再現する請求の規則の1つはデータレジデンシーの価格で、応答の usage が inference_geo: "us" を報告すると、その応答のトークンの定価を1.1倍にします(リクエストごとの料金、たとえば Web 検索は倍にしません。TypeScript Agent SDK v0.3.239 以降、Python Agent SDK v0.2.144 以降)。開発での目安と概算の予算に使い、正式な請求は Usage and Cost API か Claude Console の Usage ページで確かめます。これらのフィールドから利用者へ請求したり、金銭の判断を自動で下したりしません。
用語と読み方#
両 SDK は同じ使用量のデータを出しますが、フィールド名が違います。
| 項目 | TypeScript | Python |
|---|---|---|
| ステップごとのトークン | アシスタントメッセージの message.message.id と message.message.usage |
message.usage と message.message_id |
| モデルごとの費用 | 結果メッセージの modelUsage |
結果メッセージの model_usage |
| 累計 | 結果メッセージの total_cost_usd |
同じ(省略可能な型なので None を確かめる) |
query()の呼び出し:SDK のquery()を1回呼ぶこと。1回の呼び出しに複数のステップが入る。呼び出しごとに最後にresultメッセージが1つ出る(ストリーミング入力では、1回の呼び出しが複数のユーザーターンを運び、ターンごとにresultが出る)- ステップ:
query()の中の1回のリクエストと応答の組。アシスタントメッセージがトークン使用量を持つ - セッション:
resumeで同じセッション ID につながったquery()の連なり。再開した呼び出しの結果は、その呼び出し分だけでなく、セッション全体の支出を報告する
1ターンで Claude が複数のツールを使うと、そのターンのメッセージは同じ ID を共有します。二重に数えないよう、ID で重複を除きます。
呼び出しの合計を読む#
結果メッセージ(TypeScript の SDKResultMessage、Python の ResultMessage)が query() の終わりを示し、total_cost_usd が、その呼び出しのすべてのステップの見積もりの累計です。セッションを再開した呼び出しは、前の支出も含みます。成功とエラーのどちらの結果も持ちますが、セッションのクラッシュの最後の結果ではゼロになっていることがあります。
サブエージェントを起こすと、3つの結果のフィールドは数える範囲が違います。サブエージェントを含む全体のトークンは modelUsage(Python は model_usage)で数えます。usage は入れ子が生じるとすぐ少なくなります。
| フィールド | サブエージェントの活動 |
|---|---|
usage |
含まない。最上位のエージェントのループだけを数える |
total_cost_usd |
含む。最上位のループと並べて、サブエージェントのリクエストも数える |
modelUsage / model_usage |
含む。モデル別に分けて、サブエージェントのリクエストも数える |
- 単発のメッセージ入力で、最後のターンの終わりにバックグラウンドのサブエージェントが動いていると、Claude Code は、結果を出す前にそれを待ちます(待ちの上限はヘッドレス実行の「終了時のバックグラウンドタスク」)。結果の
total_cost_usd・duration_api_ms・modelUsageは、その待ちの間の作業も含みます - サブエージェントが
total_cost_usdに足す量を抑えるには、クエリに深さ・同時実行数・費用の上限を設定します(SDK のツール・権限・拡張)
ストリーミング入力での追跡#
ストリーミング入力では、1回の query() が複数のユーザーターンを運び、ターンごとに結果が出ます。フィールドの範囲が違います。
| フィールド | 範囲 |
|---|---|
usage |
そのターンだけ。さらにその中のメインのエージェントのループだけで、サブエージェントは含まない |
total_cost_usd と modelUsage(Python は model_usage) |
呼び出しのここまでの累計。呼び出しがセッションを再開したときに復元された支出も含む |
- アプリが
/clear・/reset・/newを送らない呼び出しでは、結果を足し合わせず、最新の結果を読んで呼び出しの合計とします - 累計は、アプリがこの3つのコマンドのどれかを送るたびに数え直されます(
query()の中では、ほかに累計を数え直すものはありません)。その3つの結果が勘定に効きます。/clearのターン自身の結果は、リセット以降に動いた分だけで、新しいsession_idを持つ。以降のすべての結果は、そのリセットから数え続ける。各/clearの直前の最後の結果は、前のリセット以降のターンの合計を持つ - 呼び出し全体を合計するには、各
/clearの前の最後の結果を、呼び出しの最後の結果に足します。ほかの結果(/clearのターン自身の結果を含む)は、あとの結果に置き換えられます - TypeScript の SDK は、リセットごとに
SDKConversationResetMessageを出すので、ストリームからリセットを検出できます。Python もConversationResetMessageを出します(Python SDK v0.2.137 より前は、Python のイテレーターがこのメッセージを落としたので、その版では、アプリが送った/clearのターンからリセットを自分で数える) maxBudgetUsd(Python はmax_budget_usd)は、呼び出し自身の支出だけを数えます。再開したセッションから復元した累計は上限に数えられず、/clearで上限が数え直されます
ステップごと・モデルごとの使用量#
- ステップごと:各アシスタントメッセージは
idとusageを持ちます(TypeScript ではmessage.messageの中)。ツールが並列に呼ばれると、複数のメッセージが同じidと同じ使用量を共有するので、数えた ID を控え、重複を飛ばします。重複を除いたステップごとの値は、入力とキャッシュのトークンについては正確です。ステップごとのoutput_tokensは仮の値なので、出力トークンは結果メッセージから読みます - モデルごと:結果メッセージの
modelUsageは、モデル名からモデルごとのトークン数と費用への対応表です。複数のモデル(サブエージェントに Haiku、メインに Opus など)を使うときに、どこでトークンを使ったか見るのに便利です。各項目のcostBasisは、そのモデルの最新のリクエストを、どの価格表で値付けしたかを示します。listは定価、managedはmodelPricingの表、unknownはどちらもそのモデル ID に当たらなかったときです(Claude Code v2.1.246 以降)
複数の呼び出しをまたぐ合計#
| 状況 | 合計の取り方 |
|---|---|
独立した呼び出し(resume も continue もなし) |
結果は各呼び出しの分だけなので、自分で合計を足す |
| 同じセッションを再開する呼び出し | Claude Code は、プロセスが通常どおり終わるときにセッションの累計をトランスクリプトに保存し、あとの呼び出しが再開か分岐したときに復元する。各結果にはセッションの前の支出がすでに含まれるので、最新の結果を読む。結果を足すと、復元した分を二重に数える |
v2.1.277 より前は、SDK や claude -p で再開したセッションの累計はゼロから始まり、各呼び出しの結果はその呼び出しの分だけでした。
エラー・キャッシュ・出力トークン#
出力トークンは結果メッセージから読む。 Claude Code は、応答が始まったときに API が報告した使用量からアシスタントメッセージを作るので、メッセージの output_tokens は、message_start の時点(応答を生成する前)に API が報告していた数だけです。1回の API 応答が複数のアシスタントメッセージを作ることがあり、どれも同じ仮の値を持ちます。実際の出力の数は応答の終わりに報告され、Claude Code が結果メッセージに足します。結果の usage か、モデル別なら modelUsage から読みます。ストリーミング中に出力の数が増えるのを見るには、includePartialMessages(Python は include_partial_messages)を設定して、各 message_delta のストリームイベントの usage を読みます。
失敗した会話。 成功もエラーも、結果メッセージは usage と total_cost_usd を持ちます(Python ではどちらも省略可能な型なので None を確かめる)。会話が途中で失敗しても、失敗までのトークンは使われています。subtype が success でもエラーでも、すべての結果メッセージから費用のデータを読みます。次のエラーの結果は、usage が実際の支出より少なく報告されます。
- セッションのクラッシュのあとの
error_during_execution:費用のフィールドがすべてゼロのことがある error_max_budget_usd:usageは、予算を超えた応答を除くが、total_cost_usdとmodelUsageは含む
選べるなら、usage でなく total_cost_usd か modelUsage で集計します。
セッションのクラッシュ後の合計。 Claude Code のプロセスがクラッシュすると、最後に error_during_execution の結果を出して終了します(単発もストリーミング入力も同じ)。その結果は usage・total_cost_usd・modelUsage がゼロのことがあるので、それより前に届いたものから合計を復元します。
- クラッシュの1つ前のターンの結果を使う(ストリーミング入力なら、ここまでの累計を持つ)。次のときは、手順2に進む:単発の呼び出しで前の結果がない、最初のターンでクラッシュした、1つ前のターンが
/clear自体でその結果がリセットの分だけ - アシスタントメッセージの
usageを、API 応答1回につき1回だけ数えて足す(単発なら全部、ストリーミング入力なら最後の結果のあとに届いたもの)。メインのループの入力とキャッシュのトークンが分かる。サブエージェントの使用量は、この方法では復元できず、出力トークンと USD の費用もできない(ステップごとのoutput_tokensが仮の値のため)
キャッシュのトークン#
Agent SDK は、繰り返しの内容の費用を抑えるため、プロンプトキャッシュを自動で使います。設定は要りません。使用量のオブジェクトにはキャッシュ用の2つのフィールドがあります。
| フィールド | 内容 |
|---|---|
cache_creation_input_tokens |
新しいキャッシュの項目を作るためのトークン(通常の入力トークンより高い料率) |
cache_read_input_tokens |
既存のキャッシュの項目から読んだトークン(割り引かれた料率) |
input_tokens とは別に追うと、キャッシュで節約できた量が分かります。TypeScript では Usage のオブジェクトに型があり、Python では ResultMessage.usage の辞書のキーです(例:message.usage.get("cache_read_input_tokens", 0))。
プロンプトキャッシュの TTL を1時間にする#
自分のターンは、メインの会話の TTL の枠に入り、Claude Code がそれと一緒に動かすヘルパーも同じ枠です。サブエージェントのように、その会話の外で Claude Code が出すリクエストは、別の TTL の制御を持ちます。API キーで認証するときや、Amazon Bedrock・Google Cloud の Agent Platform・Microsoft Foundry・Claude Platform on AWS で動かすとき、自分のターンのキャッシュ項目の TTL は既定で5分です。同じシステムプロンプトと文脈に対して、5分より長い間隔を挟む短いセッションを大量に動かすと、セッションの間にキャッシュが切れ、新しいセッションごとに入力の全額を払います。
キャッシュ書き込みの TTL を1時間にするには、環境変数 ENABLE_PROMPT_CACHING_1H を設定します(シェルやコンテナの環境で export するか、options.env で渡す)。
for await (const message of query({
prompt: "...",
options: {
env: {
...process.env,
CLAUDE_CODE_USE_BEDROCK: "1",
ENABLE_PROMPT_CACHING_1H: "1",
},
},
})) { /* ... */ }
CLAUDE_CODE_USE_BEDROCKを設定するので、Amazon Bedrock の AWS の認証情報が要ります(なければクエリは失敗します)- 1時間 TTL のキャッシュ書き込みは、5分の書き込みより高い料率で課金されます。書き込みの費用が増える代わりに、キャッシュ読み取りが増えます
- Claude のサブスクリプションで、プランに含まれる利用量の範囲内なら、この変数なしで、自分のターンと、その横で Claude Code が出すヘルパーのリクエストの一部で、1時間の TTL が得られます。追加の利用クレジットを使い始めると、それらのターンは5分の TTL に落ちます
ENABLE_PROMPT_CACHING_1H は、両方の枠のすべてのリクエストに1時間の TTL を求めます。枠ごとに TTL を選ぶには、次の設定を使います(どれも 5m か 1h を取り、ENABLE_PROMPT_CACHING_1H より優先されます)。
| 対象 | 環境変数 | 設定キー |
|---|---|---|
| メインの会話 | CLAUDE_CODE_PROMPT_CACHE_TTL |
promptCacheTtl |
| そのほかすべて | CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL |
subagentPromptCacheTtl |
promptCacheTtl を 1h にすると、利用クレジットを使っている間も、メインの会話の1時間キャッシュが保たれます。コストの考え方全般は コストを抑える、キャッシュの仕組みは コンテキストとプロンプトキャッシュ、変数は 環境変数一覧 を見てください。
計測(OpenTelemetry)#
Agent SDK は、トレース・メトリクス・ログイベントを OpenTelemetry で、OTLP を受け付ける任意のバックエンド(ホスト型の監視基盤でも自前のコレクターでも)へエクスポートできます。本番で、どのツールを呼んだか・各モデルリクエストにかかった時間・使ったトークン数・失敗した場所を見るためのものです。SDK 自身は計測を出しません。SDK は、CLI を子プロセスとして動かしてローカルのパイプで通信し、CLI が持つ OpenTelemetry の計装が、モデルリクエストとツール実行をスパンで囲み、トークンと費用のカウンターをメトリクスで出し、プロンプトとツール結果の構造化ログイベントを出します。SDK は設定を CLI のプロセスへ渡し、CLI がコレクターへ直接エクスポートします。
設定は環境変数で渡します。子プロセスは既定でアプリの環境を継承するので、設定する場所は2つあります。
| 場所 | 内容 |
|---|---|
| プロセスの環境 | アプリの起動前に、シェル・コンテナ・オーケストレーターで変数を設定する。どの query() も自動で拾う。本番のデプロイにはこれを推奨 |
| 呼び出しごとのオプション | ClaudeAgentOptions.env(Python)か options.env(TypeScript)に設定する。同じプロセスのエージェントごとに計測の設定を変えたいときに使う。Python は継承した環境の上に重ね、TypeScript は継承した環境を置き換えるので ...process.env を含める |
CLI は、3つの独立した OpenTelemetry のシグナルを出します。それぞれ有効化のスイッチとエクスポーターが別で、必要なものだけ有効にできます。
| シグナル | 中身 | 有効にする方法 |
|---|---|---|
| メトリクス | トークン・費用・セッション・コードの行数・ツールの判断のカウンター | OTEL_METRICS_EXPORTER |
| ログイベント | プロンプト・API リクエスト・API エラー・ツール結果ごとの構造化レコード | OTEL_LOGS_EXPORTER |
| トレース(ベータ) | 対話・モデルリクエスト・ツール呼び出し・フックごとのスパン | OTEL_TRACES_EXPORTER と CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 |
メトリクス名・イベント名・属性の全一覧は、Claude Code の Monitoring のリファレンスにあります(利用状況の計測)。
有効にする#
計測は、CLAUDE_CODE_ENABLE_TELEMETRY=1 を設定して、エクスポーターを少なくとも1つ選ぶまで無効です。最も一般的な設定は、3つのシグナルを OTLP の HTTP でコレクターへ送るものです。
OTEL_ENV = {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
# トレース(ベータ)に必要。メトリクスとログイベントには要らない
"CLAUDE_CODE_ENHANCED_TELEMETRY_BETA": "1",
"OTEL_TRACES_EXPORTER": "otlp",
"OTEL_METRICS_EXPORTER": "otlp",
"OTEL_LOGS_EXPORTER": "otlp",
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://collector.example.com:4318",
"OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer your-token",
}
options = ClaudeAgentOptions(env=OTEL_ENV)
const otelEnv = {
CLAUDE_CODE_ENABLE_TELEMETRY: "1",
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
OTEL_TRACES_EXPORTER: "otlp",
OTEL_METRICS_EXPORTER: "otlp",
OTEL_LOGS_EXPORTER: "otlp",
OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf",
OTEL_EXPORTER_OTLP_ENDPOINT: "http://collector.example.com:4318",
OTEL_EXPORTER_OTLP_HEADERS: "Authorization=Bearer your-token",
};
for await (const message of query({
prompt: "このディレクトリのファイルを一覧にして",
// TypeScript の env は継承した環境を置き換えるので、先に process.env を展開する
options: { env: { ...process.env, ...otelEnv } },
})) { /* ... */ }
- 子プロセスは既定でアプリの環境を継承するので、同じ変数を Dockerfile・Kubernetes のマニフェスト・シェルのプロファイルで export し、
options.envを省くこともできます - エクスポートが動いているかは、タスクの完了後に、コレクターのログで届いたスパン・メトリクス・ログイベントを見て確かめます
- CLI は、エクスポートのエラーを既定で黙って捨てます。エンドポイントに届かない、またはデータを拒否されても、エージェントは普通に動き、CLI は計測を捨てて、アプリにはエラーを出しません。エクスポーターのエラーを出すには、エクスポーターの変数と並べて
CLAUDE_CODE_OTEL_DIAG_STDERR=1を設定し、診断を SDK のstderrコールバック(Python)かstderrオプション(TypeScript)で読みます(Claude Code v2.1.179 以降)
注意
console エクスポーターは、計測を標準出力に書きます。標準出力は SDK がメッセージの通り道に使うので、SDK 経由では、エクスポーターの値に console を設定しないでください。ローカルで計測を確かめるには、OTEL_EXPORTER_OTLP_ENDPOINT をローカルの OpenTelemetry Collector へ向けます。
短い呼び出しのフラッシュ#
CLI は計測をまとめて、一定の間隔でエクスポートします。通常のプロセス終了では、残ったデータをフラッシュしようとしますが、短いタイムアウトがあるので、コレクターの応答が遅いとスパンを失うことがあります。CLI が終了処理に入る前にプロセスが殺されると、バッチのバッファに残ったものは失われます。エクスポートの間隔を短くすると、この両方の窓が狭まります。既定では、メトリクスは60秒ごと、トレースとログは5秒ごとにエクスポートされます。3つの間隔(ミリ秒)を縮めると、短い作業の実行中にデータがコレクターへ届きます。
OTEL_METRIC_EXPORT_INTERVAL=1000
OTEL_LOGS_EXPORT_INTERVAL=1000
OTEL_TRACES_EXPORT_INTERVAL=1000
コンテナやオーケストレーターの設定へ置く場合は、次の組み合わせが基本です。
CLAUDE_CODE_ENABLE_TELEMETRY=1
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=http://collector.example.com:4318
トレースを読む#
トレースは、エージェントの実行を最も詳しく見られます。CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 を設定すると、エージェントのループの各ステップが、トレースの基盤で調べられるスパンになります。
| スパン | 内容 |
|---|---|
claude_code.interaction |
エージェントのループの1ターン(プロンプトを受けてから応答を出すまで)を囲む |
claude_code.llm_request |
Claude API の各呼び出しを囲む。モデル名・待ち時間・トークン数が属性に入る |
claude_code.tool |
各ツール呼び出しを囲む。権限の待ち(claude_code.tool.blocked_on_user)と実行そのもの(claude_code.tool.execution)が子スパンになる |
claude_code.hook |
各フックの実行を囲む。詳細なベータのトレース(ENABLE_BETA_TRACING_DETAILED=1 と BETA_TRACING_ENDPOINT)が必要で、この組はログとトレースの送り先も変える |
llm_request・tool・hookのスパンは、それを囲むclaude_code.interactionの子です。エージェントが Agent ツールでサブエージェントを起こすと、サブエージェントのllm_requestとtoolのスパンは、親のエージェントのclaude_code.toolの下に入れ子になり、委任の連なり全体が1つのトレースに現れます- スパンは既定で
session.idの属性を持ちます。同じセッションに対してquery()を何度か呼ぶとき、バックエンドでsession.idで絞ると、1つのタイムラインに見えます。OTEL_METRICS_INCLUDE_SESSION_IDを偽の値にすると、この属性は付きません
補足
トレースはベータです。スパン名と属性は、リリースの間で変わることがあります。
自分のアプリのトレースにつなぐ#
SDK は、W3C のトレースコンテキストを CLI のサブプロセスへ自動で伝えます。アプリで OpenTelemetry のスパンが有効なあいだに query() を呼ぶと、SDK は子プロセスの環境に TRACEPARENT と TRACESTATE を入れ、CLI が読んで、claude_code.interaction のスパンが自分のスパンの子になります。エージェントの実行が、切り離されたルートでなく、アプリのトレースの中に現れます。
- 実行中に出る OTLP のイベントのログレコードも同じトレースコンテキストを持ち、
TRACEPARENTが設定されていれば、各レコードのtrace_idとspan_idがアプリのトレースに一致するので、バックエンドでイベントをスパンに結び付けられます(v2.1.212 より前は、有効なスパンの外で出たイベントのレコードに、trace_idとspan_idが付かなかった) - トレースコンテキストの伝播が有効なとき、CLI は動かすすべての Bash と PowerShell のコマンドにも
TRACEPARENTを渡します。Bashツールで起こしたコマンドが自分の OpenTelemetry のスパンを出すと、そのスパンは、コマンドを囲むclaude_code.tool.executionの下に入れ子になります options.envにTRACEPARENTを明示すると、自動の注入は飛ばされるので、特定の親の文脈を固定できます。対話の CLI のセッションは、受け取ったTRACEPARENTを完全に無視します。守るのは Agent SDK とclaude -pの実行だけです
計測にタグを付ける#
CLI は、既定で service.name を claude-code と報告します。複数のエージェントを動かす、または同じコレクターへエクスポートする別のサービスと並べて SDK を動かすなら、サービス名を上書きし、リソース属性を足して、バックエンドでエージェント別に絞れるようにします。これらの値は、エージェントが出すすべてのスパン・メトリクス・イベントに、OpenTelemetry のリソース属性として付きます。
options: {
env: {
...process.env,
OTEL_SERVICE_NAME: "support-triage-agent",
OTEL_RESOURCE_ATTRIBUTES: "service.version=1.4.0,deployment.environment=production",
},
}
エンドユーザーに操作を結び付ける#
CLI は、Anthropic を呼ぶのに使う認証情報から、すべてのイベントに識別の属性を付けます。1つのデプロイで多くのエンドユーザーに応える場合、その属性が示すのはサービスの認証情報で、エージェントが代わりに動いたエンドユーザーではありません。ツール呼び出しと MCP の活動をエンドユーザーに結び付けられるようにするには、query() を呼ぶたびに、エンドユーザーの識別をリソース属性として入れます。OTEL_RESOURCE_ATTRIBUTES はカンマ・空白・イコールを予約しているので、値は補間する前にパーセントエンコードします。
const attrs = [
`enduser.id=${encodeURIComponent(request.userId)}`,
`tenant.id=${encodeURIComponent(request.tenantId)}`,
].join(",");
options: { env: { ...process.env, OTEL_RESOURCE_ATTRIBUTES: attrs } }
エンドユーザーの識別を付けると、tool_decision・tool_result・mcp_server_connection・permission_mode_changed のイベント(claude_code. の接頭辞のログレコードとして出る)が、ユーザーごとの監査証跡になり、SIEM へ転送できます。セキュリティに関わるイベントの全一覧と属性は、Monitoring のリファレンスの「Audit security events」にあります。
機微なデータの扱い#
計測は、既定で構造だけです。所要時間・モデル名・ツール名は、すべてのスパンに記録されます。トークン数は、元の API リクエストが使用量を返したときに記録されるので、失敗や中断したリクエストのスパンには付かないことがあります。エージェントが読み書きする内容は、既定では記録されません。次の、オプトインの変数が、エクスポートするデータに内容を足します。
| 変数 | 足されるもの |
|---|---|
OTEL_LOG_USER_PROMPTS=1 |
claude_code.user_prompt イベントと claude_code.interaction のスパンに、プロンプトのテキスト |
OTEL_LOG_TOOL_DETAILS=1 |
claude_code.tool_result イベントに、ファイルパス・シェルコマンド・検索パターンなどのツールの入力引数。費用とトークンのメトリクスに、実際のエージェント・スキル・プラグイン・MCP サーバーの名前 |
OTEL_LOG_TOOL_CONTENT=1 |
claude_code.tool に、ファイルの内容・Bash の出力・MCP ツールや WebFetch や WebSearch が返したものを持つ tool.output のスパンイベント。既定で 60 KB で切り詰められ、CLAUDE_CODE_OTEL_CONTENT_MAX_LENGTH で変えられる(Claude Code v2.1.214 以降)。MCP ツール・WebFetch・WebSearch の結果は v2.1.283 以降が必要。トレースが有効なことが前提。スパンの属性は、ツールの内容を別の条件で持つ |
OTEL_LOG_RAW_API_BODIES |
Anthropic の Messages API のリクエストとレスポンスの JSON 全体を、claude_code.api_request_body と claude_code.api_response_body のログイベントとして。1 なら、既定で 60 KB で切り詰めた本文をインラインで、file:<dir> なら切り詰めない本文をディスクへ置き、イベントに body_ref のパスを付ける。本文には会話履歴の全体が入り、拡張思考の内容は伏せられる。これを有効にすると、上の3つが明かすものすべてに同意したことになる |
注意
観測の基盤が、エージェントが扱うデータの保存を承認されていない限り、これらは設定しません。属性と伏せ字の挙動の全体は、Monitoring のリファレンスの「Security and privacy」にあります。
ホスティング#
Agent SDK は、claude の CLI のサブプロセスを起こして監督します。そのサブプロセスが、シェル・作業ディレクトリ・ディスク上のセッションファイルを持ちます。ステートレスな API の包みを置くのとは違い、動いているエージェントは、どれもローカルの状態に結び付いた長生きのプロセスです。このことが、リソースの割り当て・セッションの保存・テナントをまたぐ拡張の仕方を決めます。このページは、自前のインフラでのセルフホストを扱います。デプロイ用の Dockerfile と Kubernetes のマニフェストは、hosting cookbook にあります。エージェントのループ自体を自前のインフラで動かす必要がないなら、Anthropic がループをホストする Managed Agents も選べます。
サブプロセスのモデル#
query() を呼ぶと、SDK は別の claude の CLI プロセスを起こし、標準入出力で通信します。1つのエージェントのセッションは1つのサブプロセスに対応します。N 個の同時のセッションは、それぞれプロセスツリーとトランスクリプトのファイルを持つ N 個のサブプロセスです。既定では、すべてがアプリの作業ディレクトリを継承します。セッションごとにファイルシステムを分けたいときは、各セッションの query() のオプションに別々の cwd を渡します。
for await (const message of query({
prompt: "...",
options: { cwd: `/workspaces/${sessionId}` },
})) { /* ... */ }
補足
このページの TypeScript の例はトップレベルの await を使うので、.mts で保存するか、package.json に "type": "module" を設定します。
エージェントの状態のうち3種類は、既定でコンテナのファイルシステムにあり、コンテナの再起動・スケールダウン・別のノードへの移動では残りません。
| 状態 | 既定の場所 |
|---|---|
| セッションのトランスクリプト | ~/.claude/projects/(CLAUDE_CONFIG_DIR を設定していれば、その下の projects/) |
CLAUDE.md のメモリファイル |
ユーザー層は ~/.claude/CLAUDE.md、プロジェクト層はセッションの作業ディレクトリ |
| 作業ディレクトリの成果物 | セッションの作業ディレクトリ |
ホストをまたいでトランスクリプトを残すには、SessionStore のアダプターを設定します(SDK のセッションと入出力)。メモリファイルと作業ディレクトリのそのほかの成果物には、マウントしたボリュームやオブジェクトストアの同期など、別の保存の方法が要ります。
セッションのパターンを選ぶ#
次の4つのパターンは、コンテナの寿命を、それが担当するセッションに対してどう置くかを決めます。コンテナをどこで動かすか(ローカルの Docker・Modal・Kubernetes)は、cookbook にデプロイ用のコードがあります。
| パターン | 内容 | 向く仕事 |
|---|---|---|
| 使い捨て(ephemeral) | ユーザーの作業ごとにコンテナを作り、完了したら破棄する。作業中にユーザーが AI とやり取りしてもよい | 一度きりの作業。バグの調査と修正・請求書や領収書の抽出・文書の翻訳・メディアの変換 |
| 長く動かす(long-running) | 常駐するコンテナのインスタンスを動かし、1つのコンテナで複数の SDK プロセスを動かすことも多い | 自律して動くエージェント・コンテンツを配るもの・大量のメッセージの流れ。受信メールを仕分けて返すメールのエージェント、ユーザーごとの編集できるサイトを動かすサイトビルダー、Slack のような基盤から続くトラフィックを扱うチャットボット |
| ハイブリッド(hybrid) | 起動時に SessionStore から文脈を戻し、更新を書き戻す使い捨てのコンテナ。アイドル中はコンテナが止まり、ユーザーが戻ると再び立ち上がる |
多くのやり取りにまたがるが、間が空くセッション。たまにしか確認しない個人のプロジェクト管理・数時間にわたり一時停止と再開をする深い調査・やり取りをまたいでチケットの履歴を読み込むカスタマーサポート |
| マルチエージェントのコンテナ | 1つのコンテナの中で複数の SDK サブプロセスを動かす | 互いに密に協調するエージェント(共有の環境でやり取りするマルチエージェントのシミュレーションなど) |
- 使い捨て:コンテナは、環境変数
TASK_PROMPTから作業を読み、SDK を呼んで終了する、1回きりのエントリーポイントを動かします。作業がターンの上限(例:20)に達すると、結果のsubtypeがerror_max_turnsになり、query()は結果を出したあとでエラーを送出するので、コンテナをきれいに終わらせるならループをtryで囲みます - 長く動かす:コンテナが HTTP か WebSocket のエンドポイントを公開し、有効なセッションごとに、長生きのクエリとその裏のサブプロセスを対応づけます。TypeScript は、動いているセッションにターンを足す
streamInput()、着信の前にサブプロセスを温めるstartup()(セッションの作業ディレクトリが最初のリクエストまで分からないならprewarm())。Python は、セッションをターンをまたいで開いたままにするClaudeSDKClientを使います。コンテナは、同時のセッションの最大数をメモリに載せられるよう、大きさを決めます - ハイブリッド:コンテナを止めるタイミング(プロバイダーのアイドルのタイムアウト)は、ユーザーが戻ってくる頻度に合わせます。
SessionStoreなしでコンテナを止めると、トランスクリプトも失われるので、このパターンではストアは任意でなく必須です。要は、共有のストアをつけて、セッションを ID で再開することです - マルチエージェントのコンテナ:エージェントごとに自分の作業ディレクトリを与えて、互いのファイルを上書きしないようにし、設定の読み込みを分離して、エージェントごとの
CLAUDE.mdが漏れないようにします
コンテナを用意する#
コンテナのサンドボックス。 SDK を、プロセスの分離・リソースの上限・ネットワークの制御・使い捨てのファイルシステムを持つ、サンドボックスのコンテナの中で動かします。プロバイダーを選ぶときに決めることは次のとおりです。
- 誰がサンドボックスを動かすか:サービスとして提供するプロバイダーがインフラを運用するか、自前で動かすソフトウェアか
- コールドスタートの待ち時間:サンドボックスを作ってから、最初のリクエストを受けられるまで。使い捨てのパターンは1秒未満の起動が要り、長く動かすパターンは、それより許容できる
- 永続ストレージ:耐久性のあるボリュームがあるか、使い捨てのディスクだけか。ハイブリッドは、サンドボックスの中か隣のどこかに耐久ストレージが要る
- 価格のモデル:秒単位・リクエスト単位・時間単位の定額。秒単位は、突発の使い捨ての仕事に合い、時間単位は長く動かすセッションに合う
- ネットワーク:独自のエグレスの規則・外向きのプロキシ・規制のある環境向けのプライベート VPC のピアリングへの対応
Docker・gVisor・Firecracker などの自前の選択肢と、分離の設定の詳細は、このページの「安全なデプロイ」にあります。
実行環境の依存。 コンテナには、SDK の言語のランタイムが要ります。
- Python SDK は Python 3.10 以降、TypeScript SDK は Node.js 18 以降
- どちらの SDK も、ほとんどの導入で Claude Code のネイティブバイナリを同梱し、起こされる CLI に別の Node.js は要りません。別にネイティブの Claude Code が要る導入は、Agent SDK の基本 の導入の節にあります
- 同梱のバイナリは SDK のパッケージの版に固定されるので、SDK を更新することが CLI を更新することです。SDK は semver に従うので、パッチは継続して取り込み、マイナーを取り込む前に、TypeScript か Python の変更履歴を確かめます
リソース。 起動したばかりのインスタンスの出発点として、エージェント1つにつき、RAM 1 GiB・ディスク 5 GiB・CPU 1 が妥当です。メモリの使用量は、セッションの長さとツールの活動に応じて増えるので、アイドル時の基準でなく、実際に要るセッションの長さと同時実行数で大きさを決めます。
ネットワーク。 SDK には、api.anthropic.com(Amazon Bedrock や Google Cloud の Agent Platform で動かすときはプロバイダーのリージョンのエンドポイント)への外向きの HTTPS が要ります。エージェントが MCP サーバーや外部ツールを使うなら、それらのエンドポイントへの外向きの通信も要ります。本番では、ドメインの許可リストの強制・認証情報の注入・リクエストの記録を行う、エグレスのプロキシを通して外向きの通信を流します。内向きでは、コンテナの HTTP か WebSocket のポートを公開します。クライアントのリクエストはアプリがそのポートで受けて SDK を呼ぶので、サブプロセス自身はネットワークで待ち受けません。
本番での検討事項#
自前でホストするエージェントを出す前に、次を決めます。
セッションと状態の保存。 ローカルのディスクは、再起動・スケールダウン・別のノードへの移動で失われます。ユーザーが再開を期待するセッションは、SessionStore のアダプターで、トランスクリプトを耐久ストレージへ写します。
- トランスクリプトだけ:
SessionStoreが写すのはトランスクリプトで、CLAUDE.mdのメモリファイルや作業ディレクトリのほかの成果物ではありません。共有のボリュームをマウントするか、別に同期します - 置き換えでなく写し:サブプロセスが先にローカルのディスクへ書き、SDK がバッチの写しをストアへ送ります。新しいセッションのローカルのトランスクリプトは、実行のあとも残り、ストアから再開した実行は、終了時にローカルの写しを消すので、ストアが唯一の耐久的な写しです
mirror_errorのメッセージ:SDK がバッチをストアへ届けられないと、そのバッチを捨て、{ type: "system", subtype: "mirror_error" }のメッセージを出して、クエリを続けます。ストアの耐久性が大事なら、これに警告を設定します
計測。 Agent SDK のエージェントは、多くの API の往復にまたがってツール呼び出しを起こす長生きのプロセスです。計測がないと、どのツールが動き、どれだけかかり、セッションがどこで止まったか分かりません。SDK は OpenTelemetry の設定を環境から継承するので、OTEL の環境変数をコンテナかオーケストレーターで設定すれば、どの query() もコレクターへ、スパン・メトリクス・ログイベントをエクスポートします。CLAUDE_CODE_ENHANCED_TELEMETRY_BETA はトレースにだけ要ります。メトリクスとログだけなら省きます。プロンプトのテキストとツールの入力は、既定ではエクスポートに入りません(前の節「計測」)。
認証と秘密。 ホスティングで気をつける認証は3つです。
- Anthropic API:サブプロセスは環境の
ANTHROPIC_API_KEYを読みます。秘密の管理から渡すか、ANTHROPIC_BASE_URLを、コンテナの外でキーを注入するプロキシへ向けます(このページの「認証情報の管理」) - 内向き:エージェントのコンテナの前のゲートウェイで認証します。エージェントは認証済みのリクエストを受け取る側で、ユーザートークンを検証する部品ではありません
- 外向きのツール:ツールの認証情報を、エージェントの環境の外に置きます。外向きの呼び出しを、リクエストがコンテナを出たあとで API キーを注入するプロキシに通します。エージェントは呼び出しをし、プロキシが認証情報を足します
スケールと同時実行。 各セッションは自分のサブプロセスで動くので、ホストの同時実行数は、RAM が保持できるサブプロセスの数で決まります。ホストの大きさは次の式で見積もります。
agents per host = (host RAM - overhead) / (per-session RAM ceiling)
- セッションごとの上限は、代表的なセッションを目標の長さまで、想定するツールの負荷で動かし、ピークの RSS を記録して測ります。出発点の 1 GiB は下限で、上限ではありません
- 水平のスケールのルーティングは、パターンで決まります。長く動かすセッションでは、コンテナが多くのセッションを持つので、ロードバランサーの後ろにコンテナのプールを置き、
sessionIdの一貫ハッシュで、各セッションを1つのコンテナに固定します。固定されたセッションは、追い出されるかコンテナが再起動するまで、同じコンテナ、つまり同じ動いているサブプロセスに当たり続けます
費用。 Anthropic のトークン費用は、通常、コンテナのインフラ費用を桁で上回ります。最小限のコンテナは時間あたり約 $0.05 ですが、1回の長いエージェントのセッションは、トークンで数ドルを使うことがあります(セッションごとの集計は前の節「費用とトークンの追跡」)。
マルチテナントの分離。 SDK の既定の動きは、設定と CLAUDE.md のメモリファイルをファイルシステムから読みます。複数のテナントに応える共有のコンテナでは、それらのファイルが、あるテナントの文脈を別のテナントのセッションへ漏らしかねません。共有のコンテナの中でテナントを分離するには、次のことをします。
settingSources: [](Python はsetting_sources=[])を渡して、user・project・local の設定を読まないenvにCLAUDE_CODE_DISABLE_AUTO_MEMORY=1を設定する。~/.claude/projects/<project>/memory/の自動メモリは、settingSourcesにかかわらずシステムプロンプトへ読み込まれる。無条件に読み込まれるほかの入力は Agent SDK の基本 の「settingSourcesが制御しないもの」にあるCLAUDE_CONFIG_DIRをテナントごとのディレクトリに向け、テナントが~/.claude.jsonの全体の設定を共有しないようにする。設定ディレクトリごとに1つの作業ディレクトリだけに使い、SessionStoreを渡さないなら、envにCLAUDE_CODE_PROJECT_DIR_NAMEも設定して、その下のトランスクリプトのパスを短くできる(TypeScript Agent SDK v0.3.234 以降、Python Agent SDK v0.2.140 以降)- テナントごとの作業ディレクトリを使う。すべての
query()でcwdを明示する - テナントごとのエグレスの規則(別々の外向きの IP・認証情報・ドメインの許可リスト)をプロキシで適用する。侵害されたテナントが、別のテナントの外向きの方針で情報を持ち出せないようにするため
for await (const message of query({
prompt: "...",
options: {
cwd: tenantDir,
settingSources: [],
// TypeScript の env は継承した環境を置き換えるので process.env を展開する
env: {
...process.env,
CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1",
CLAUDE_CONFIG_DIR: configDir,
},
},
})) { /* ... */ }
tenantDir と configDir は、ほかのテナントが読めないパスをテナントごとに作ります。Python の env は継承した環境の上に重ねられます。
既知の制限#
| 制限 | 対処 |
|---|---|
| セッション全体のタイムアウトがない | セッションは自分では時間切れにならない。maxTurns(Python は max_turns)で、停止するまでのツール使用の往復の数を抑える |
| 長いセッションでメモリが増える | セッションの長さを制限するか、サブプロセスを定期的に作り直す |
| 並列のサブエージェントの大きな広がりがレート制限に当たる | 1回の広い送り出しでなく、仕事を小さなバッチに分ける |
| サブエージェントごとの実時間の期限がない | 各サブエージェントを AgentDefinition の maxTurns で抑える。CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS は、サブエージェントが出力しなくなったときに働く停滞の監視で、合計の実行時間の期限ではない |
デプロイの失敗を調べる#
手元で動くエージェントが、デプロイしたサービスで失敗するときの手がかりです。
- サービスの起動時に CLI が見つからない:Python は、コンテナやサービスマネージャーが、シェルと違う
PATHでアプリを動かすため、手元で動く導入がプロセスから見えない。TypeScript は、イメージのビルドで SDK の optional dependencies を飛ばしたか、pathToClaudeCodeExecutableがイメージに無いファイルを指している(Agent SDK の基本 の「CLI の起動」) - CLI がイメージにあるのに起動しない:コンテナのアーキテクチャや libc に合わないバイナリ、またはイメージのビルドで実行権限を失ったファイルから、Claude Code は起動できない
- Claude Code のプロセスが実行の途中で終了する:アプリが受け取るエラーは、SDK の言語と、CLI が先にエラーの結果を報告したかで決まる(Agent SDK の基本 の「CLI の終了」)
安全なデプロイ#
Claude Code と Agent SDK は、コードの実行・ファイルへのアクセス・外部サービスとのやり取りを、あなたに代わって行えます。決まった経路をたどる従来のソフトウェアと違い、文脈と目標から動作を動的に生み出します。これが役に立つ理由ですが、扱う内容(ファイル・Web ページ・ユーザーの入力)に動きが左右されうるということでもあります。これは、プロンプトインジェクションとも呼ばれます。たとえば、リポジトリの README に見慣れない指示があると、Claude Code が運用者の想定外の形でそれを動作に取り込むことがあります。すべてのデプロイに最大のセキュリティが要るわけではありません。ノートパソコンで Claude Code を動かす開発者と、マルチテナントで顧客のデータを処理する会社では、要件が違います。以降は、Claude Code の組み込みの機能から、強化した本番の構成までの選択肢です(Claude Code 本体の考え方は セキュリティとデータの扱い と サンドボックス)。
脅威モデル#
エージェントは、プロンプトインジェクション(扱う内容に埋め込まれた指示)やモデルの誤りで、意図しない動作をしうます。Claude のモデルはこれに抵抗するよう作られています。それでも、多層の防御は良い習慣です。たとえば、エージェントが、顧客のデータを外部のサーバーへ送れと指示する悪意あるファイルを処理しても、ネットワークの制御がそのリクエストを完全に止められます。
組み込みのセキュリティ機能#
- 権限の仕組み:すべてのツールと Bash のコマンドを、許可・ブロック・ユーザーへの確認から設定できる。グロブのパターンで「npm のコマンドをすべて許可」「sudo を含むコマンドをブロック」のような規則を作れる。組織は、全ユーザーに適用する方針を設定できる(権限ルール)
- 権限のためのコマンドの解析:Bash のコマンドを実行する前に、Claude Code は AST に解析し、結果を権限ルールと照合する。きれいに解析できないコマンドや、allow ルールに合わないコマンドは、明示の承認が要る。
evalのような少数の構成は、allow ルールにかかわらず常に承認が要る。これは権限の関門で、サンドボックスではない。rmとrmdirの重要なパスの検査や保護されたパスの一覧のような組み込みの安全確認を除き、対象のパスや影響からコマンドが危険かを推測することはしない - Web 検索の要約:検索結果は、生の内容を文脈へそのまま入れるのでなく要約されるので、悪意ある Web の内容からのプロンプトインジェクションの危険が下がる
- サンドボックスモード:Bash のコマンドを、ファイルシステムとネットワークのアクセスを制限したサンドボックスの環境で動かせる
セキュリティの原則#
Claude Code の既定を超える強化が要るデプロイでは、次の原則が選択肢の指針になります。
- セキュリティの境界:信頼の水準が違う部品を分ける境界。高セキュリティのデプロイでは、機微なリソース(認証情報など)を、エージェントを含む境界の外に置ける。たとえば、エージェントに API キーを直接渡さず、エージェントの環境の外でキーをリクエストに注入するプロキシを動かす。エージェントは API を呼べるが、認証情報自体は見ない。マルチテナントや信頼できない内容を処理するときに役立つ
- 最小権限:必要なら、エージェントを、その仕事に要る機能だけに制限する
- 多層防御:高セキュリティの環境では、複数の制御を重ねる。コンテナによる分離・ネットワークの制限・ファイルシステムの制御・プロキシでのリクエストの検証など。組み合わせは、脅威モデルと運用の要件で決める
| リソース | 制限の選択肢 |
|---|---|
| ファイルシステム | 必要なディレクトリだけをマウントし、読み取り専用を選ぶ |
| ネットワーク | プロキシで特定のエンドポイントに限る |
| 認証情報 | 直接見せず、プロキシで注入する |
| システムの権限(capability) | コンテナで Linux の capability を落とす |
分離の技術#
分離の技術ごとに、安全性の強さ・性能の負担・運用の複雑さのトレードオフが違います。以下のどの構成でも、Claude Code(か Agent SDK のアプリ)は、分離の境界(サンドボックス・コンテナ・VM)の内側で動き、下の制御は、その境界の中からエージェントが届く範囲を制限します。
| 技術 | 分離の強さ | 性能の負担 | 複雑さ |
|---|---|---|---|
| サンドボックスランタイム | 良い(安全な既定) | とても低い | 低い |
| コンテナ(Docker) | 設定による | 低い | 中 |
| gVisor | とても強い(正しい設定で) | 中〜高 | 中 |
| VM(Firecracker・QEMU) | とても強い(正しい設定で) | 高い | 中〜高 |
サンドボックスランタイム。 コンテナなしの軽い分離には、OS の水準でファイルシステムとネットワークの制限を強制する sandbox-runtime があります。利点は簡単さで、Docker の設定・コンテナイメージ・ネットワークの設定が要りません。プロキシとファイルシステムの制限が組み込みです。
- ファイルシステム:OS の仕組み(Linux は
bubblewrap、macOS はsandbox-exec)で、設定したパスだけに読み書きを制限する - ネットワーク:ネットワーク名前空間を外し(Linux)、または Seatbelt のプロファイルで(macOS)、通信を組み込みのプロキシへ通す
- 設定:ドメインとファイルシステムのパスの JSON の許可リスト
npm install @anthropic-ai/sandbox-runtime
そのあと、許可するパスとドメインを書いた設定ファイルを作ります。留意点は2つです。
- 同じホストのカーネル:VM と違い、サンドボックスされたプロセスはホストのカーネルを共有する。カーネルの脆弱性で、理論上は脱出されうる。脅威モデルによっては許容できるが、カーネル水準の分離が要るなら gVisor か別の VM を使う
- TLS の検査がない:プロキシは、クライアントが示すホスト名でドメインを許可し、暗号化された通信を終端も検査もしない。サンドボックス内のコードは、ドメインフロンティングなどの手で、許可リストの外のホストに届きうる。より強い保証が要るなら、TLS を終端するプロキシを設定する。別の問題として、許可したドメインに対してエージェントが広い認証情報を持つなら、そのドメインを使って別のネットワーク要求を起こしたり、データを持ち出したりできないようにする
多くの1人の開発者や CI/CD の用途では、少ない設定でも sandbox-runtime が水準を大きく上げます。より強い分離が要るデプロイは、コンテナと VM の節へ進みます。
コンテナ。 コンテナは Linux の名前空間で分離します。各コンテナは、ファイルシステム・プロセスツリー・ネットワークスタックの自分の見え方を持ち、ホストのカーネルを共有します。セキュリティを強めたコンテナの設定の例です。
docker run \
--cap-drop ALL \
--security-opt no-new-privileges \
--security-opt seccomp=/path/to/seccomp-profile.json \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=100m \
--tmpfs /home/agent:rw,noexec,nosuid,size=500m \
--network none \
--memory 2g \
--cpus 2 \
--pids-limit 100 \
--user 1000:1000 \
-v /path/to/code:/workspace:ro \
-v /var/run/proxy.sock:/var/run/proxy.sock:ro \
agent-image
| オプション | 目的 |
|---|---|
--cap-drop ALL |
権限の昇格につながりうる NET_ADMIN や SYS_ADMIN などの Linux の capability を外す |
--security-opt no-new-privileges |
setuid のバイナリで、プロセスが権限を得ることを防ぐ |
--security-opt seccomp=... |
使えるシステムコールを制限する。Docker の既定は約44をブロックし、独自のプロファイルでさらにブロックできる |
--read-only |
コンテナのルートファイルシステムを書き換え不可にし、エージェントが変更を残せないようにする |
--tmpfs /tmp:... |
コンテナが止まると消える、書き込み可能な一時ディレクトリを与える |
--network none |
すべてのネットワークインターフェースを外す。エージェントは、下のマウントした Unix ソケットで通信する |
--memory 2g |
メモリの使用量を制限して、リソースの枯渇を防ぐ |
--pids-limit 100 |
プロセス数を制限して、フォーク爆弾を防ぐ |
--user 1000:1000 |
root でないユーザーで動かす |
-v ...:/workspace:ro |
コードを読み取り専用でマウントし、エージェントが解析できるが変更できないようにする。~/.ssh・~/.aws・~/.config のような機微なホストのディレクトリはマウントしない |
-v .../proxy.sock:... |
コンテナの外で動くプロキシへつながる Unix ソケットをマウントする |
追加の強化のオプションです。
| オプション | 目的 |
|---|---|
--userns-remap |
コンテナの root を、権限のないホストのユーザーへ対応づける。デーモンの設定が要るが、コンテナの脱出の被害を抑える |
--ipc private |
プロセス間通信を分離して、コンテナをまたぐ攻撃を防ぐ |
Unix ソケットの構成:--network none では、コンテナにネットワークインターフェースが一切ありません。エージェントが外へ届く唯一の道は、ホストで動くプロキシにつながるマウントした Unix ソケットです。プロキシは、ドメインの許可リストの強制・認証情報の注入・すべての通信の記録ができます。これは sandbox-runtime と同じ構成です。エージェントがプロンプトインジェクションで乗っ取られても、任意のサーバーへデータを持ち出せず、届くドメインを制御するプロキシ経由でしか通信できません。
gVisor。 標準のコンテナはホストのカーネルを共有するので、カーネルの脆弱性でコンテナから脱出されうります。gVisor は、システムコールを、ホストのカーネルへ届く前にユーザー空間で受け止め、ほとんどのシステムコールを実カーネルに触れずに処理する、独自の互換層を持ちます。エージェントが(プロンプトインジェクションなどで)悪意あるコードを動かしても、そのコードがまず gVisor のユーザー空間の実装を突破しなければならず、実カーネルへのアクセスは限られます。Docker で使うには、runsc のランタイムを入れてデーモンを設定し、そのランタイムでコンテナを動かします。
| 作業 | 負担 |
|---|---|
| CPU 中心の計算 | 約0%(システムコールの介入がない) |
| 単純なシステムコール | 約2倍遅い |
| ファイル I/O の多い処理 | 開閉を多用すると 10〜200 倍まで遅い |
マルチテナントの環境や信頼できない内容を処理するときは、追加の分離が負担に見合うことが多いです。
仮想マシン。 VM は、CPU の仮想化の拡張で、ハードウェア水準の分離を与えます。各 VM が自分のカーネルを動かし、強い境界になります。ゲストのカーネルの脆弱性は、ホストを直接は侵害しません。ただし、VM が gVisor などより自動で「安全」とは限らず、安全性はハイパーバイザーとデバイスのエミュレーションのコードに大きく左右されます。Firecracker は、軽量な microVM の分離のための設計で、125ミリ秒未満で起動し、メモリの負担は 5 MiB 未満、不要なデバイスのエミュレーションを削って攻撃面を減らします。この方式では、エージェントの VM に外向きのネットワークインターフェースがなく、vsock(仮想ソケット)で通信します。すべての通信が vsock でホストのプロキシへ流れ、プロキシが許可リストの強制と認証情報の注入をしてから転送します。
クラウドのデプロイ。 上の分離の技術のどれかに、クラウドのネットワーク制御を組み合わせます。
- エージェントのコンテナを、インターネットゲートウェイのないプライベートなサブネットで動かす
- クラウドのファイアウォールの規則(AWS のセキュリティグループ、GCP の VPC ファイアウォール)で、プロキシ以外へのエグレスをすべて止める
- リクエストの検証・ドメインの許可リストの強制・認証情報の注入・外部 API への転送をするプロキシ(
credential_injectorフィルターを持つ Envoy など)を動かす - エージェントのサービスアカウントには最小限の IAM 権限を与え、機微なアクセスは、可能ならプロキシ経由にする
- 監査のために、プロキシですべての通信を記録する
認証情報の管理#
エージェントは、API の呼び出し・リポジトリへのアクセス・クラウドサービスとのやり取りで、認証情報が要ることが多いです。課題は、認証情報自体を見せずにアクセスを与えることです。
プロキシのパターン。 推奨は、エージェントのセキュリティ境界の外でプロキシを動かし、外向きのリクエストへ認証情報を注入させることです。エージェントは認証情報なしでリクエストを送り、プロキシが認証情報を足して、宛先へ転送します。利点は次のとおりです。
- エージェントが、実際の認証情報を見ない
- プロキシが、許可するエンドポイントの許可リストを強制できる
- プロキシが、監査のためにすべてのリクエストを記録できる
- 認証情報が、各エージェントへ配られず、1か所の安全な場所に置かれる
Claude Code にプロキシを使わせる。 サンプリングのリクエストをプロキシ経由にする方法は2つあります。
| 方法 | 設定 | 内容 |
|---|---|---|
ANTHROPIC_BASE_URL(簡単だが、サンプリングの API リクエストだけ) |
export ANTHROPIC_BASE_URL="http://localhost:8080" |
Claude Code と Agent SDK が、サンプリングのリクエストを Claude API へ直接でなくプロキシへ送る。プロキシは平文の HTTP リクエストを受け取り、検査と変更(認証情報の注入を含む)をして、本物の API へ転送する |
HTTP_PROXY / HTTPS_PROXY(システム全体) |
export HTTP_PROXY="http://localhost:8080" と export HTTPS_PROXY="http://localhost:8080" |
Claude Code と Agent SDK が、この標準の環境変数に従い、すべての HTTP の通信をプロキシへ流す。HTTPS では、プロキシが暗号化された CONNECT のトンネルを作り、TLS の介入なしには、リクエストの内容を見ることも変えることもできない |
プロキシは自作することも、既存のものを使うこともできます。
| 製品 | 内容 |
|---|---|
| Envoy Proxy | 認証ヘッダーを足す credential_injector フィルターを持つ、本番向けのプロキシ |
| mitmproxy | HTTPS の通信を検査・変更する、TLS を終端するプロキシ |
| Squid | アクセス制御リストを持つキャッシュプロキシ |
| LiteLLM | 認証情報の注入とレート制限を持つ LLM ゲートウェイ |
ほかのサービスの認証情報。 Claude API からのサンプリング以外に、エージェントは、git リポジトリ・データベース・内部 API のようなサービスへの認証つきのアクセスが要ることが多いです。主な方法は2つです。
- 自作ツール:エージェントのセキュリティ境界の外で動くサービスへリクエストを回す、MCP サーバーか自作ツールでアクセスを与える。エージェントはツールを呼ぶが、実際の認証つきのリクエストは外で起き、ツールがプロキシを呼んで、プロキシが認証情報を注入する。たとえば git の MCP サーバーが、エージェントからコマンドを受け、ホストで動く git のプロキシへ転送し、プロキシが認証してからリモートのリポジトリへ接続する。利点は、TLS の介入が要らず、外のサービスが直接、認証つきのリクエストをすることと、認証情報が外に留まり、エージェントがツールのインターフェースだけを見ること
- トラフィックの転送:Claude API の呼び出しは、
ANTHROPIC_BASE_URLで、平文で検査・変更できるプロキシへ回せる。しかし、GitHub・npm のレジストリ・内部 API のようなほかの HTTPS のサービスは、通信が端から端まで暗号化されていることが多く、HTTP_PROXYでプロキシに通しても、プロキシには不透明な TLS のトンネルしか見えず、認証情報を注入できない
自作ツールなしで任意のサービスへの HTTPS の通信を変更するには、通信を復号し、検査や変更をして、転送の前に暗号化し直す、TLS を終端するプロキシが要ります。そのためには、次が要ります。
- プロキシを、エージェントのコンテナの外で動かす
- プロキシの CA 証明書を、エージェントの信頼ストアへ入れる(エージェントがプロキシの証明書を信頼するように)
HTTP_PROXY/HTTPS_PROXYで、通信をプロキシへ回すよう設定する
この方式は、自作ツールを書かずに、HTTP を使うどのサービスにも対応できますが、証明書の管理が複雑になります。
補足
HTTP_PROXY / HTTPS_PROXY に従わないプログラムもあります。多くのツール(curl・pip・npm・git)は従いますが、変数を迂回して直接つなぐものがあります。たとえば Node.js の fetch() は、既定でこの変数を無視します(Node 24 以降では NODE_USE_ENV_PROXY=1 で対応を有効にできる)。網羅するには、proxychains でネットワーク呼び出しを横取りするか、iptables で外向きの通信を透過プロキシへ転送します。透過プロキシはネットワーク水準で通信を横取りするので、クライアントに設定が要りません。どちらの方法も、TLS を終端するプロキシと、信頼された CA 証明書は必要で、通信が確実にプロキシへ届くようにするだけです。
ファイルシステムの設定#
ファイルシステムの制御は、エージェントが読み書きできるファイルを決めます。
コードを読み取り専用でマウントする。 エージェントがコードを解析するだけで変更しないなら、ディレクトリを読み取り専用でマウントします。
注意
読み取り専用のアクセスでも、コードのディレクトリから認証情報が漏れうります。マウントの前に、次のファイルを除くか、中身を消します。
| ファイル | 危険 |
|---|---|
.env、.env.local |
API キー・データベースのパスワード・秘密 |
~/.git-credentials |
平文の git のパスワードやトークン |
~/.aws/credentials |
AWS のアクセスキー |
~/.config/gcloud/application_default_credentials.json |
Google Cloud の ADC のトークン |
~/.azure/ |
Azure CLI の認証情報 |
~/.docker/config.json |
Docker のレジストリの認証トークン |
~/.kube/config |
Kubernetes クラスターの認証情報 |
.npmrc、.pypirc |
パッケージレジストリのトークン |
*-service-account.json |
GCP のサービスアカウントのキー |
*.pem、*.key |
秘密鍵 |
要るソースのファイルだけをコピーするか、.dockerignore のような絞り込みを使います。
書き込める場所。 エージェントがファイルを書く必要があるなら、変更を残したいかで選びます。コンテナの使い捨ての作業場所には、メモリ上だけにあり、コンテナが止まると消える tmpfs のマウントを使います。変更を残す前に確認したいなら、オーバーレイファイルシステムで、下のファイルを変えずにエージェントが書けます。変更は別の層に保存され、確認・適用・破棄できます。完全に永続する出力には、専用のボリュームをマウントしますが、機微なディレクトリとは分けます。
公式ドキュメント(英語)
- Track cost and usage
- Observability with OpenTelemetry
- Hosting the Agent SDK
- Securely deploying AI agents
2026年10月5日時点の内容をもとに、日本語でまとめています。