Architecture¶
flowchart LR
subgraph Clients
A[Agent or app] -->|Python SDK| API
M[MCP client] -->|stdio| MCP[agent-sandbox-mcp]
MCP -->|HTTP| API
C[curl] -->|HTTP| API
end
subgraph Server["agent-sandbox server"]
API[FastAPI routes<br/>auth, validation, NDJSON streaming]
MGR[SandboxManager<br/>quotas, TTL reaper, snapshots]
STORE[(data dir<br/>records + snapshot tarballs)]
IF{{Backend interface}}
API --> MGR
MGR --> STORE
MGR --> IF
end
IF --> DOCKER[DockerBackend<br/>Docker Engine API]
IF -.-> FC[Firecracker<br/>roadmap]
IF -.-> GV[gVisor runtime<br/>roadmap]
DOCKER --> S1[sandbox container<br/>+ workspace volume]
DOCKER --> S2[sandbox container<br/>+ workspace volume]
- API layer (
api.py) validates requests with Pydantic, checks the API key, and maps errors to HTTP status codes. - Manager (
manager.py) owns IDs, quotas, TTLs, locking, and snapshot storage. It persists sandbox records so a restarted server picks up its sandboxes and removes orphans. - Backend (
backends/base.py) is the interface an isolation technology implements: create, destroy, exec, file I/O, and workspace export and import as tar streams. - Docker backend (
backends/docker.py) implements that interface with hardened containers.
A snapshot is a tar of /workspace stored in the data directory. Rollback
destroys the container, creates a fresh one with the same spec, and extracts the
tar into the new workspace. Fork does the same into a new sandbox ID.
Request flow¶
sequenceDiagram
participant Agent
participant API as FastAPI routes
participant Manager as SandboxManager
participant Backend as DockerBackend
Agent->>API: POST /v1/sandboxes/{id}/snapshots
API->>API: check the API key
API->>Manager: snapshot(id)
Manager->>Backend: export_workspace(id)
Backend-->>Manager: tar stream of /workspace
Manager->>Manager: write the tarball and the record to the data dir
Manager-->>API: SnapshotInfo
API-->>Agent: 201 {"id": "snap_..."}
The decision records explain why the pieces look the way they do.