Sandboxes¶
A sandbox is an isolated execution environment (a Docker container, a Kubernetes pod, or a native process) where an agent's tool calls actually run. The goal is a hard boundary: the LLM can propose any command, but execution stays confined to a sandbox with its own filesystem, network, and resource limits.
Source lives under src/opensage/sandbox/.
Two Orthogonal Axes¶
The sandbox system deliberately separates two concerns:
- Backend: where and how a container runs. One of
native(local Docker),podman,remotedocker,opensandbox,nitrobox,local(no container), ork8s. Each backend subclassesBaseSandbox(base_sandbox.py) and implements the container or pod lifecycle. - Initializer: what gets installed inside. A built-in set under
sandbox/initializers/configures a particular sandbox type (main,joern,codeql,neo4j,gdb_mcp,coverage,fuzz) by running setup commands after the container is up.
Because these axes are independent, the same sandbox type runs identically on every backend, and you can swap backends (local dev -> remote Docker -> k8s in CI) without touching the per-sandbox setup code.
Many Sandboxes per Session¶
A single session can own many sandboxes at once. For example, a PoC generation agent typically launches:
| Sandbox | Purpose |
|---|---|
main |
Where the agent edits source, runs builds, drops PoC inputs. |
neo4j |
A Neo4j container serving the code-property graph for static analysis. |
gdb_mcp |
A sidecar running the GDB MCP server; exposed on sse_port = 1111. |
joern |
Joern for CPG generation. |
The [sandbox.sandboxes.<name>] section in your config.toml declares each one. Tools pick their sandbox by name, so a GDB tool knows to execute against gdb_mcp, not main.
Lifecycle¶
# During session startup
session.sandboxes.initialize_shared_volumes()
await session.sandboxes.launch_all_sandboxes()
await session.sandboxes.initialize_all_sandboxes(continue_on_error=True)
- Shared volumes created. The
/sharedbind mount is populated with the project's code / data. - Containers launched. All sandboxes referenced by the agent (after pruning) are started in parallel.
- Initializers run. Each container runs its initializer (apt installs, venv creation, MCP-service start-up, and so on).
- MCP readiness polled. A sandbox hosting an MCP service (
gdb_mcpor a custom service) stays out of theREADYstate until_check_mcp_connections()pings the SSE endpoint successfully. - Ready. The agent can now use tools that target this sandbox.
continue_on_error=True is the default; if one sandbox fails to initialize, the others still come up. Makes debugging easier, at the cost of the agent having to discover missing capabilities lazily.
Sandbox Dependencies¶
Tools declare which sandbox(es) they need via @requires_sandbox(...):
from opensage.toolbox.sandbox_requirements import requires_sandbox
@requires_sandbox("gdb_mcp", "main")
def step_into(tool_context, ...):
...
Skills declare the same thing in their SKILL.md:
collect_sandbox_dependencies() walks the agent tree at session startup, gathers every declared dependency, and prunes the sandbox list to just those. main is always included.
Shared Storage¶
Shared mount points that are present in every sandbox by default:
/shared: populated from the session's shared-data path (project_relative_shared_data_pathorabsolute_shared_data_pathin[sandbox]). This is where target code, PoC inputs, and submission scripts live./sandbox_scripts: read-only framework scripts used by initializers and sandbox tools./bash_tools: read-only staged bash skills selected byenabled_skills.
Per-sandbox mounts (volumes, mounts) are additive on top of these.
Cleanup¶
On shutdown:
- Containers are stopped (backend-dependent).
- If
auto_cleanup = truein the root config, shared volumes and per-sandbox volumes are deleted. - Networks / namespaces created for this session are removed.
Related References¶
[sandbox]configuration reference: every field.- Sandbox system guide: internals, backend implementation.
- Adding a Sandbox and Adding a Sandbox Backend: extension points.