Appearance
Sandbox API
Package: elsai-agents v0.3.0+
Sandbox execution APIs live in elsai.execution and elsai.agent. Remote backends (AgentCore, Daytona) ship in elsai-agents-tools.
Concept guides: Sandbox overview · Shared workspace · Backends
Sandbox
python
from elsai.execution import SandboxShared sandbox workspace for one or more agents in a single process. The application creates and owns the Sandbox; agents attach via AgentConfig(sandbox=...).
python
async with Sandbox(
workspace_id="graph-run-1",
backend=backend_template,
ttl_seconds=7200,
seed_from="/path/to/project",
) as sandbox:
...Sandbox.__init__
| Parameter | Required | Default | Description |
|---|---|---|---|
workspace_id | Yes | — | Registry key for this workspace in the current process. All agents on the same Sandbox instance share one backend. Not a cross-process sharing key. |
backend | Yes | — | Backend instance or template (LocalSandboxBackend, AgentCoreSandboxTemplate, DaytonaSandboxTemplate, …). |
ttl_seconds | Yes | — | Workspace idle TTL (seconds). Required — choose explicitly (for example 7200). Idle time is measured from the last sandbox activity. Enforced when SandboxManager.cleanup_expired() runs; primary teardown is stop() or async with Sandbox. See Timeouts and TTL. |
manager | No | auto SandboxManager(ttl_seconds=...) | Session manager instance. Override for tests or multi-sandbox applications. |
seed_from | No | None | Host project directory copied into the workspace on start(). |
seed_exclude | No | None | Extra glob patterns merged with DEFAULT_SANDBOX_SEED_EXCLUDES. |
sync_policy | No | default_sandbox_sync_policy() | Include/exclude globs for flush(). |
sync_dest | No | seed_from | Host path for artifact sync when flush() dest is omitted. |
Properties
| Property | Type | Description |
|---|---|---|
is_started | bool | True after a successful start(). |
owner_id | str | None | In-process ownership token for the current start cycle. |
Sandbox.start
python
await sandbox.start() # -> WorkspaceSeedResult | NoneCreate the backend, claim the workspace, and seed when seed_from is set. Idempotent.
Raises: SandboxStartError when another Sandbox instance already owns this workspace_id in-process.
Sandbox.stop
python
await sandbox.stop(sync=True)Detach all attached agents, optionally flush() artifacts, then close the backend session.
| Parameter | Required | Default | Description |
|---|---|---|---|
sync | No | True | When True, run flush() before closing. |
Sandbox.add_files
python
await sandbox.add_files(files, overwrite=True) # -> list[str]Write or upload files into the workspace without an agent turn (orchestrator API).
| Parameter | Required | Default | Description |
|---|---|---|---|
files | Yes | — | Mapping of workspace-relative path → str, bytes, or host Path. |
overwrite | No | True | When False, skip paths that already exist in the workspace. |
Returns: List of workspace paths written.
Sandbox.download_files
python
await sandbox.download_files(files, overwrite=True) # -> list[str]Download sandbox files to host paths.
| Parameter | Required | Default | Description |
|---|---|---|---|
files | Yes | — | Mapping of sandbox-relative path → host destination path. |
overwrite | No | True | When False, skip when the host destination already exists. |
Returns: Sandbox paths downloaded successfully.
Raises: OSError when a sandbox file cannot be read or written on the host.
Sandbox.delete_files
python
await sandbox.delete_files(paths)Delete workspace-relative paths. Host-side API — not restricted by agent attach mode.
| Parameter | Required | Description |
|---|---|---|
paths | Yes | List of workspace-relative file paths. |
Sandbox.move_file
python
await sandbox.move_file(src, dest)Move or rename a file within the workspace (read → write → delete).
| Parameter | Required | Description |
|---|---|---|
src | Yes | Source workspace-relative path. |
dest | Yes | Destination workspace-relative path. |
Sandbox.read_file
python
await sandbox.read_file(path) # -> strConvenience read for applications and tests.
| Parameter | Required | Description |
|---|---|---|
path | Yes | Workspace-relative path. |
Sandbox.flush
python
await sandbox.flush(dest=None) # -> WorkspaceSyncResult | NoneSync sandbox artifacts to the host per sync_policy.
| Parameter | Required | Default | Description |
|---|---|---|---|
dest | No | sync_dest or seed_from | Host directory for synced files. Returns None when no destination is configured. |
Sandbox.attach
python
sandbox.attach(agent, mode=SandboxAttachMode.READ_WRITE)Wire an agent to this sandbox. Normally invoked automatically from AgentConfig(sandbox=...) — callers rarely call this directly.
| Parameter | Required | Default | Description |
|---|---|---|---|
agent | Yes | — | Agent instance to attach. |
mode | No | READ_WRITE | SandboxAttachMode — tool access level. |
Raises: SandboxAttachError when the sandbox is not started, the agent is already attached elsewhere, or attach cannot complete.
Sandbox.detach
python
sandbox.detach(agent)Remove an agent's binding without stopping the sandbox VM. Idempotent when the agent is not attached.
See Shared workspace — Attach and detach.
Sandbox.ref
python
sandbox.ref() # -> SandboxRefReturn a forward-compatible handle stub (v1 — not used for cross-process sharing).
Context manager
python
async with Sandbox(...) as sandbox:
...Calls start() on enter and stop(sync=True) on exit.
SandboxManager
python
from elsai.execution import SandboxManagerGet-or-create sandbox sessions keyed by workspace_id, with optional TTL expiry.
python
SandboxManager(ttl_seconds=3600.0)| Parameter | Required | Default | Description |
|---|---|---|---|
ttl_seconds | No | 3600.0 | Idle seconds before a session is eligible for cleanup_expired(). Set to None to disable idle expiry checks. Does not run automatically — call cleanup_expired() on a schedule in long-running apps. |
Methods
| Method | Description |
|---|---|
resolve_workspace_id(agent) | Workspace key: _sandbox_workspace_id → SessionManager.session_id → agent_id. |
resolve_session_id(agent) | Legacy alias for resolve_workspace_id. |
get_session(session_id) | Return ManagedSandboxSession or None. |
get_or_create(session_id, factory) | Return or create a managed session (per-agent path). |
claim_session(session_id, factory, owner_id=...) | Atomically claim a workspace for a Sandbox start cycle. |
mark_prepared(session_id, seed_result=...) | Mark session as seeded. |
close_session(session_id, sync_artifacts=True, agent=None) | Destroy session and close backend. |
cleanup_expired() | Close all idle sessions past ttl_seconds. Returns closed session IDs. Call periodically if you rely on TTL as a backstop when stop() is missed. |
backend_factory_for_session(template, session_id) | Build a factory; uses build_for_session when the template supports it. |
LocalSandboxBackend
python
from elsai.execution import LocalSandboxBackendHost-mounted workspace for local development only — no process isolation.
Not isolated
Commands run as subprocesses on the host. Use AgentCore or Daytona for production workloads.
python
LocalSandboxBackend(
workspace_root="/tmp/sandbox-ws",
timeout=120,
max_output_bytes=100_000,
)| Parameter | Required | Default | Description |
|---|---|---|---|
workspace_root | Yes | — | Host directory used as the workspace root. |
timeout | No | 120 | Default command timeout in seconds. Timeout → exit code 124. |
max_output_bytes | No | 100000 | Max combined stdout/stderr captured; longer output is truncated. |
inherit_env | No | True | Whether subprocesses inherit the parent process environment. |
env | No | None | Extra environment variables (merged when inherit_env=True). |
Remote backend templates and their environment variables are documented in Sandbox backends.
SandboxPlugin
python
from elsai.plugins.sandbox import SandboxPluginRegisters sandbox workspace tools when a backend is active. When AgentConfig(sandbox=...) is set, the plugin is auto-registered on attach if not already present.
Tools
execute
| Parameter | Type | Required | Description |
|---|---|---|---|
command | str | Yes | Shell command to run in the workspace. |
cwd | str | No | Working directory override inside the workspace. |
Omitted when the backend does not implement SandboxBackend (no shell execution support).
read_file
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
path | str | Yes | — | Workspace-relative file path. |
offset | int | No | 0 | Line offset for partial reads. |
limit | int | No | None | Maximum number of lines to read. |
write_file
| Parameter | Type | Required | Description |
|---|---|---|---|
path | str | Yes | Workspace-relative file path. |
content | str | Yes | Text content to write. |
list_dir
| Parameter | Type | Required | Description |
|---|---|---|---|
path | str | Yes | Workspace-relative directory path. |
Custom tool helpers
python
from elsai.plugins.sandbox import (
run_command,
read_workspace_file,
write_workspace_file,
list_workspace_dir,
format_execute_result,
)| Helper | Description |
|---|---|
run_command(agent, command, cwd=None) | Run a shell command; returns formatted exit code and output. |
read_workspace_file(agent, path, offset=0, limit=None) | Read a workspace file from a custom @tool. |
write_workspace_file(agent, path, content) | Write a workspace file from a custom @tool. |
list_workspace_dir(agent, path) | List a directory from a custom @tool. |
format_execute_result(result) | Format an ExecuteResult like the execute tool. |
Mutating helpers call ensure_sandbox_mutation_allowed — raises on read-only or detached agents.
For custom timeouts on run_command, wrap with async with asyncio.timeout(seconds):.
AgentConfig sandbox fields
python
from elsai.agent import AgentConfig, SandboxMode, SandboxAttachMode, WorkspaceSyncPolicy, SandboxOutputOffloadPolicy| Field | Type | Default | Description |
|---|---|---|---|
sandbox | Sandbox | None | None | Shared workspace. Triggers attach at agent init. Do not combine with execution_backend, workspace_root, or sandbox_manager. |
sandbox_attach_mode | SandboxAttachMode | READ_WRITE | Access mode when attaching to sandbox. |
sandbox_mode | SandboxMode | OFF | Sandbox execution mode. See SandboxMode. |
sandbox_output_offload | SandboxOutputOffloadPolicy | None | None | Auto-register ContextOffloader for large sandbox tool outputs. |
execution_backend | Any | None | Advanced: per-agent backend template or instance (single-agent path). |
workspace_root | str | None | None | Advanced: host directory seeded into the sandbox (per-agent path). |
workspace_sync_policy | WorkspaceSyncPolicy | defaults | Include/exclude globs for seed and artifact sync. |
sandbox_manager | SandboxManager | None | None | Advanced: session manager for per-agent path. Auto-created when sandbox mode is enabled without sandbox=. |
Validation errors at Agent.__init__:
| Invalid combination | Error |
|---|---|
sandbox= + execution_backend | Dual config — use Sandbox only |
sandbox= + workspace_root | Use Sandbox(seed_from=...) |
sandbox= + sandbox_manager | Manager is owned by Sandbox |
sandbox_mode=SANDBOX without backend or sandbox= | Missing backend |
SandboxMode
python
from elsai.agent import SandboxMode| Value | Description |
|---|---|
off | No sandbox backend active. Host prebuilt tools run normally. |
sandbox | Production mode. Host execution tools (shell, file_read, file_write, python_repl, environment) are blocked — use sandbox plugin tools instead. |
local_dev | Sandbox tools active and host prebuilt tools remain available. Development only. |
SandboxAttachMode
python
from elsai.agent import SandboxAttachMode
# also: from elsai.execution import SandboxAttachMode| Value | Agent tools |
|---|---|
read_write | execute, read_file, write_file, list_dir |
read_only | read_file, list_dir only |
Read-only enforcement uses tool unregistration plus runtime hooks. Custom mutating helpers raise SandboxMutationDeniedError.
WorkspaceSyncPolicy
python
from elsai.agent import WorkspaceSyncPolicy| Field | Default | Description |
|---|---|---|
exclude_patterns | .git, node_modules, __pycache__, .venv | Glob patterns skipped when seeding into the sandbox. |
sync_include_patterns | None | Globs copied back on flush. When omitted, backends use default sync behavior. |
SandboxOutputOffloadPolicy
python
from elsai.agent import SandboxOutputOffloadPolicy| Field | Required | Default | Description |
|---|---|---|---|
storage | Yes | — | Offload backend (InMemoryStorage, FileStorage, S3Storage, …). |
max_result_tokens | No | 2500 | Offload results above this token count. |
preview_tokens | No | 1000 | Preview tokens kept in active context. |
include_retrieval_tool | No | True | Register retrieve_offloaded_content. |
tool_names | No | execute, read_file, write_file, list_dir | Tools whose outputs are eligible. |
Workspace helpers
python
from elsai.execution import (
prepare_workspace,
sync_workspace_artifacts,
close_sandbox_workspace,
sync_workspace_for_sandbox,
sandbox_is_active,
workspace_is_prepared,
WorkspaceOrchestrator,
)| Symbol | Description |
|---|---|
prepare_workspace(agent) | Create backend and seed if needed. Usually automatic via WorkspaceOrchestrator on first invocation. |
sync_workspace_artifacts(agent) | Sync artifacts from sandbox to host per agent workspace_sync_policy. |
close_sandbox_workspace(agent, sync_artifacts=True) | Optional sync, then close per-agent sandbox session. |
sync_workspace_for_sandbox(sandbox, dest=None) | Alias for sandbox.flush(dest). |
sandbox_is_active(agent) | True when sandbox_mode != off and backend is configured. |
workspace_is_prepared(agent) | True after prepare completed for this agent. |
WorkspaceOrchestrator | Hook provider — calls prepare_workspace before each agent invocation. |
Workspace ID helpers
python
from elsai.execution import workspace_id_for_run, workspace_id_for_context| Function | Returns | Use case |
|---|---|---|
workspace_id_for_run(run_id) | graph-{run_id} | Graph or workflow runs |
workspace_id_for_context(context_id, shared=False) | context_id or {context_id}:shared-ws | A2A isolated vs team workspace |
Default constants
python
from elsai.execution import (
DEFAULT_SANDBOX_SEED_EXCLUDES,
DEFAULT_SANDBOX_SYNC_INCLUDE,
default_sandbox_sync_policy,
HOST_EXECUTION_TOOL_NAMES,
SANDBOX_OFFLOAD_TOOL_NAMES,
)DEFAULT_SANDBOX_SEED_EXCLUDES
.git, node_modules, __pycache__, .venv, .env, *.pem, credentials.json, secrets/
DEFAULT_SANDBOX_SYNC_INCLUDE
src/**, tests/**, *.md, *.py, *.json, *.yaml, *.yml, dist/**, build/**
HOST_EXECUTION_TOOL_NAMES
Host tools blocked when sandbox_mode=sandbox: shell, python_repl, file_read, file_write, environment.
Other prebuilt tools (for example editor, http_request, retrieve) are not in this set.
Result types
ExecuteResult
| Field | Type | Description |
|---|---|---|
output | str | Combined stdout (and primary output text). |
exit_code | int | Process exit code. |
truncated | bool | Whether output was truncated. |
stderr | str | Stderr text when captured separately. |
success | bool (property) | True when exit_code == 0. |
FileInfo
| Field | Type | Description |
|---|---|---|
path | str | Workspace-relative path. |
is_dir | bool | Whether the entry is a directory. |
size | int | None | File size in bytes when available. |
FileTransferResult
| Field | Type | Description |
|---|---|---|
path | str | File path. |
content | bytes | None | Transferred content. |
error | str | None | Error message when transfer failed. |
success | bool (property) | True when error is None. |
WorkspaceSeedResult
| Field | Type | Description |
|---|---|---|
files_seeded | int | Number of files copied. |
bytes_seeded | int | Total bytes copied. |
skipped_paths | list[str] | Paths skipped by exclude patterns. |
errors | list[str] | Errors encountered during seeding. |
success | bool (property) | True when errors is empty. |
WorkspaceSyncResult
| Field | Type | Description |
|---|---|---|
files_synced | int | Number of files synced to host. |
bytes_synced | int | Total bytes synced. |
errors | list[str] | Errors during sync. |
success | bool (property) | True when errors is empty. |
SandboxRef
| Field | Type | Description |
|---|---|---|
workspace_id | str | Workspace identifier. |
backend_type | str | Backend class name. |
manager_id | str | None | Optional manager hint (forward-compatible). |
ManagedSandboxSession
| Field | Type | Description |
|---|---|---|
session_id | str | Workspace / session key. |
backend | Any | Live backend instance. |
prepared | bool | Whether seeding completed. |
seed_result | WorkspaceSeedResult | None | Seed summary. |
owner_id | str | None | Owning Sandbox instance for this cycle. |
created_at | float | Monotonic creation time. |
last_used_at | float | Last activity time (for TTL). |
Protocols
ExecutionBackend
Minimal backend for filesystem and workspace operations: id, create, close, read_file, write_file, list_dir, remove_file, seed_workspace, sync_artifacts, upload_files, download_files.
SandboxBackend
Extends ExecutionBackend with execute(command, timeout=None, cwd=None, **kwargs).
python
from elsai.execution import supports_execution
supports_execution(backend) # True when execute tools should registerExceptions
| Exception | When raised |
|---|---|
SandboxStartError | Second Sandbox instance claims the same workspace_id in-process. |
SandboxAttachError | Attach before start(), wrong Sandbox instance, agent already attached elsewhere, or partial attach failure. |
AgentSandboxDetachedError | Detached agent calls a mutating sandbox helper. |
SandboxMutationDeniedError | Read-only attached agent attempts execute or write_file. |
WorkspacePrepareError | Seeding or sync reports errors. |
Observability
Structured audit events are emitted to the logger elsai.sandbox.audit at INFO level for lifecycle and mutation events (sandbox.start, sandbox.add_files, sandbox.execute, sandbox.flush, sandbox.stop, attach, detach, …).
Shell command audit records command length and SHA-256 prefix — not full command text. Optional inferred_op labels (for example shell_delete) are best-effort.