Skip to content

Sandbox — Overview

Sandbox execution gives agents a separate workspace for shell commands and file operations — without running the agent loop or API keys inside that environment.

Use it when agents need to run tests, edit code, install packages, or inspect project files safely. For multiple agents collaborating on one codebase, attach them to the same Sandbox so they share one filesystem while keeping separate conversations.

How it works

Sandbox-as-tool: The LLM and business tools (HTTP, retrieve, search, …) stay on the host. Only delegated workspace operations — execute, read_file, write_file, list_dir — run inside the backend.

On the first agent invocation, WorkspaceOrchestrator automatically prepares the workspace (create backend, seed project files if configured).

Core concepts

ConceptDescription
Sandbox resourceApplication-owned object with lifecycle (start, stop, flush). Agents attach via AgentConfig(sandbox=...).
Shared workspaceFiles and command side effects in one environment; each agent keeps its own messages and LLM context.
SandboxPlugin toolsexecute, read_file, write_file, list_dir — registered when a backend is active. Auto-added on attach if missing.
Execution backendWhere commands run — local host dir (dev), AgentCore VM, or Daytona workspace. See Backends.
Host vs sandbox toolsIn production SandboxMode.SANDBOX, host prebuilt tools (shell, file_read, …) are blocked; use sandbox plugin tools instead.

Quick start — single agent

python
import asyncio
from pathlib import Path
from uuid import uuid4

from elsai import Agent
from elsai.agent import AgentConfig
from elsai.execution import LocalSandboxBackend, Sandbox, workspace_id_for_run
from elsai.plugins.sandbox import SandboxPlugin

HOST_REPO = Path("./my-repo")
SANDBOX_ROOT = Path("/tmp/elsai-sandbox-ws")

async def main() -> None:
    async with Sandbox(
        workspace_id=workspace_id_for_run(str(uuid4())),
        backend=LocalSandboxBackend(SANDBOX_ROOT),
        ttl_seconds=3600,
        seed_from=HOST_REPO,
    ) as sandbox:
        agent = Agent(
            config=AgentConfig(
                sandbox=sandbox,
                plugins=[SandboxPlugin()],
            ),
        )
        result = await agent.invoke_async("List the project files and summarize the README")
        print(result.message)

asyncio.run(main())

What this shows:

PieceConcept
async with Sandbox(...)Lifecycle — start on enter, flush + stop on exit
seed_fromCopy host project into workspace at start
AgentConfig(sandbox=sandbox)Auto-attach agent to shared workspace
SandboxPluginRegisters workspace tools on the agent

Local backend

LocalSandboxBackend runs on the host with no isolation. Use AgentCore or Daytona for production.

Inject files before the agent runs

python
async with Sandbox(...) as sandbox:
    await sandbox.add_files({
        "TASK.md": "Fix the authentication bug described below.\n",
        "notes/context.txt": "Priority: refresh token handling.\n",
    })

    agent = Agent(config=AgentConfig(sandbox=sandbox, plugins=[SandboxPlugin()]))
    await agent.invoke_async("Read TASK.md and write a plan to notes/plan.md")

What this shows:

PieceConcept
add_files()Orchestrator-side upload — no agent turn required
Agent reads/writes via plugin toolsFiles live in the shared workspace

Sandbox tools

When a backend is active, SandboxPlugin registers:

ToolDescription
executeRun a shell command in the workspace
read_fileRead a text file (optional line offset and limit)
write_fileWrite a text file
list_dirList a directory

The execute tool is omitted when the backend does not support shell execution.

Production vs local development

SandboxMode controls host prebuilt tool access:

ModeSandbox plugin toolsHost tools (shell, file_read, file_write, python_repl, environment)
offNot activeAllowed
sandboxActiveBlocked in production
local_devActiveAllowed — development convenience

HostExecutionGuard also blocks dynamically loaded host execution tools at runtime.

Other prebuilt tools such as editor, http_request, and retrieve are not in the blocked set unless you restrict them separately.

How this relates to prebuilt tools

MechanismPackageRole
Sandbox + SandboxPluginelsai-agentsWorkspace execution (this guide)
shell, file_read, …elsai-agents-toolsHost tools — blocked when sandbox_mode=sandbox
code_interpreterelsai-agents-toolsLegacy AgentCore code sessions — prefer Sandbox + backend for full workspaces

Advanced — per-agent backend

For single-agent flows or tests without a shared Sandbox facade:

python
from elsai.agent import AgentConfig, SandboxMode
from elsai.execution import LocalSandboxBackend, SandboxManager
from elsai.plugins.sandbox import SandboxPlugin

AgentConfig(
    execution_backend=LocalSandboxBackend("/tmp/ws"),
    sandbox_mode=SandboxMode.LOCAL_DEV,
    workspace_root="./my-repo",
    sandbox_manager=SandboxManager(),
    plugins=[SandboxPlugin()],
)

Do not combine this with AgentConfig(sandbox=...). Setting sandbox_mode=local_dev together with sandbox= raises ValueError at construction — shared attach always uses production host-tool blocking. See Shared workspace — Configuration validation.

Timeouts and TTL

Sandbox uses several timeout knobs — they are not interchangeable.

SettingWhereWhat it controls
ttl_secondsSandbox(...) (required)Workspace idle TTL — how long a managed session may sit unused before SandboxManager.cleanup_expired() can close it.
session_timeout_secondsAgentCoreSandboxTemplateRemote AgentCore code-interpreter session lifetime (default 900).
default_timeoutDaytonaSandboxTemplatePer-command wall-clock limit in Daytona (default 900; 0 = poll until the command exits).
timeoutLocalSandboxBackendPer-command limit for host subprocesses (default 120).

ttl_seconds (workspace idle)

  • Required on every Sandbox(...) — pick a value for your deployment (examples use 36007200).
  • Primary teardown is async with Sandbox(...) or explicit stop(). TTL is a backstop when a run crashes or stop() is never called.
  • Suggested formula: longest_expected_run_duration + margin (for example a 90-minute graph → ttl_seconds=7200).
  • Activity on the workspace resets the idle clock (last_used_at on the managed session).
  • Expiry is applied when your application calls await sandbox.manager.cleanup_expired() (or the same on a shared SandboxManager). There is no built-in background sweeper — long-running services should call it on a schedule if you rely on TTL cleanup.
  • Set SandboxManager(ttl_seconds=None) only when constructing a custom manager to disable idle expiry checks.

session_timeout_seconds, default_timeout, and timeout are separate — they limit individual remote sessions or shell commands, not workspace idle time.

Limitations (v1)

  • In-process sharing only — share the same Python Sandbox object; the same workspace_id string in two processes creates two separate workspaces silently.
  • No agent delete/move tools — agents use execute("rm …") or orchestrator Sandbox.delete_files.
  • Sequential multi-agent turns recommended — parallel mutating graph nodes on one sandbox are unsupported.
  • Cross-process workspace sharing is not supported.

Copyright © 2026 elsai foundry.