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
"jupyter" variant, code_sandboxes.LocalJupyterSandbox
starts its own Jupyter server on a random free port.
Arguments:
agent_id- Unique agent identifier.variant- The sandbox variant ("local-eval","jupyter", or"local-jupyter").- ``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 "jupyter" 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.