rm -rf, a runaway pip install, or a model that decides to curl a dubious URL reaches straight into your machine. A sandbox is the boundary that prevents that.
Sandbox is an abstract execution environment. You create one, run commands in it, move files in and out, and close it when you are done. Because the interface is a clean abstract class, a sandbox can be backed by anything: a local Docker container, a remote machine, a microVM, a serverless runtime, your own in-house isolation technology. Motus ships two reference backends so you do not have to start from scratch:
DockerSandboxruns work inside a local Docker container. Great for local development and self-hosted deployments.CloudSandboxtalks to a remote sandbox over a REST API. Used automatically when your agent runs on Motus Cloud.
get_sandbox(...) call works in both places. You get a DockerSandbox on your laptop and a CloudSandbox when the deploy target is Motus Cloud. If neither fits, implement the Sandbox interface yourself and callers of get_sandbox() keep working unchanged.
Quick start
get_sandbox() picks the right backend, the with block tears it down on exit, and each exec/sh call returns the command’s output as a string.
Local and cloud, one call
Your code makes the sameget_sandbox(...) call in both environments. Locally it returns a DockerSandbox. When the same agent runs on Motus Cloud, it returns a CloudSandbox that talks to a sandbox the cloud side manages for you. The switch happens automatically.
Motus Cloud currently runs your agent inside a fixed, Motus-provided sandbox image.
Creating a sandbox
get_sandbox() is the recommended entry point. It manages a global provider behind the scenes, so repeated calls in the same process do not spin up redundant infrastructure.
DockerSandbox class is a direct factory with the same options plus an async variant:
Parameter reference
| Parameter | Type | Description |
|---|---|---|
image | str | Docker image to run. Default "python:3.12". |
dockerfile | str | None | Path to a directory with a Dockerfile. Built on creation. |
name | str | None | Container name. If provided, must be unique. |
env | dict[str, str] | None | Environment variables inside the container. |
mounts | dict[str, str] | None | Host-to-container bind mounts ({"/local": "/inside"}). |
ports | dict[int, int | None] | None | Container-to-host port mapping. None value picks a random host port. |
connect | str | None | Docker backend only. Attach to an existing container by name or id. When set, image and dockerfile are ignored. |
What you can do in a sandbox
Every sandbox exposes the same small surface, built onexec().
| Method | Purpose |
|---|---|
exec(*cmd, input=, cwd=, env=) | Run an arbitrary command. Returns the command output as a string (Docker and Cloud interleave stdout + stderr; LocalShell returns stdout on success and both streams on non-zero exit). |
python(script) | Shortcut for exec("python3", "-c", script). |
sh(command) | Shortcut for exec("sh", "-c", command). |
put(local_path, sandbox_path) | Copy a file in. |
get(sandbox_path, local_path) | Copy a file out. |
endpoint(port) | URL to reach a service listening on port inside the sandbox. |
Handing a sandbox to an agent
ASandbox is both a Python object you can drive yourself and a tool collection an agent can call. Two common patterns:
Pass the sandbox directly
python and sh methods and exposes them as tools (Sandbox is declared with @tools(allowlist={"python", "sh"})).
Use the full builtin_tools suite
builtin_tools(sandbox=sb) wraps the sandbox in a richer developer toolkit: bash, read_file, write_file, edit_file, glob_search, grep_search, a todo list, and (when skills_dir= is passed) load_skill. See Tools for how these fit together.
builtin_tools() with no argument and it binds to a LocalShell, which runs commands directly on the host. Convenient for prototyping; not suitable once the model is running code you did not write yourself.
Lifecycle
Context managers are the recommended path. Motus supports both sync and async:Ownership
Sandboxes created withcreate() / acreate() own the underlying container: closing them attempts to stop and remove it. Sandboxes obtained with connect(name) do not own the container and will leave it running when closed. This is what you want when your sandbox is a shared dev environment rather than a disposable workspace.
Backends
- DockerSandbox (local)
- CloudSandbox (Motus Cloud)
- LocalShell (no isolation)
The default on your laptop and on self-hosted deploys. Requires a running Docker daemon. Supports images, Dockerfiles, bind mounts, port mapping, and attach-to-existing.The first time Motus brings up the
DockerToolProvider in a process, it checks for a ghcr.io/lithos-ai/sandbox image (a Python base with common utilities) and builds it locally from the bundled Dockerfile if missing. This check only runs once per provider. Your get_sandbox(image=...) call is separate: it spins up whatever image you name.Where to go next
Tools
How the
@tool decorator, builtin_tools, and sandbox fit together.Motus Cloud
How your agents end up running on cloud sandboxes without a code change.
MCP integration
Running MCP servers inside a sandbox when you want their side effects contained.
Human in the Loop
Require approval before the agent runs commands that write to disk or reach the network.

