◉CLAUDE LABJP
●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/API & SDK
⬡ API & SDK/2026-04-12Advanced

Claude Managed Agents Sandbox Design: Running Autonomous Agents Safely in Production

A deep dive into the sandbox architecture of Claude Managed Agents, with production-ready security patterns and implementation code for running autonomous agents safely.

managed-agents6sandbox9security19production111api39

When embedding autonomous AI agents into a production service, the first wall you hit is drawing the line on how much freedom to grant them.

In April 2026, Anthropic released Claude Managed Agents in public beta, and it comes with a clear answer to this problem. Through a combination of container-based sandboxing, server-sent event (SSE) streaming, and built-in tools, the design balances agent autonomy with security.

But the official documentation alone doesn't cover the practical decisions you face in production: how to set container resource limits, what network access scope is appropriate, or how to handle runaway agents. This article draws on hands-on experience deploying Managed Agents in production to walk through sandbox security design patterns with concrete code.

The Three-Layer Sandbox Architecture

The Managed Agents sandbox isn't just a Docker container. It achieves defense-in-depth through three distinct layers.

import anthropic
 
client = anthropic.Anthropic()
 
# Specify sandbox settings when creating an agent
agent = client.agents.create(
    model="claude-sonnet-4-6",
    instructions="An agent that performs data analysis tasks",
    tools=[
        {"type": "code_execution"},
        {"type": "file_read"},
        {"type": "file_write"},
    ],
    # Sandbox configuration
    sandbox={
        "type": "managed",
        "memory_mb": 512,
        "timeout_seconds": 300,
        "network_access": "restricted",
        "allowed_domains": [
            "api.example.com",
            "storage.googleapis.com"
        ]
    }
)

Layer 1: Container isolation means each agent session runs in an independent Linux container. Filesystems are completely isolated—files created in one session are invisible to another.

Layer 2: Resource limits cap memory, CPU, and execution time. The code above specifies 512MB of memory and a 300-second timeout, but choosing the right values takes some thought.

Layer 3: Network policy is the most critical. Setting network_access to "restricted" blocks all outbound traffic except to domains explicitly listed in allowed_domains. There's almost never a reason to use "full" in production.

Practical Resource Limit Configuration

The official docs state that the default memory_mb is 1024MB, but in practice, the optimal value varies dramatically by task type.

# Resource configuration templates by task type
 
SANDBOX_PROFILES = {
    # Text processing (summarization, classification, transformation)
    "text_processing": {
        "memory_mb": 256,
        "timeout_seconds": 120,
        "network_access": "none",
    },
    # Data analysis (CSV/JSON parsing, chart generation)
    "data_analysis": {
        "memory_mb": 1024,
        "timeout_seconds": 600,
        "network_access": "restricted",
        "allowed_domains": ["storage.googleapis.com"],
    },
    # Code generation and execution
    "code_execution": {
        "memory_mb": 2048,
        "timeout_seconds": 900,
        "network_access": "restricted",
        "allowed_domains": ["pypi.org", "registry.npmjs.org"],
    },
    # External API integration
    "api_integration": {
        "memory_mb": 512,
        "timeout_seconds": 300,
        "network_access": "restricted",
        # allowed_domains set dynamically per task
    },
}
 
def create_agent_with_profile(profile_name: str, extra_domains: list[str] | None = None):
    """Create an agent based on a predefined profile"""
    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},
    )

Pay attention to the "text_processing" profile's "network_access": "none". Text processing tasks rarely need external communication, and completely blocking the network minimizes security risk. The principle of least privilege applies just as strongly to agent design.

Real-Time Monitoring with SSE Streaming

A standout feature of Managed Agents is the ability to get real-time execution status via SSE (Server-Sent Events). This enables anomaly detection and automatic shutdown mechanisms.

import json
from collections import defaultdict
 
class AgentMonitor:
    """Monitors agent execution and detects anomalies"""
 
    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:
        """Process an SSE event and return warnings if anomalies detected"""
        result = {"action": "continue", "warnings": []}
 
        if event.type == "tool_use":
            self.tool_call_count += 1
 
            # Check tool call count limit
            if self.tool_call_count > self.max_tool_calls:
                result["action"] = "stop"
                result["warnings"].append(
                    f"Tool call limit exceeded: {self.tool_call_count}/{self.max_tool_calls}"
                )
                return result
 
            # Track file access
            if event.tool_name in ("file_read", "file_write"):
                path = event.tool_input.get("path", "")
                self.accessed_files.append(path)
 
                # Detect access to sensitive paths
                sensitive_patterns = ["/etc/", "/proc/", "/sys/", ".env", "credentials"]
                for pattern in sensitive_patterns:
                    if pattern in path:
                        result["warnings"].append(
                            f"Sensitive path access detected: {path}"
                        )
 
        elif event.type == "error":
            self.error_count += 1
            if self.error_count >= self.max_errors:
                result["action"] = "stop"
                result["warnings"].append(
                    f"Error limit reached: {self.error_count}/{self.max_errors}"
                )
 
        return result
 
async def run_monitored_agent(agent_id: str, task: str):
    """Run an agent with monitoring"""
    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}")
                    # In production, send notifications to Slack/PagerDuty here
 
            if check["action"] == "stop":
                print("🛑 Force-stopping agent")
                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,
    }

The key here is the process_event method, which runs three checks on every tool call: count limits, file path safety validation, and cumulative error tracking. The moment any threshold is crossed, the session is immediately cancelled.

In production, we layer on additional logic: monitoring the moving average of tool execution time to detect sudden latency spikes, and detecting repeated calls to the same tool (a sign of infinite loops).

Failsafe Design: When Agents Go Off the Rails

No matter how carefully you design your sandbox, unexpected behavior will happen. What matters is having mechanisms ready to minimize the blast radius.

import asyncio
from contextlib import asynccontextmanager
from dataclasses import dataclass, field
from datetime import datetime
 
@dataclass
class SessionGuard:
    """Guards managing session lifecycle"""
    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:
        """Track output data, return False if limit exceeded"""
        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):
    """Context manager for guarded agent sessions"""
    session = client.agents.sessions.create(agent_id=agent_id)
    guard = SessionGuard(session_id=session.id, **guard_kwargs)
 
    # Launch timeout watchdog as a concurrent task
    async def _watchdog():
        while not guard.is_expired:
            await asyncio.sleep(5)
        print(f"⏰ Session {session.id} timed out")
        try:
            client.agents.sessions.cancel(session_id=session.id)
        except Exception:
            pass  # Already terminated
 
    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:
            pass

SessionGuard manages both time limits and output volume limits simultaneously. The output volume limit is easy to overlook, but it's essential for the scenario where an agent keeps generating massive files.

The _watchdog coroutine checks for timeout every 5 seconds and force-cancels the session the moment the limit is exceeded. This is separate from the Managed Agents API's own timeout_seconds—having an independent timeout on the application side acts as a safety net in case the API-side control fails for any reason.

Production Deployment Checklist

Based on the patterns above, here's what to verify when deploying Managed Agents to production.

Network controls: Ensure network_access is set to "none" or "restricted" based on the task. Keep allowed_domains to the absolute minimum. When internal API access is needed, route it through a dedicated API gateway for agents.

Resource limits: Set memory_mb and timeout_seconds based on actual workload measurements. Run load tests to measure peak memory usage, then add a 20% margin.

Monitoring and alerts: Log SSE events and detect anomaly patterns (tool call spikes, consecutive errors). Visualize average session duration and success rates on a dashboard.

Failsafes: Application-side timeouts must function independently from the API side. Agent output volume must be capped. Emergency shutdown procedures (bulk session cancellation) must be included in runbooks.

These principles aren't specific to Managed Agents—they're universal for operating any autonomous AI agent. Thinking about what an agent shouldn't do is just as important as thinking about what it can do when it comes to production reliability.


This article is published in full as a free sample of our premium content. Premium articles on Claude Lab feature practical implementation patterns and production know-how like this, with working code examples. To access all premium articles, consider joining our membership.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⬡ API & SDK2026-04-26
Building a Scalable Real-Time AI Chat Server with Claude API × WebSocket × Redis Pub/Sub — Node.js Production Architecture, Multi-User Management, and Cost Control
Running a real-time AI chat server on Claude API, WebSocket, and Redis Pub/Sub in production. SSE trade-offs, multi-instance routing, and how stop latency quietly corrupts per-user budgets — with the instrumentation code to measure it.
⬡ API & SDK2026-04-23
Using tool_choice to Cut Wasted Inference: Four Modes and Cost Patterns for Production
tool_choice is one of the most underused parameters in the Claude API. The four modes — auto, any, tool, and none — each change both behavior and token cost. Here are the patterns I reach for in production, with runnable code.
⬡ API & SDK2026-03-27
Claude API Production Resilience Patterns — Model Routing, Circuit Breakers, and Fallback Strategies for Indie Teams
Production resilience patterns for Claude API: circuit breakers, intelligent model routing, fallback chains, exponential backoff with jitter, and disaster recovery — with TypeScript implementations and operational notes from an indie developer.