Architecture: Sandbox
Strategy pattern, binary bridges internals, and the integration pipeline
This page explains the internal architecture of the sandbox system. Read this if you want to understand how the pieces fit together, extend the system with custom strategies, or debug issues.
Strategy Pattern
The Docker sandbox uses the Strategy pattern with a Template Method base class. Three concrete strategies handle different container creation approaches:
DockerSandboxStrategy (abstract)
├── RuntimeStrategy — install packages/binaries at runtime
├── DockerfileStrategy — build image from Dockerfile (cached)
└── ComposeStrategy — multi-container via docker composeTemplate Method Flow
The base class defines the creation algorithm. Subclasses override getImage() and configure():
create()
│
├─ 1. getImage() ← strategy-specific
│ ├─ Runtime: return image name (e.g., 'alpine:latest')
│ ├─ Dockerfile: build image, return cached tag
│ └─ Compose: return '' (compose manages images)
│
├─ 2. prepareVolumes() ← common: validate bind paths, inspect/create Docker volumes
│
├─ 3. startContainer() ← common: docker run -d --rm
│ └─ Compose override: docker compose up -d
│
├─ 4. configure() ← strategy-specific
│ ├─ Runtime: install packages (apk/apt), install binaries (curl)
│ ├─ Dockerfile: no-op (image already configured)
│ └─ Compose: no-op (compose file defines everything)
│
└─ 5. createSandboxMethods() ← common: return { executeCommand, spawn, readFile, writeFiles, dispose }If configure() throws, the base class auto-stops the container before re-throwing.
Container Startup
All single-container strategies start containers with:
docker run -d --rm \
--name sandbox-<uuid> \
--memory=1g --cpus=2 \
-w /workspace \
--mount type=bind,src=/host/path,dst=/container/path,readonly \
<image> [command ...]Command resolution for Runtime and Dockerfile strategies:
commandomitted (default): appendtail -f /dev/nullas a keep-alive.command: nullorcommand: []: append nothing; image/DockerfileCMD/ENTRYPOINTrun as declared.- Non-empty
command: append verbatim, overriding image/DockerfileCMD.
Commands then execute via docker exec <id> sh -c "<command>".
Dockerfile Image Caching
The DockerfileStrategy generates deterministic image tags from Dockerfile content:
Dockerfile content → SHA-256 → first 12 chars → "sandbox-a1b2c3d4e5f6"Same Dockerfile produces the same tag. Docker's layer cache handles the rest — if the image exists locally, the build is skipped entirely.
Compose Overrides
ComposeStrategy overrides three base class methods:
| Method | Standard | Compose Override |
|---|---|---|
startContainer() | docker run | docker compose up -d |
exec() | docker exec <id> | docker compose exec -T <service> |
stopContainer() | docker stop | docker compose down |
Binary Bridges
Binary bridges connect just-bash virtual environments to real host binaries. They solve three path resolution problems:
Virtual CWD to Real CWD
just-bash uses virtual paths like /home/user. Binary bridges resolve them to real host paths:
ReadWriteFs: root + cwd → path.join(fs.root, ctx.cwd)
OverlayFs: fs.toRealPath(ctx.cwd)
InMemoryFs: fallback to process.cwd()Virtual PATH to Real PATH
just-bash sets PATH=/bin:/usr/bin which doesn't include host binary locations (nvm, homebrew, etc.). Binary bridges always use process.env.PATH for binary resolution.
File Argument Detection
Arguments that look like file paths are resolved relative to the real CWD:
Detected as path: Has file extension (.md, .py), contains /, starts with .
Passed through: Starts with - (flags), no path indicatorsSecurity via allowedArgs
createBinaryBridges({
name: 'git',
allowedArgs: /^(status|log|diff|show)/,
});
// Allowed: git status, git log, git diff
// Blocked: git log --oneline (flags are also tested), git reset --hardEach argument is tested individually against the regex. Any non-matching argument returns exit code 1 with a security policy error.
Integration Pipeline
To give an AI agent a bash tool that runs inside Docker, compose the two independent systems explicitly at the call site:
1. createDockerSandbox(sandboxOptions)
└─ Returns: DockerSandbox { executeCommand, spawn, readFile, writeFiles, dispose }
2. createBashTool({ sandbox, skills, ...bashOptions })
└─ Returns: { bash, tools, sandbox, skills }The DockerSandbox interface still matches what createBashTool expects from
its sandbox parameter (executeCommand(command, options?) → { stdout, stderr, exitCode }), while also exposing Docker-only spawn(command, options?) for
streaming stdout/stderr. This is why Docker sandboxes can be plugged directly
into the shared createBashTool from @deepagents/context, which in turn
delegates to the upstream bash-tool package.
Decorator Chain Behavior
createBashTool layers decorators over the backend sandbox. Both core
decorators now preserve spawn when the backend exposes it:
withAbortSignalinjects the ambient signal fromrunWithAbortSignal(...)into bothexecuteCommandandspawnunless the call already providesoptions.signal.observeSandboxFileEventssnapshots before and afterspawnto record write/modify/delete events underdestination, and delaysexitresolution until the post-spawn snapshot is captured.
File Operations: Base64 Encoding
readFile and writeFiles use base64 encoding internally because buffered
executeCommand uses nano-spawn, which strips trailing newlines from stdout:
readFile: docker exec <id> sh -c 'base64 "/path/to/file"' → decode on host
writeFiles: echo "<base64>" | base64 -d > "/path/to/file" → decode in containerspawn bypasses nano-spawn and uses raw child-process streams so callers get
byte-accurate live output and structured exit metadata.
Extending with Custom Strategies
Subclass DockerSandboxStrategy to add new container creation approaches:
import { DockerSandboxStrategy, type DockerSandbox } from '@deepagents/context';
class KubernetesStrategy extends DockerSandboxStrategy {
protected async getImage(): Promise<string> {
return 'my-registry/my-image:latest';
}
protected async configure(): Promise<void> {
// Post-creation configuration
}
// Override exec/startContainer/stopContainer for k8s
}Next Steps
- Docker Sandbox - Usage guide for all three strategies
- Agent Wrapper - Agent integration
- Sandbox Overview - Choosing the right approach