Agent SDK本番ホスティング — サブプロセスとセッションパターンの設計
Agent SDKはclaude CLIのサブプロセスを起動する構造で、ステートレスなAPIラッパーとは前提が違います。4つのセッションパターンと本番前に決めるべき設計判断がわかります。
Agent SDKホスティングがAPIラッパーと根本的に違う理由
Agent SDKで query() を呼ぶと、SDKは claude というCLIのサブプロセスを別プロセスとして起動し、標準入出力(stdio)経由でやり取りします。このサブプロセスがシェルと作業ディレクトリ、そしてローカルディスク上のセッション記録(JSONL形式のトランスクリプト)を保有します。1つのエージェントセッションは1つのサブプロセスに対応します。N個の同時セッションを動かすなら、N個のサブプロセスとN個のプロセスツリーが立ち上がります。
これは、リクエストを受けて計算し即座に返すだけのステートレスなAPIラッパーとは前提がまったく違います。ふつうのWeb APIならコンテナはいつ潰しても構いませんが、Agent SDKのコンテナは「ローカルディスクに状態を持つ長生きプロセスの器」です。リソース配分もセッションの永続化もスケーリングも、この一点から逆算して設計する必要があります。
デフォルトでは、すべてのセッションがアプリケーションの作業ディレクトリを継承します。セッションごとにファイルシステムを分けたいなら、query() ごとに cwd を明示的に渡します。
query({ prompt, options: { cwd: "/work/session-a" } });query(prompt=prompt, options=ClaudeAgentOptions(cwd="/work/session-a"))コンテナのファイルシステムには3種類の状態が乗ります。セッショントランスクリプト(既定は ~/.claude/projects/、CLAUDE_CONFIG_DIR を設定していればその配下の projects/)、CLAUDE.md のメモリーファイル、そして作業ディレクトリ上の成果物です。このどれも、コンテナの再起動・スケールダウン・別ノードへの移動を生き延びません。複数ホストにまたがってセッションを持ち回る設計は、Agent SDKのセッション管理で扱う resume / fork の仕組みとセットで理解する必要があります。
4つのセッションパターン — コンテナの寿命をどう設計するか
Agent SDKのホスティング設計は、「コンテナがセッションに対してどれだけ長生きするか」で4つの型に分かれます。どれか1つに固定する必要はなく、ワークロードごとに使い分けます。
| パターン | コンテナの寿命 | 向くワークロード |
|---|---|---|
| Ephemeral(使い捨て) | コンテナの寿命タスクごとに生成、完了で破棄 | 向くワークロードバグ調査・請求書抽出・文書翻訳など一発タスク |
| Long-running(常駐) | コンテナの寿命永続稼働、1コンテナで複数セッションを保持 | 向くワークロードメール自動応答・Slack常駐bot・サイトビルダーなど継続稼働 |
| Hybrid(ハイブリッド) | コンテナの寿命アイドル時に停止、再開時に起動 | 向くワークロード断続的なチェックインが続く個人アシスタント・長時間かかるリサーチ |
| Multi-agent container | コンテナの寿命1コンテナに複数サブプロセス | 向くワークロードエージェント同士が同じ環境で協調するマルチエージェントシミュレーション |
Ephemeralは、TASK_PROMPT を環境変数で受け取り query() を1回呼んで終了するワンショットのエントリーポイントで足ります。TypeScriptではトップレベル await を使うため entrypoint.mts にするか package.json に "type": "module" を指定します。
常駐型では、HTTPまたはWebSocketのエンドポイントを公開します。アクティブなセッションごとに稼働中の query() とその裏のサブプロセスを対応させる構成です。TypeScriptでは streamInput() でアクティブなセッションにターンを追加し、startup() で着信前にサブプロセスを事前起動(プレウォーム)できます。Pythonでは ClaudeSDKClient でセッションをターンをまたいで開いたまま保持します。コンテナのサイズは、同時に保持しうる最大セッション数がメモリーに収まるよう決めます。
アイドル時に停止し再開時だけ起動する構成では、セッションIDをキーに SessionStore からセッションを読み込み、更新をストアへ書き戻します。
import { query, type SessionStore } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: userInput,
options: { resume: sessionId, sessionStore },
})) {
// ...
}1コンテナに複数エージェントを同居させる構成では、まずエージェントごとに作業ディレクトリを分けます。そのうえで設定の読み込みも分離し、 CLAUDE.md が他のエージェントに漏れないようにします。具体的なオプションは後述のマルチテナント分離と同じです。
コンテナのプロビジョニング — サンドボックスとリソースをどう決めるか
コンテナはサンドボックス化した環境で動かし、プロセス分離・リソース制限・ネットワーク制御・エフェメラルなファイルシステムを確保します。プロバイダーを選ぶときの判断軸は5つです。サンドボックスの運用主体がプロバイダー側か自社側か、コールドスタート(起動から最初のリクエストを受けられるまで)の速さ、永続ストレージの有無、秒単位・リクエスト単位・時間単位のどの課金モデルか、そしてカスタムのアウトバウンドルールやプライベートVPCピアリングに対応するかです。Ephemeralパターンはサブ秒起動が必要ですが、Long-runningは多少の起動遅延を許容できます。
自社でDocker・gVisor・Firecrackerを運用する分離手法の詳細は、Agent SDKの安全なデプロイにまとめています。
ランタイムはPython 3.10以上またはNode.js 18以上が必要です。TypeScript・Python両SDKとも、ほとんどのインストール構成でネイティブのClaude Codeバイナリを同梱しているため、起動するCLI自体に別途Node.jsを入れる必要はありません。同梱バイナリはSDKパッケージのバージョンに固定されているので、CLIの更新はSDKの更新経由で行います。SDKはセマンティックバージョニングに従うため、パッチリリースは継続的に取り込み、マイナーリリースは変更履歴(changelog)を確認してから取り込みます。
リソースの出発点は、起動直後のエージェント1体あたりRAM 1GiB・ディスク5GiB・CPU 1コアです。これはあくまで最低ラインで、天井ではありません。メモリー使用量はセッションの長さとツール活動量に比例して伸びるため、アイドル時ではなく実運用で想定する同時実行数とセッション長でサイジングします。
ネットワークは api.anthropic.com へのアウトバウンドHTTPSが必須で、Amazon BedrockやGoogle CloudのAgent Platform経由ならそれぞれのリージョナルエンドポイントに向けます。MCPサーバーや外部ツールを使うなら、それらのエンドポイントへの到達性も必要です。インバウンドはコンテナにHTTPまたはWebSocketのポートを1つ公開し、アプリケーションがそこでクライアントのリクエストを受けます。サブプロセス自体はネットワークをリッスンしません。
本番で必ず決める5つのこと
自社ホスティングを出荷する前に、次の5点は避けて通れません。
セッションと状態の永続化
既定のローカルディスクは再起動・スケールダウン・ノード移動で失われます。ユーザーが再開を期待するセッションは、SessionStore アダプターでトランスクリプトを永続ストレージへミラーします。サブプロセスはまずローカルディスクに書き込み、SDKがそのコピーをストアへ転送する「ミラー」構造です。ストアから再開した実行だけは終了時にローカルコピーを削除するため、その場合はストアが唯一の永続コピーになります。ストアへの転送に失敗すると { type: "system", subtype: "mirror_error" } メッセージを発してバッチを捨て、クエリ自体は継続します。ストアの耐久性が重要な運用では、このメッセージを監視対象に入れます。
観測性(オブザーバビリティ)
Agent SDKのエージェントは、多数のAPI往復をまたいでツール呼び出しを重ねる長生きプロセスです。テレメトリーなしでは、どのツールが動いたか、どれだけ時間がかかったか、どこでセッションが詰まったかが見えません。SDKは環境変数からOpenTelemetryの設定を継承するため、コンテナまたはオーケストレーター側で環境変数を設定すれば、すべての query() 呼び出しがスパン・メトリクス・ログイベントをコレクターへ書き出します。トレースだけは CLAUDE_CODE_ENHANCED_TELEMETRY_BETA が別途必要です。プロンプト本文やツール入力は既定でエクスポートに含まれません。
認証とシークレット
サブプロセスは環境変数の ANTHROPIC_API_KEY を読みます。シークレットマネージャーから供給するか、ANTHROPIC_BASE_URL でモデル呼び出しをプロキシ経由に切り替え、コンテナの外側で鍵を注入します。受信側の認証はエージェントコンテナの手前のゲートウェイに置き、エージェント自身がユーザートークンを検証する構成にはしません。外部ツール向けの認証情報も、エージェントの実行環境には置かず、リクエストがコンテナを出た後にプロキシが注入します。
スケーリングと同時実行数
各セッションは自分専用のサブプロセスで動くため、1ホストで捌ける同時実行数はRAMがいくつのサブプロセスを保持できるかで決まります。
1ホストあたりのエージェント数 = (ホストRAM − オーバーヘッド) ÷ (セッションあたりのRAM上限)セッションあたりの上限は、想定する長さとツール負荷で代表的なセッションを実際に流し、ピークのRSS(実メモリー使用量)を計測して求めます。Long-runningのように1コンテナが多数のセッションを保持する構成では、コンテナのプールをロードバランサーの背後に置き、sessionId の一貫性ハッシュで各セッションを1つのコンテナに固定します。固定されたセッションは、退避されるかコンテナが再起動するまで同じサブプロセスに当たり続けます。
コスト
Anthropicのトークン費用は、たいていコンテナのインフラ費用を一桁以上上回ります。最小構成のコンテナは1時間あたり0.05ドル程度で動きますが、1本の長いエージェントセッションはトークン代だけで数ドルに達することがあります。
マルチテナント分離はどこまでやるべきか
共有コンテナで複数テナントを捌く構成では、SDKの既定動作(ファイルシステムから設定と CLAUDE.md メモリーファイルを読む挙動)が、あるテナントの文脈を別のテナントのセッションへ漏らす経路になります。
隔離には次の設定を組み合わせます。TypeScriptなら settingSources: []、Pythonなら setting_sources=[] を渡してユーザー・プロジェクト・ローカルの各設定読み込みをスキップします。env に CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 を設定します(自動メモリーは settingSources の指定にかかわらず ~/.claude/projects/<project>/memory/ から読み込まれるため、これは別立てで止める必要があります)。CLAUDE_CONFIG_DIR をテナントごとのディレクトリに向け、グローバル設定を共有させません。各 query() 呼び出しで cwd を明示し、テナントごとの作業ディレクトリを使います。プロキシ側ではテナントごとに出口IP・認証情報・ドメイン許可リストを分け、あるテナントが侵害されても別テナントのアウトバウンドポリシー経由でデータを持ち出せないようにします。
for await (const message of query({
prompt,
options: {
cwd: tenantDir,
settingSources: [],
env: {
...process.env,
CLAUDE_CONFIG_DIR: configDir,
CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1",
},
},
})) {
// ...
}CLAUDE_CONFIG_DIR を細かく分けるほどトランスクリプトのパスが長くなりがちですが、1つの設定ディレクトリが1つの作業ディレクトリしか扱わず、かつテナント間で SessionStore を共有しないなら、env に CLAUDE_CODE_PROJECT_DIR_NAME を設定してパスを短く保てます。これはTypeScript Agent SDK 0.3.234以降、Python Agent SDK 0.2.140以降が必要です。
既知の制限 — 設計に織り込んでおくべき4点
公式ドキュメントが挙げる制限は、対処法とセットで設計に組み込む必要があります。
| 制限 | 対処 |
|---|---|
| セッションにトップレベルのタイムアウトがない | 対処Options の maxTurns でツール呼び出しの往復回数に上限を設ける |
| 長いセッションでメモリーが伸び続ける | 対処セッション長に上限を設けるか、サブプロセスを定期的に入れ替える |
| サブエージェントの並列ファンアウトが大きいとレート制限に当たりうる | 対処1回の広い並列発行ではなく、小さいバッチに分割する |
| サブエージェントごとのウォールクロック期限がない | 対処AgentDefinition の maxTurns で各サブエージェントに上限を設ける。バックグラウンドのサブエージェント限定で CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS が出力停止時のウォッチドッグとして働くが、総実行時間の期限ではない |
デプロイ後のトラブルは、ローカルでは動くのにサービス化すると失敗するパターンが典型です。CLIが起動時に見つからない場合、コンテナやサービスマネージャーがシェルと違う PATH でアプリケーションを実行しているか(Python)、イメージビルドでSDKのオプション依存関係を落としているか pathToClaudeCodeExecutable の指す先がイメージ内に存在しないか(TypeScript)を疑います。イメージ内にCLIはあるのに起動しない場合は、コンテナのアーキテクチャやlibcに合わないバイナリか、イメージビルド中に実行権限が落ちていないかを確認します。
Managed Agentsという選択肢との線引き
インフラの制御・独自の分離方式・自前のデータプレーンが要らないなら、Anthropicがエージェントとサンドボックスを運用するホスト型REST APIのManaged Agentsという選択肢もあります。アプリケーションはイベントを送って結果をストリームで受け取るだけで、ここまで説明してきたサブプロセス管理・セッション永続化・マルチテナント分離のどれも自分で運用する必要がありません。
この記事で扱った自社ホスティングが要るのは、独自のサンドボックス実装を差し込みたい、既存のインフラ(Kubernetes・独自のVPC構成など)にそのまま載せたい、あるいはコンプライアンス上データプレーンを自社で持つ必要がある場合です。逆に言えば、「サブプロセスの寿命管理」「マルチテナント分離」「スケーリングの計算式」のどれもピンとこないなら、まずManaged Agentsを検討したほうが早く本番に出せます。自社ホスティングは、これらの設計判断を引き受ける覚悟とセットで選ぶインフラです。
よくある質問
Agent SDKのコンテナはKubernetesとDockerのどちらで動かせますか
どちらでも動きます。公式のホスティングクックブックにローカルDocker・Modal・Kubernetes向けのデプロイ可能なコードが用意されており、この記事で説明したセッションパターン(Ephemeral / Long-running / Hybrid / Multi-agent)は、どのデプロイ先を選んでも同じ判断軸で設計できます。
SessionStoreを設定しないとどうなりますか
Hybridパターン以外なら動作はしますが、コンテナが再起動・スケールダウンすると、その時点でローカルディスク上のセッショントランスクリプトは失われます。ユーザーが後から会話を再開することを想定する構成では、ストアの設定を省略できません。
1台のホストに何セッション詰め込めますか
固定値はなく、(ホストRAM − オーバーヘッド) ÷ (セッションあたりのRAM上限) で計算します。RAM上限は、想定する長さとツール負荷で代表的なセッションを実際に流し、ピークのRSSを計測して決めます。1GiBはあくまで起動直後の出発点です。
まとめ
Agent SDKのホスティングは、サブプロセスモデルというひとつの事実から設計が逆算されます。セッションパターン(Ephemeral・Long-running・Hybrid・Multi-agent)をワークロードに合わせて選び、コンテナのプロビジョニングとスケーリング式でリソースを見積もり、永続化・観測性・認証・マルチテナント分離を本番前に決める。この5点セットを飛ばしたまま「ローカルで動いたから」とデプロイすると、再起動でセッションが消える・複数テナントの文脈が混線する・レート制限で並列ファンアウトが詰まるといった形で、本番に出てから初めて表面化します。自前でインフラを持つ理由が明確でないなら、Managed Agentsという選択肢も比較対象に入れる価値があります。