自律型AIエージェントを本番サービスに組み込むとき、最初にぶつかる壁は「どこまで自由にさせるか」の線引きです。
2026年4月、AnthropicがパブリックベータとしてリリースしたClaude Managed Agentsは、この問題に対する明確な回答を持っています。コンテナベースのサンドボックス、サーバーサイドイベント(SSE)ストリーミング、そしてビルトインツールの組み合わせによって、エージェントの自律性とセキュリティを両立させる設計です。
しかし公式ドキュメントだけでは、本番運用で直面する「コンテナのリソース制限をどう設定するか」「ネットワークアクセスの許可範囲はどこまでが適切か」といった実務的な判断が難しいです。ここでは実際にManaged Agentsを本番環境に導入した経験をもとに、サンドボックスのセキュリティ設計パターンを具体的なコードとともに解説します。
サンドボックスアーキテクチャの3層構造
Managed Agentsのサンドボックスは、単なるDockerコンテナではありません。以下の3つのレイヤーで多層防御を実現しています。
import anthropic
client = anthropic.Anthropic()
# エージェントの作成時にサンドボックス設定を指定
agent = client.agents.create(
model="claude-sonnet-4-6",
instructions="データ分析タスクを実行するエージェント",
tools=[
{"type": "code_execution"},
{"type": "file_read"},
{"type": "file_write"},
],
# サンドボックス設定
sandbox={
"type": "managed",
"memory_mb": 512,
"timeout_seconds": 300,
"network_access": "restricted",
"allowed_domains": [
"api.example.com",
"storage.googleapis.com"
]
}
)第1層:コンテナ分離は、各エージェントセッションが独立したLinuxコンテナで実行される仕組みです。ファイルシステムは完全に隔離され、あるセッションで作成したファイルが別セッションから見えることはありません。
第2層:リソース制限では、メモリ・CPU・実行時間に上限を設ける。上のコード例では512MBのメモリと300秒のタイムアウトを指定しているが、この値の決め方にコツがあります。
第3層:ネットワークポリシーが最も重要です。network_access を "restricted" にすると、allowed_domains に明示したドメイン以外への外向き通信がすべてブロックされます。本番環境では "full" を使うべきケースはほぼありません。
リソース制限の実践的な設定値
公式ドキュメントでは memory_mb のデフォルトが1024MBと書かれているが、実際に本番で運用してみると、タスクの種類によって最適値は大きく異なります。
# タスク別のリソース設定テンプレート
SANDBOX_PROFILES = {
# テキスト処理系(要約・分類・変換)
"text_processing": {
"memory_mb": 256,
"timeout_seconds": 120,
"network_access": "none",
},
# データ分析系(CSV/JSON解析、グラフ生成)
"data_analysis": {
"memory_mb": 1024,
"timeout_seconds": 600,
"network_access": "restricted",
"allowed_domains": ["storage.googleapis.com"],
},
# コード生成・実行系
"code_execution": {
"memory_mb": 2048,
"timeout_seconds": 900,
"network_access": "restricted",
"allowed_domains": ["pypi.org", "registry.npmjs.org"],
},
# 外部API連携系
"api_integration": {
"memory_mb": 512,
"timeout_seconds": 300,
"network_access": "restricted",
# allowed_domains はタスクごとに動的に設定
},
}
def create_agent_with_profile(profile_name: str, extra_domains: list[str] | None = None):
"""プロファイルに基づいてエージェントを生成する"""
config = SANDBOX_PROFILES[profile_name].copy()
if extra_domains and config.get("network_access") == "restricted":
config.setdefault("allowed_domains", [])
config["allowed_domains"].extend(extra_domains)
return client.agents.create(
model="claude-sonnet-4-6",
instructions=f"Profile: {profile_name}",
tools=[{"type": "code_execution"}],
sandbox={"type": "managed", **config},
)ここで注目してほしいのは "text_processing" プロファイルの "network_access": "none" です。テキスト処理タスクでは外部通信が不要なケースがほとんどで、ネットワークを完全に遮断することでセキュリティリスクを最小化できます。「必要最小限の権限」という原則は、エージェント設計においても変わらありません。
SSEストリーミングによるリアルタイム監視
Managed Agentsの大きな特徴は、エージェントの実行状況をSSE(Server-Sent Events)でリアルタイムに取得できることです。これを活用すると、異常検知や自動停止の仕組みを構築できます。
import json
from collections import defaultdict
class AgentMonitor:
"""エージェントの実行を監視し、異常を検知する"""
def __init__(self, max_tool_calls: int = 50, max_errors: int = 3):
self.max_tool_calls = max_tool_calls
self.max_errors = max_errors
self.tool_call_count = 0
self.error_count = 0
self.accessed_files: list[str] = []
def process_event(self, event) -> dict:
"""SSEイベントを処理し、異常があれば警告を返す"""
result = {"action": "continue", "warnings": []}
if event.type == "tool_use":
self.tool_call_count += 1
# ツール呼び出し回数の上限チェック
if self.tool_call_count > self.max_tool_calls:
result["action"] = "stop"
result["warnings"].append(
f"ツール呼び出し上限超過: {self.tool_call_count}/{self.max_tool_calls}"
)
return result
# ファイルアクセスの追跡
if event.tool_name in ("file_read", "file_write"):
path = event.tool_input.get("path", "")
self.accessed_files.append(path)
# 機微なパスへのアクセス検知
sensitive_patterns = ["/etc/", "/proc/", "/sys/", ".env", "credentials"]
for pattern in sensitive_patterns:
if pattern in path:
result["warnings"].append(
f"機微なパスへのアクセス検知: {path}"
)
elif event.type == "error":
self.error_count += 1
if self.error_count >= self.max_errors:
result["action"] = "stop"
result["warnings"].append(
f"エラー上限到達: {self.error_count}/{self.max_errors}"
)
return result
async def run_monitored_agent(agent_id: str, task: str):
"""監視付きでエージェントを実行する"""
monitor = AgentMonitor(max_tool_calls=30, max_errors=2)
session = client.agents.sessions.create(agent_id=agent_id)
with client.agents.sessions.turn_stream(
session_id=session.id,
messages=[{"role": "user", "content": task}],
) as stream:
for event in stream:
check = monitor.process_event(event)
if check["warnings"]:
for w in check["warnings"]:
print(f"⚠️ {w}")
# 本番ではここで Slack/PagerDuty に通知
if check["action"] == "stop":
print("🛑 エージェントを強制停止")
client.agents.sessions.cancel(session_id=session.id)
break
return {
"tool_calls": monitor.tool_call_count,
"errors": monitor.error_count,
"files_accessed": monitor.accessed_files,
}このコードの肝は process_event メソッドで、ツール呼び出しのたびに3つのチェックを走らせている点です。回数制限、ファイルパスの安全性チェック、エラー回数の累積。どれか1つでも閾値を超えたら即座にセッションをキャンセルします。
本番運用ではここにさらに、ツール実行時間の移動平均を監視して急激な遅延を検知するロジックや、特定のツールの連続呼び出し(無限ループの兆候)を検知するパターンを追加しています。
フェイルセーフ設計:エージェントが暴走したときの対処
どれだけ慎重にサンドボックスを設計しても、「想定外の振る舞い」は起こりうる。重要なのは、暴走したときのダメージを最小化する仕組みを事前に用意しておくことです。
import asyncio
from contextlib import asynccontextmanager
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class SessionGuard:
"""セッションのライフサイクルを管理するガード"""
session_id: str
started_at: datetime = field(default_factory=datetime.now)
max_duration_seconds: int = 600
max_output_bytes: int = 10 * 1024 * 1024 # 10MB
total_output_bytes: int = 0
@property
def elapsed_seconds(self) -> float:
return (datetime.now() - self.started_at).total_seconds()
@property
def is_expired(self) -> bool:
return self.elapsed_seconds > self.max_duration_seconds
def track_output(self, data: str) -> bool:
"""出力データを追跡し、上限超過でFalseを返す"""
self.total_output_bytes += len(data.encode("utf-8"))
return self.total_output_bytes <= self.max_output_bytes
@asynccontextmanager
async def guarded_session(agent_id: str, **guard_kwargs):
"""ガード付きセッションのコンテキストマネージャ"""
session = client.agents.sessions.create(agent_id=agent_id)
guard = SessionGuard(session_id=session.id, **guard_kwargs)
# タイムアウト監視タスクを並行起動
async def _watchdog():
while not guard.is_expired:
await asyncio.sleep(5)
print(f"⏰ セッション {session.id} がタイムアウト")
try:
client.agents.sessions.cancel(session_id=session.id)
except Exception:
pass # すでに終了済みの場合
watchdog_task = asyncio.create_task(_watchdog())
try:
yield session, guard
finally:
watchdog_task.cancel()
try:
client.agents.sessions.delete(session_id=session.id)
except Exception:
passSessionGuard は時間制限と出力量制限の2つを同時に管理します。特に出力量制限は見落としがちだが、エージェントが巨大なファイルを生成し続けるケースへの対策として不可欠です。
_watchdog コルーチンは5秒間隔でタイムアウトを監視し、制限時間を超えた瞬間にセッションを強制キャンセルします。Managed Agents APIの timeout_seconds とは別に、アプリケーション側でも独立したタイムアウトを設けておくことで、API側の制御が何らかの理由で効かなかった場合のセーフティネットになります。
本番デプロイ時のチェックリスト
ここまでのパターンを踏まえて、Managed Agentsを本番にデプロイする際に確認すべき項目をまとめる。
ネットワーク制御として、network_access がタスクに応じて "none" か "restricted" に設定されていること。allowed_domains が必要最小限であること。内部APIへのアクセスが必要な場合は、エージェント専用のAPIゲートウェイを経由させること。
リソース制限として、memory_mb と timeout_seconds が実際のワークロードに基づいて設定されていること。負荷テストで最大メモリ使用量を計測し、20%のマージンを加えた値を設定すること。
監視・アラートとして、SSEイベントをログに記録し、異常パターン(ツール呼び出しの急増、エラーの連続発生)を検知する仕組みがあること。セッションの平均実行時間と成功率をダッシュボードで可視化していること。
フェイルセーフとして、アプリケーション側のタイムアウトがAPI側とは独立して機能すること。エージェントの出力量に上限があること。緊急停止の手段(全セッション一括キャンセル)が運用手順に含まれていること。
これらはManaged Agentsに限らず、自律型AIエージェントを運用する際の普遍的な原則です。エージェントに「何ができるか」を考えるのと同じくらい、「何をさせないか」を考えることが、本番環境での信頼性を左右します。
この記事はプレミアムコンテンツの見本として全文を無料公開しています。Claude Lab のプレミアム記事では、このような実践的な実装パターンや本番運用のノウハウを、動作するコード付きで詳しく解説しています。すべてのプレミアム記事を読むには、メンバーシップへの登録をご検討ください。