Skip to main content

services.code_sandbox_manager

Code Sandbox Manager for Agent Runtimes.

This module provides a centralized manager for code sandbox instances, allowing runtime configuration of the sandbox variant (local-eval or local-jupyter).

It also provides :class:ManagedSandbox, a transparent proxy that delegates every call to the manager's current sandbox. All consumers (CodemodeToolset, SandboxExecutor, …) should receive a ManagedSandbox instead of a concrete sandbox so that when the manager is reconfigured (e.g. switching from local-eval to local-jupyter via the /mcp-servers/start API), every component automatically uses the new sandbox without being rebuilt.

Usage: from agent_runtimes.services.code_sandbox_manager import ( get_code_sandbox_manager, CodeSandboxManager, )

# Get the singleton manager
manager = get_code_sandbox_manager()

# Configure for Jupyter sandbox
manager.configure(
variant="local-jupyter",
jupyter_url="http://localhost:8888",
jupyter_token="my-token",
)

# Get a managed proxy — safe to hold long-term
sandbox = manager.get_managed_sandbox()

SandboxConfig Objects

@dataclass
class SandboxConfig()

Configuration for the code sandbox.

Attributes:

  • variant - The sandbox variant to use.

  • jupyter_url - The Jupyter server URL (only for local-jupyter variant).

  • jupyter_token - The Jupyter server token (only for local-jupyter variant).

  • mcp_proxy_url - The MCP tool proxy URL for two-container setups. When set, remote sandboxes will call tools via HTTP to this URL instead of trying to use stdio MCP processes directly.

    Example for local dev: "http://localhost:8765/api/v1/mcp/proxy" Example for K8s: "http://agent-runtimes:8765/api/v1/mcp/proxy"

ManagedSandbox Objects

class ManagedSandbox()

Transparent proxy that always delegates to the manager's current sandbox.

When the :class:CodeSandboxManager is reconfigured (e.g. switching from local-eval to local-jupyter), the proxy automatically picks up the new sandbox. Consumers that hold a reference to this proxy never need to be rebuilt or notified.

This class implements the same interface as :class:code_sandboxes.Sandbox so it is a drop-in replacement everywhere a Sandbox is expected (CodemodeToolset, SandboxExecutor, etc.).

__getattr__

def __getattr__(name: str) -> Any

Forward any attribute not found on the proxy to the current sandbox.

This catches attributes like _default_context, config, _started, _tool_caller, _tags, _namespaces, _execution_count, etc. that the concrete sandbox sets in its __init__ / start() and that consumers access directly.

__getattr__ is only called when normal lookup fails, so explicit methods and properties defined on this class take precedence.

__setattr__

def __setattr__(name: str, value: Any) -> None

Forward attribute writes to the current sandbox.

Attributes that belong to the proxy itself (_manager) are stored on the proxy; everything else is forwarded.

is_executing

@property
def is_executing() -> bool

Check if the sandbox is currently executing code.

interrupt

def interrupt() -> bool

Interrupt the currently running code.

CodeSandboxManager Objects

class CodeSandboxManager()

Manages the lifecycle of code sandbox instances.

This manager provides:

  • Singleton pattern for global access
  • Runtime configuration of sandbox variant
  • Thread-safe sandbox creation and access
  • Automatic sandbox lifecycle management (start/stop)
  • Per-agent sandbox isolation (each agent gets its own sandbox)

The manager supports three sandbox variants:

  • local-eval: Uses Python exec() for code execution (default)
  • local-jupyter: Connects to an existing Jupyter server (URL required)
  • jupyter: Delegates to code_sandboxes to start its own Jupyter server on a random free port (no external URL needed)

__init__

def __init__() -> None

Initialize the manager with default configuration.

get_instance

@classmethod
def get_instance(cls) -> CodeSandboxManager

Get the singleton instance of the manager.

Returns:

The CodeSandboxManager singleton instance.

reset_instance

@classmethod
def reset_instance(cls) -> None

Reset the singleton instance (primarily for testing).

This stops any running sandbox and clears the instance.

config

@property
def config() -> SandboxConfig

Get the current sandbox configuration.

variant

@property
def variant() -> SandboxVariant

Get the current sandbox variant.

is_jupyter

@property
def is_jupyter() -> bool

Check if the current variant is Jupyter-based.

configure

def configure(variant: SandboxVariant | None = None,
jupyter_url: str | None = None,
jupyter_token: str | None = None,
mcp_proxy_url: str | None = None,
env_vars: dict[str, str] | None = None) -> None

Configure the sandbox settings.

If the sandbox is running and the variant changes, the existing sandbox will be stopped and a new one will be created on next access.

Arguments:

  • variant - The sandbox variant to use. If None, keeps current.
  • jupyter_url - The Jupyter server URL. Can include token as query param.
  • jupyter_token - The Jupyter server token. Overrides token in URL.
  • mcp_proxy_url - The MCP tool proxy URL for two-container setups. When set, remote sandboxes will call tools via HTTP to this URL.
  • env_vars - Environment variables to inject into the sandbox. For local-jupyter, these are set in the Jupyter kernel's os.environ so that executed code can access them (e.g. API keys).

configure_from_url

def configure_from_url(jupyter_sandbox_url: str,
mcp_proxy_url: str | None = None,
env_vars: dict[str, str] | None = None) -> None

Configure for Jupyter sandbox from a URL with optional token.

This is a convenience method for CLI/API usage where the URL format is: <URL>?token=<TOKEN>

For two-container setups (Kubernetes), the mcp_proxy_url should be set to the agent-runtimes container's MCP proxy endpoint.

Arguments:

  • jupyter_sandbox_url - The Jupyter server URL, optionally with token.
  • mcp_proxy_url - The MCP tool proxy URL for two-container setups. If not provided, will default to http://127.0.0.1:8765/api/v1/mcp/proxy for local-jupyter variant (assumes colocated containers).
  • env_vars - Environment variables to inject into the sandbox kernel (e.g. API keys decoded by the companion service).

get_sandbox

def get_sandbox() -> Sandbox

Get the current sandbox instance, creating one if needed.

The sandbox will be started automatically if not already running.

Returns:

The configured Sandbox instance.

Raises:

  • ImportError - If required sandbox dependencies are not installed.

get_or_create_sandbox

def get_or_create_sandbox(start: bool = True) -> Sandbox

Get existing sandbox or create a new one.

This method allows getting an unstarted sandbox if needed, which can be useful when the sandbox will be started later or when passing to components that manage their own lifecycle.

Arguments:

  • start - Whether to start the sandbox if creating new one. Default is True for backward compatibility.

Returns:

The configured Sandbox instance.

get_managed_sandbox

def get_managed_sandbox() -> ManagedSandbox

Return a :class:ManagedSandbox proxy bound to this manager.

The proxy delegates every call to whatever concrete sandbox the manager currently holds. When the manager is reconfigured (e.g. configure_from_url switches from local-eval to local-jupyter), the proxy automatically picks up the new sandbox — consumers never need to be rebuilt.

This is the recommended way to obtain a sandbox for long-lived components (CodemodeToolset, SandboxExecutor, …).

Returns:

A ManagedSandbox proxy that is safe to hold indefinitely.

stop

def stop() -> None

Stop the current sandbox if running.

create_agent_sandbox

def create_agent_sandbox(agent_id: str,
variant: SandboxVariant = "local-eval",
env_vars: dict[str, str] | None = None) -> Sandbox

Create a dedicated sandbox for a specific agent.

Each agent gets its own isolated sandbox instance. For the &quot;jupyter&quot; variant, code_sandboxes.LocalJupyterSandbox starts its own Jupyter server on a random free port.

Arguments:

  • agent_id - Unique agent identifier.
  • variant - The sandbox variant (&quot;local-eval&quot;, &quot;jupyter&quot;, or &quot;local-jupyter&quot;).
  • ``2 - Environment variables to inject into the sandbox after it starts.

Returns:

The started Sandbox instance.

Raises:

  • ``5 - If the agent already has a sandbox.

get_agent_sandbox

def get_agent_sandbox(agent_id: str) -> Sandbox | None

Get the sandbox for a specific agent.

Arguments:

  • agent_id - Unique agent identifier.

Returns:

The agent's Sandbox or None if not found.

stop_agent_sandbox

def stop_agent_sandbox(agent_id: str) -> None

Stop and remove the sandbox for a specific agent.

For the &quot;jupyter&quot; variant this stops the Jupyter server that code_sandboxes started.

Arguments:

  • agent_id - Unique agent identifier.

stop_all_agent_sandboxes

def stop_all_agent_sandboxes() -> None

Stop all per-agent sandboxes.

This should be called during server shutdown to ensure every Jupyter server that code_sandboxes spawned is terminated.

restart

def restart() -> Sandbox

Restart the sandbox with current configuration.

Returns:

The new Sandbox instance.

get_status

def get_status() -> dict[str, Any]

Get the current status of the sandbox manager.

Returns:

A dictionary with status information including paths.

get_code_sandbox_manager

def get_code_sandbox_manager() -> CodeSandboxManager

Get the global CodeSandboxManager singleton.

Returns:

The CodeSandboxManager instance.

interrupt_sandbox

def interrupt_sandbox() -> bool

Interrupt any code currently running in the managed sandbox.

This is the single entry-point that should be called whenever a user-initiated stop/cancel occurs (stop button, ACP session/cancel, AG-UI terminate, etc.).

Returns:

True if code was executing and was successfully interrupted.