Sandbox#
Sandbox is where OpenRath runs tool side effects. When the model requests a tool, the runtime finds the active sandbox through Session, then the backend executes command, file, or code payloads.
This page explains backend registration, how Session binds a sandbox, lifecycle differences between local and OpenSandbox, directory mapping, and backend selection.
The diagram below is the execution boundary for this page. A session records the target backend, the registry resolves an implementation, and the sandbox executes typed payloads behind that boundary.
Backend execution is intentionally separated from model-facing tools: tools produce typed payloads, and the selected backend decides how those payloads run.#
Overview#
Session stores execution placement, Backend opens and executes work, and BackendSandbox is the handle for one opened runtime.
Layer |
Object |
Responsibility |
|---|---|---|
Choose backend |
|
Records execution placement. |
Open runtime |
|
Creates a local directory or container sandbox. |
Execute payload |
|
Runs command, filesystem, and code payloads. |
Release resources |
|
Closes the handle and releases the directory or container. |
This layering lets the same FlowToolCall run on different backends. The tool builds a backend payload; local and OpenSandbox decide how that payload is executed.
Source map#
File |
Responsibility |
|---|---|
|
|
|
Backend registry, default backend, preferred selection. |
|
host-side local backend. |
|
optional OpenSandbox backend. |
|
backend payload dataclasses. |
|
backend result dataclasses. |
|
stream/event/future concurrency helpers. |
Backend Abstraction#
All backends implement the same abstract interface:
Method |
Purpose |
|---|---|
|
Checks whether this backend can be used in the current environment. |
|
Returns isolation level and capability details. |
|
Returns supported backend payload types. |
|
Opens a sandbox and returns |
|
Closes the sandbox and releases resources. |
|
Executes a backend payload. |
BackendSandboxSpec holds optional settings for opening a sandbox:
Field |
Purpose |
|---|---|
|
Image name for container backends. |
|
Container entrypoint. |
|
Environment variables passed when opening the sandbox. |
|
Sandbox lifecycle or operation timeout. |
|
Workspace directory; local uses it directly, OpenSandbox tries to bind it to |
Backend registry#
Backends are registered with @register(name). The public API lives in rath.backend.
from rath.backend import get, is_available, list_names, preferred
print(list_names())
print(is_available("opensandbox"))
backend = preferred(["opensandbox", "local"])
The main backends are:
Backend |
Availability |
Isolation level |
Main capabilities |
|---|---|---|---|
|
Automatically registered after importing |
|
command, filesystem, code interpreter |
|
Optional extra is installed and environment variables or |
|
command, filesystem, code interpreter |
preferred([...]) returns the first registered backend whose is_available() is true. It is useful for development scripts that prefer OpenSandbox and fall back to local when the environment is not configured.
How Session Binds Sandbox#
Session can record only a target, or it can bind an already-open handle.
from rath.session import Session
session = Session.from_user_message("List files.").to("local")
with session:
sandbox = session.require_sandbox()
print(sandbox.backend.name)
Lifecycle order:
Stage |
Method |
Behavior |
|---|---|---|
target |
|
Records the backend name and open spec. |
lazy open |
|
Opens a sandbox handle from the target and adds one reference. |
share |
|
Loop / |
close |
|
Drops one reference. When the count reaches zero, the backend closes the handle. |
BackendSandbox._refcount tracks live references. Every Session.sandbox slot, every with sandbox: block, and every explicit sb.acquire() counts as one. There is no force-close path: the backend’s close(sb) runs only when the final reference is released. In v1.2, registry get(name) returns a process-level backend singleton, and refcount updates are protected by a lock so concurrent release paths cannot double-close the same handle.
run_session_loop(...) and run_session_compress(...) share the user session’s sandbox with the returned session (refcount + 1). Both sessions end up holding the same handle; either side can close_sandbox() independently.
local backend#
LocalBackend executes payloads on the current machine. It is always available and fits development, unit tests, and trusted workloads.
Behavior |
Current implementation |
|---|---|
open |
Uses |
relative path |
Resolves relative paths from the working directory pointed to by the sandbox handle. |
absolute path |
Uses the absolute path as provided. |
command |
Uses |
code |
Writes a temporary Python file and runs it with the current Python interpreter. |
close |
Marks closed and calls |
The main risk in the local backend is close: it deletes the directory referenced by the handle. If a real project directory is used as working_dir, treat it as a rebuildable workspace.
from rath.backend import BackendToolFilesWrite
from rath.session import Session
session = Session.from_user_message("write").to("local")
with session:
result = session.require_sandbox().dispatch(
BackendToolFilesWrite(path="note.txt", data="hello")
)
print(result.bytes_written)
OpenSandbox backend#
OpenSandboxBackend uses the optional opensandbox SDK and code_interpreter package to map backend payloads to the OpenSandbox API.
Behavior |
Current implementation |
|---|---|
availability |
Checks SDK, code interpreter, |
API reachability |
|
default image |
|
default entrypoint |
|
workspace root |
|
async bridge |
SDK async calls run on a dedicated event loop thread and expose a blocking API outward. |
OpenSandbox resolves working_dir to a host path and requests a bind mount at /workspace inside the container. That host path must be visible from the machine running the OpenSandbox server.
from rath.session import Session
session = Session.from_user_message("List workspace.")
session.to("opensandbox", spec=".")
The OpenSandbox server checks storage.allowed_host_paths in .sandbox.toml to decide whether a host path can be bound. The repository startup script adds the current project directory to the allowlist; if the server is started manually or another directory is bound, update that list as well. If the server rejects the host bind, OpenRath retries with an empty workspace by default. Set RATH_OPENSANDBOX_STRICT_WORKSPACE_BIND=1 to fail immediately on bind rejection, which makes configuration problems easier to find.
Choosing local Or OpenSandbox#
Scenario |
Backend |
Reason |
|---|---|---|
Writing docs tutorials or running unit tests |
|
Starts quickly, has few dependencies, and is easy to inspect. |
Debugging tool schemas and session loop |
|
Files, stdout, and stderr are easy to observe. |
Need a container environment or dependency isolation |
|
Tool side effects happen in a container workspace. |
Validating OpenSandbox integration |
|
Covers SDK, server, workspace bind, and code interpreter. |
Handling untrusted workloads |
|
local uses host-side subprocesses and filesystem access. |
These choices reflect the current implementation. local has isolation level PROCESS; OpenSandbox has isolation level CONTAINER.
Payload Dispatch Matrix#
Payload |
local |
opensandbox |
|---|---|---|
|
|
|
|
local filesystem read |
OpenSandbox filesystem read |
|
local filesystem write |
OpenSandbox filesystem write |
|
local directory listing |
OpenSandbox filesystem search/list |
|
|
OpenSandbox filesystem lookup |
|
temporary Python script |
|
OpenSandbox currently returns an unsupported failure for BackendToolCommandRun.stdin. BackendToolCodeRun.language supports only bash, go, java, javascript, python, and typescript.
Stream API#
BackendSandbox.stream() can organize backend payloads on the same sandbox.
Behavior |
Semantics |
|---|---|
same stream |
FIFO queue, one worker thread, sequential execution. |
different streams |
Different worker threads can make progress concurrently. |
event |
|
synchronize |
Waits for already-submitted operations in the current stream to finish. |
The session loop’s private async runtime may run model-returned tool calls concurrently when tools expose distinct resource_key(...) values. Backend streams remain useful for manually written backend-level concurrent flows.
Health Check And Validation#
After the OpenSandbox server starts, first check the control plane:
curl -fsS http://127.0.0.1:8080/health
The health check only proves that the server responds. The OpenRath example also validates the backend client, container runtime, and workspace bind:
python example/03_sandbox_backend.py opensandbox
This covers OpenRath client configuration, sandbox open, command/file/code payloads, and workspace bind behavior.
Edge Cases#
Behavior |
Current implementation |
|---|---|
|
Raises |
backend-level dispatch on a closed sandbox |
Returns |
unsupported payload |
Returns |
local close |
Deletes the directory pointed to by the sandbox handle. |
local absolute path |
Uses the absolute path as provided. |
local command timeout |
Returns |
OpenSandbox bind rejected |
Retries with empty |
OpenSandbox stdin |
Returns unsupported failure. |
OpenSandbox unsupported language |
Returns |
Code Reading Checkpoints#
Question |
Where to look |
|---|---|
Backend abstract interface |
|
Backend registration and selection |
|
Whether local close deletes the directory |
|
OpenSandbox bind fallback |
|
Payload types |
|
Result types |
|
Stream behavior |
|
Test Coverage#
Behavior |
Tests |
|---|---|
local lifecycle |
|
opensandbox lifecycle |
|
command payload |
|
file payloads |
|
code payload |
|
stream/event |
|
backend registry |
|
opensandbox bind fallback |
|