Microsandbox sandbox
Execute OCI images in local hardware-isolated microVMs without Docker daemon coupling
The Microsandbox backend runs commands inside local hardware-isolated microVMs
through the optional microsandbox
SDK. It implements the same DisposableSandbox contract as the other
DeepAgents backends: executeCommand, spawn, readFile, writeFiles, and
dispose.
Use it when you want standard OCI images and real process streaming, but do not
want your app coupled to a Docker daemon. The backend boots a microVM from an
image, creates the working directory, and returns a sandbox that can plug
directly into createBashTool.
Microsandbox is an optional peer dependency. It requires Node.js 22+ and hardware virtualization: Apple silicon, Linux with KVM, or Windows with WHP.
Installation
npm install microsandbox@^0.7.8If the package or runtime is unavailable, createMicrosandboxSandbox() throws
MicrosandboxNotAvailableError. This includes an incomplete runtime installation.
Basic usage
import { createMicrosandboxSandbox } from '@deepagents/context';
await using sandbox = await createMicrosandboxSandbox();
const result = await sandbox.executeCommand('echo "Hello from Microsandbox"');
console.log(result.stdout);The default image is bash:5.3-alpine3.24. The default working directory is
/workspace, and the factory creates it during boot because common images do
not ship that directory.
AI agent integration
import {
MICROSANDBOX_DEFAULT_DESTINATION,
createBashTool,
createMicrosandboxSandbox,
} from '@deepagents/context';
const backend = await createMicrosandboxSandbox({
name: 'chat-123',
configure: (builder) => builder.image('node:lts-bookworm-slim'),
});
const sandbox = await createBashTool({
sandbox: backend,
workingDirectory: MICROSANDBOX_DEFAULT_DESTINATION,
});Pass the sandbox.tools object to an agent the same way you would with Docker,
Apple Container, Daytona, or a virtual sandbox.
Named sandboxes
Pass name when a chat or workflow should resume the same microVM:
const sandbox = await createMicrosandboxSandbox({
name: 'analytics-session',
configure: (builder) => builder.image('python:3.12-slim').memory(2048),
});Named creation uses get-or-create semantics:
- If a sandbox with that name is running, the factory connects to it.
- If it exists but is stopped, the factory starts it and preserves rootfs state.
- If it does not exist, the factory creates it.
dispose() stops a named sandbox but does not remove it, so the next
createMicrosandboxSandbox({ name }) can resume it. For a fresh named sandbox,
pass replace: true.
Unnamed sandboxes are ephemeral: the factory generates a name, and dispose()
stops and removes the microVM.
Options
| Option | Type | Description |
|---|---|---|
name | string | Stable sandbox name for get-or-create reuse |
workdir | string | Default working directory; defaults to /workspace |
replace | boolean | Replace an existing named sandbox instead of attaching |
commandTimeout | number | Per-command timeout for executeCommand, in milliseconds |
readiness | (sandbox) => void | Promise<void> | Hook that must pass before the factory returns |
configure | (builder) => builder | Fluent access to the SDK builder: image (defaults to bash:5.3-alpine3.24), cpus, memory, env, volumes, network policy, secrets, user, idle timeout |
The options are the inputs the factory itself reads. Everything else is set
fluently on the SDK builder through configure, which runs on every connect;
the SDK applies builder options only when creating, so an existing named
sandbox keeps its configuration.
The factory applies workdir, the persistence implied by name, and the
Bash shell after configure. A named sandbox therefore stays persistent even
if configure calls ephemeral(true), and the factory's workdir wins over
one set in configure.
Command execution APIs
executeCommand is buffered and accepts an optional AbortSignal:
const controller = new AbortController();
const pending = sandbox.executeCommand('sleep 30', {
signal: controller.signal,
});
setTimeout(() => controller.abort(), 1_000);
await pending;For live byte streams, use spawn:
const proc = await sandbox.spawn?.('node long-task.js', { cwd: '/workspace' });
if (!proc) throw new Error('spawn is unavailable');
const decoder = new TextDecoder();
for await (const chunk of proc.stdout) {
process.stdout.write(decoder.decode(chunk, { stream: true }));
}
const exit = await proc.exit;executeCommand lowers onto the same streaming path as spawn, so cancellation
kills the guest process instead of abandoning it.
Readiness hooks
Use readiness when the image needs a daemon or service started before the
agent gets the sandbox:
const sandbox = await createMicrosandboxSandbox({
name: 'daemon-backed-chat',
configure: (builder) => builder.image('my-daemon-image:0.1.0'),
readiness: async (sandbox) => {
const result = await sandbox.executeCommand('start-daemon && wait-ready');
if (result.exitCode !== 0) throw new Error(result.stderr);
},
});If readiness throws, the factory disposes the sandbox and rethrows the error.
Image cache demo
The Text2SQL demo shows the local image-cache flow:
node demo/microsandbox-text2sql/bootstrap.ts
node demo/microsandbox-text2sql/demo-microsandbox.tsbootstrap.ts exports the shared daemon image from Buildx as an OCI archive and
loads it directly into the local Microsandbox cache with the SDK's
Image.load(). The demo then uses
createMicrosandboxSandbox({ configure }) with image(...) and pullPolicy('never') so a
missing local image fails clearly instead of trying a registry pull.
Error handling
All package-owned errors extend MicrosandboxSandboxError:
| Error | When |
|---|---|
MicrosandboxNotAvailableError | The optional dependency is unavailable, or the runtime is missing or incomplete |
MicrosandboxCreationError | Sandbox creation fails outside a known SDK error |
MicrosandboxCommandError | File reads or writes through the VM filesystem fail |
The adapter wraps the SDK's LibkrunfwNotFoundError, RuntimeNotInstalledError,
and RuntimeIncompleteError in MicrosandboxNotAvailableError, retaining the
SDK error in cause. Other MicrosandboxError subclasses pass through unchanged,
so callers can handle those SDK failure classes directly.
Next steps
- Sandbox Overview - choosing the right approach
- Docker Sandbox - full container isolation through Docker
- Apple Container Sandbox - lightweight VMs on Apple silicon
- Architecture: Sandbox - how sandbox backends compose with
createBashTool