The SQLite of agent sandboxes — a self-hosted sandbox platform for AI agents. One machine, sandboxes that live forever, idle costs nothing.
Status: early development. The daemon, its lifecycle engine, the SDK, the CLI, the web console, the real Docker + gVisor executor, the S3 archiver, and the E2B-compatible API work end to end — the full create → freeze → stop → archive → restore cycle, command execution, file I/O, and the official
e2bSDK against real infrastructure. Nothing here is ready for production yet.
Cloud sandbox platforms charge for every second a sandbox exists, so their sandboxes are disposable. Dormice inverts that: you run it on a machine you already pay for, and sandboxes are permanent — they just get cheaper to keep the longer they sit idle.
acquireSandbox(userKey)is the entire mental model. Idempotent: the same key always comes back to the same sandbox, whatever state it was in. No sandbox → create; frozen → wake; stopped → start; archived → restore.- Idle is free. Sandboxes cool down on their own —
active → frozen → stopped → archived— one rung at a time, and any acquire brings them back. Measured on real hardware: freezing takes an idle sandbox holding 1 GiB down to ~5 MiB of resident memory, and waking it takes ~50 ms. - Deploys like a single binary. One daemon, one SQLite ledger, one port. No Kubernetes, no external database.
- E2B compatible: the official
e2bSDK works against Dormice by changing two URLs.
One command on a bare Ubuntu/Debian x86_64 host (as root):
curl -fsSL https://raw.githubusercontent.com/BitMiracle-AI/Dormice/main/deploy/install.sh | bashBehind a slow connection to the usual sources, add -s -- --mirror cn.
The installer is idempotent — re-running it upgrades the code and repairs
drift, and never rotates your API token. It ends by running dor doctor,
a battery of read-only checks — three of them boot a real gVisor
container — that decides whether the install actually succeeded;
dor doctor can be re-run on its own at any time.
@dormice/sdk is the native TypeScript client. (Not on npm yet — the
first release is queued; inside this repo, pnpm build produces it.)
import { Dormice } from '@dormice/sdk';
const client = new Dormice({
endpoint: 'http://127.0.0.1:3676',
token: process.env.DORMICE_API_TOKEN!,
});
// One key, one sandbox — created, woken or restarted as needed.
// stopAfterSeconds: null makes it a resident agent: it may freeze when
// idle (~50 ms to wake) but never cold-starts.
await client.acquireSandbox('my-agent', { policy: { stopAfterSeconds: null } });
const result = await client.execCommand('my-agent', 'python3 -c "print(6 * 7)"');
console.log(result.exitCode, result.stdout); // 0 42
await client.writeFiles('my-agent', [
{ path: 'notes.txt', content: 'survives freeze and stop' },
]);
await client.destroySandbox('my-agent'); // destroy — the only verb that loses dataThe wire is plain HTTP RPC (POST /acquireSandbox, POST /execCommand, …),
so curl works where the SDK doesn't reach, and the dor CLI covers the
operator side: dor sandbox ls / exec / push / pull / rebuild / destroy,
plus dor doctor. The full native surface is documented in
packages/sdk, and runnable versions of these
flows live in examples/.
Dormice's users are agents, so the manual comes in a form an agent can install — a skill in the open Agent Skills format:
npx skills add BitMiracle-AI/DormiceOne file, skills/dormice/SKILL.md, teaching a
coding agent the whole surface: how to connect, pick between the E2B SDKs /
native API / CLI, run commands, move files, and set lifecycle policy. The
documentation site is AI-readable too — its build emits /llms.txt,
/llms-full.txt, and a plain-markdown .md twin of every docs page.
The daemon speaks the E2B protocol on two prefixes (/e2b/api,
/e2b/envd). The official e2b package — unmodified, straight from
npm — runs against Dormice with two URLs and an API-key prefix; migrating
an application is configuration, not code:
import { Sandbox } from 'e2b';
const sbx = await Sandbox.create({
apiKey: `e2b_${process.env.DORMICE_API_TOKEN}`,
apiUrl: 'http://127.0.0.1:3676/e2b/api',
sandboxUrl: 'http://127.0.0.1:3676/e2b/envd',
});Everything below is exercised by the black-box e2e suite through the official package, and has passed against a real Docker + gVisor daemon:
| Surface | Notes |
|---|---|
Sandbox.create / connect / kill / list |
list filters by state and metadata, and paginates |
| Timeouts | timeoutMs and setTimeout() are real absolute deadlines; lifecycle: { onTimeout: 'pause' } parks the sandbox instead of killing it |
pause() / resume |
explicit pause; connect revives it, files intact |
commands.run |
live streaming (onStdout / onStderr), background + connect / disconnect, sendStdin, kill, list, per-command envs, user: 'root'; a non-zero exit raises the SDK's own CommandExitError |
pty.* |
create / sendInput / resize / kill — a real interactive bash |
files read / write |
text or bytes, streamed, no artificial size cap — the disk quota is the cap |
uploadUrl() / downloadUrl() |
signed URLs: expiry enforced, a tampered signature is a 401 |
files.list / exists / makeDir / rename / remove |
typed errors match the SDK's expectations |
files.watchDir |
streamed events, and the polling watcher API the Python sync SDK uses |
getHost(port) |
port proxy with wake-on-traffic (set DORMICE_SANDBOX_DOMAIN) |
getMetrics() |
one live sample; observing never wakes a frozen sandbox |
Deliberate deltas from the hosted product:
- No template builds. Templates are named images: build one with
docker, register it with
dor template add, andSandbox.create('name')resolves it (an unregistered name is an honest 404). The hosted product's build pipeline (e2b template build) is not implemented. - Freezing keeps processes. A frozen sandbox's processes suspend and resume mid-flight — any touch wakes the sandbox in ~50 ms.
- Permanence stays the default. Sandboxes created through the E2B surface get real deadlines, as E2B semantics demand; deadlines are never imposed on natively created sandboxes.
metadata.externalIdturnsSandbox.createinto Dormice's idempotent acquire: the same key returns the same sandbox instead of a new one.- Not implemented, answered with an honest
unimplementedon the wire:Process/StreamInput(the JS SDK never calls it) and xattr-based file metadata. The daemon reports envd0.6.1, so the SDK itself gates those features client-side.
The daemon serves a small web console at http://127.0.0.1:3676/console —
sign in with the API token once and it becomes an httpOnly session cookie;
the token itself is never stored anywhere the page can read. The console
shows every sandbox with its live lifecycle state (the same
/listSandboxes the SDK sees), opens a per-sandbox detail view, creates
sandboxes (the same idempotent acquire, with the lifecycle knobs),
releases them, and has a Connect page with copy-paste snippets for every
client (E2B SDK, native SDK, CLI) pointed at your own endpoint.
The daemon listens on 127.0.0.1 only, so reaching it from another machine is a choice you make explicitly, one of two ways:
-
SSH tunnel (private, zero setup):
ssh -L 3676:127.0.0.1:3676 root@host, then openhttp://127.0.0.1:3676/console. -
Reverse proxy for the console, the API, and the E2B surface at once — e.g. Caddy, which also handles TLS certificates automatically once you give it a domain:
your-domain.example { reverse_proxy 127.0.0.1:3676 { flush_interval -1 } }flush_interval -1matters: streamed command output is written frame by frame, and a buffering proxy would turn it into one lump at the end. Anything exposed beyond localhost should be HTTPS — the API token and the session cookie travel in every request.
Set the four DORMICE_S3_* variables and idle sandboxes take the last
step down: a week after stopping (tunable per sandbox via
archiveAfterSeconds), the disk is packed with tar + zstd, shipped to
any S3-compatible bucket (AWS, Cloudflare R2, MinIO, Alibaba OSS in
S3-compat mode), and freed locally. The next acquireSandbox answers
{ status: 'restoring', progress } immediately and flips to ready once
the disk is back — slow wake-ups are honest, never a silent hang.
DORMICE_S3_ENDPOINT=https://s3.example.com # full URL; MinIO speaks http
DORMICE_S3_BUCKET=dormice-archive
DORMICE_S3_ACCESS_KEY_ID=...
DORMICE_S3_SECRET_ACCESS_KEY=...
# DORMICE_S3_REGION=us-east-1
# DORMICE_S3_FORCE_PATH_STYLE=true # MinIO needs thisUnset, the feature is honestly absent: sandboxes park at stopped
forever, and a policy asking to archive is refused rather than silently
stored. The bucket must exist; the host needs zstd (install.sh
installs it, dor doctor checks it).
The daemon itself runs anywhere Node 22+ runs, with an in-memory fake
executor for development. Running real sandboxes needs a Linux host
prepared as above — install.sh automates all of it and dor doctor
verifies it, but these are the facts underneath:
- Docker + gVisor (
runsc), and root (loop mounts, cgroup writes). - Swap, and
vm.swappiness=100. Freezing squeezes an idle sandbox's memory out to swap; gVisor holds sandbox memory as shared memory, which the kernel refuses to swap at the default swappiness — measured on real hardware: 0 bytes reclaimed at the default, 99.5% reclaimed at 100. Note that some cloud images (Alibaba Cloud, for one) shipvm.swappiness=0; check the effective value withsysctl vm.swappiness, not the config files. - Network hardening is not optional. Sandboxes run untrusted code:
block the cloud metadata service from containers
(
iptables -I DOCKER-USER -d 169.254.0.0/16 -j DROP, persisted) and disable inter-container traffic ("icc": falseindaemon.json). The daemon binds to 127.0.0.1 only, by design without a knob; exposing it is a reverse proxy's job. - One machine, one daemon. The daemon enforces this with a lock next to its ledger and refuses to start when its ledger and the machine's reality cannot belong together.
Pick something else if:
- You need a fleet. One machine, one daemon, by design — that is where the simplicity comes from. Multi-machine sharding is a future direction (the schema already carries the fields), not a current feature.
- You want a managed service. No hosted anything, no SLA. That is E2B's product, and it is good at it.
- Your threat model demands hardware virtualization. Sandboxes are Docker + gVisor — a userspace kernel, chosen deliberately: freezing requires sandboxes to be processes, and "installs anywhere" rules out requiring KVM. If only Firecracker-class VM isolation will do, this trade is not for you.
- Your workloads aren't Linux. Sandboxes are Linux containers.
pnpm monorepo:
| Path | What it is |
|---|---|
packages/shared |
Protocol schemas (zod) — the single source of truth for wire types |
packages/server |
The daemon: Fastify + SQLite ledger + lifecycle engine |
packages/sdk |
@dormice/sdk — TypeScript client for the native API |
packages/cli |
dormice command-line tool (dor for short) |
packages/console |
Web console: React SPA, served by the daemon at /console |
e2e |
Black-box suite: boots the built daemon, drives it over the wire |
examples |
Runnable demos: the native SDK, the official e2b package, a resident agent |
pnpm install
pnpm build # the e2e suite boots the built daemon, so build comes first
pnpm typecheck
pnpm lint
pnpm testE2B is a trademark of its respective owner. Dormice is an independent project and is not affiliated with, endorsed by, or sponsored by E2B. References to the e2b package describe interoperability with its published API only.