---
name: dormice
description: Operate Dormice self-hosted agent sandboxes — acquire a sandbox, run commands, move files, tune lifecycle policy — through the official E2B SDKs, the native HTTP API and SDK, or the dor CLI. Use when connecting to a Dormice server, running untrusted or AI-generated code in a sandbox, giving an agent a persistent workspace, or migrating an application off E2B.
---

# Dormice

Dormice is a self-hosted sandbox platform: a gateway (the front door —
console, API keys, fleet settings) and one or more daemons (the nodes that
run the sandboxes; a single machine runs both), and sandboxes that are
**permanent** — idle ones cool down
(`active → frozen → stopped → archived`) instead of being destroyed, and any
acquire brings them back. Two facts drive every workflow below:

- **`acquireSandbox(name)` is the entire mental model.** Idempotent — the name is a unique address that never errors as a duplicate:
  the same name always returns the same sandbox, whatever state it was in.
  No sandbox → create; frozen → wake (~50 ms); stopped → start; archived →
  restore. Only `acquireSandbox` creates — other verbs answer 404 for an
  unknown name.
- **The disk, not the container, is the sandbox's body.** Files survive
  freezes, stops, daemon restarts, and host reboots. Only `destroySandbox`
  loses data.

## Connecting

Two doors, both bound to `127.0.0.1` only: the gateway on `3677` (the
console, templates, API keys, settings, and every per-sandbox verb, which
it forwards to the node) and the daemon on `3676` (the sandbox verbs plus
the list and observation verbs the gateway does not answer yet). On the
server itself use those addresses directly; from another machine the
operator has either an SSH tunnel (`ssh -L 3677:127.0.0.1:3677 -L
3676:127.0.0.1:3676 root@host`) or a reverse-proxy domain in front of the
gateway (then use `https://their-domain`). Auth is one API token (created
by the installer, hex): ask the user for the endpoint and token,
conventionally held in `DORMICE_ENDPOINT` / `DORMICE_API_TOKEN`. API keys
minted in the console (or with `dor apikey create <name>`) open the
gateway wherever the token does — same variables, revocable per client —
except the verbs that configure the fleet (keys, settings, templates,
domains), which require the token (keys cannot manage keys); a daemon
knows only the token.

## Pick an entry path

| Path | When |
| --- | --- |
| Official `e2b` SDK (npm / PyPI, unmodified) | Default for application code today, and for anything already written against E2B |
| Native HTTP API (`curl`) | Shell scripts, quick checks, any language without an SDK |
| `@dormice/sdk` (TypeScript) | Native semantics with types; first npm release is queued — inside the repo, `pnpm build` produces it |
| `dor` CLI | Operator work in a terminal: list, exec, push/pull, rebuild, doctor |

## Official E2B SDKs — change two URLs

The daemon speaks the E2B protocol on `/e2b/api` (control plane) and
`/e2b/envd` (in-sandbox). Prefix the token with `e2b_`:

```ts
import { Sandbox } from 'e2b';

const sbx = await Sandbox.create({
  apiKey: `e2b_${process.env.DORMICE_API_TOKEN}`,
  apiUrl: 'http://127.0.0.1:3677/e2b/api',
  sandboxUrl: 'http://127.0.0.1:3677/e2b/envd',
});
await sbx.commands.run('echo hello');
```

```python
import os
from e2b import Sandbox

sandbox = Sandbox.create(
    api_key=f"e2b_{os.environ['DORMICE_API_TOKEN']}",
    api_url="http://127.0.0.1:3677/e2b/api",
    sandbox_url="http://127.0.0.1:3677/e2b/envd",
)
sandbox.commands.run("echo hello")
```

`Sandbox.create` makes a fresh sandbox each time (faithful E2B semantics).
To get Dormice's idempotent acquire through the E2B surface, pass
`metadata: { name: 'my-project' }` — the same name then always returns
the same sandbox with its files intact. E2B `timeoutMs` deadlines are real:
at the deadline the sandbox is killed, or parked with
`lifecycle: { onTimeout: 'pause' }` and revived by `connect`.

## Native API — one POST route per verb

Every operation is `POST /<sdkMethodName>` with a JSON body and
`Authorization: Bearer <token>`; every non-2xx body is
`{ "message": "..." }`. Core loop with the SDK:

```ts
import { Dormice } from '@dormice/sdk';

const client = new Dormice({
  endpoint: 'http://127.0.0.1:3677',
  token: process.env.DORMICE_API_TOKEN!,
});

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'); // the only verb that loses data
```

The same loop in curl:

```sh
curl -X POST http://127.0.0.1:3677/acquireSandbox \
  -H "Authorization: Bearer $DORMICE_API_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name": "my-agent"}'
```

Verbs: `acquireSandbox` (optionally with `metadata` — string-to-string
labels stored at creation for grouping/filtering), `updatePolicy` (patch
an existing sandbox's lifecycle policy in place — no wake, no destroy),
`updateMetadata` (replace an existing sandbox's label set wholesale,
`{}` clears — same no-wake manners), `listSandboxes`,
`execCommand`, `writeFiles` / `writeFile`, `readFile` / `readFiles`,
`rebuildSandbox` (fresh container, `/home/user` kept), `destroySandbox`,
`registerTemplate` / `listTemplates` / `removeTemplate` (at the gateway;
nodes learn a template at their next check-in),
`createApiKey` / `listApiKeys` / `updateApiKey` / `revokeApiKey` (at the
gateway: revocable peers of the API token with optional expiry and a
reversible disable switch; the create response shows the key once, never
again; these four verbs accept only the token),
`getHostMetrics` (one machine's reading; at the gateway name the node
with `nodeId`, a fleet of one needs none), `getSandboxMetrics` /
`listSandboxMetrics` (live resource samples; never wake anything),
`listSandboxImages` (who still runs an old template image) — the lists
at the gateway are every node's, with `silent` naming a node it could
not include — `getFleetMetrics` / `getFleetStateHistory` (the fleet's
sums and its census over time, answered by the gateway from the nodes'
check-ins), `getConfig` / `updateSettings` (the fleet's settings at the
gateway, secrets redacted; applied by every node at its next check-in),
`getIngress` / `setIngress` (bind domains on the gateway's managed reverse
proxy), `listNodes` (every node and what it last reported).
`execCommand` takes
`{ name, command, timeoutSeconds?, cwd?, env? }` and returns
`{ exitCode, stdout, stderr, ... }` — **a non-zero exit code is a result,
not an HTTP error**. When a sandbox is coming back from S3, `acquireSandbox`
returns `{ status: 'restoring', progress }` immediately; poll it until the
status flips to `ready`.

## CLI

```sh
dor sandbox ls                        # every sandbox, with lifecycle state
dor sandbox exec my-agent 'uname -r'  # exit code passes through
dor sandbox push my-agent ./data.csv  # → /home/user/data.csv
dor sandbox pull my-agent notes.txt
dor sandbox rebuild my-agent          # fresh container, /home/user kept
dor sandbox meta my-agent app=crawler # replace labels (no args = show)
dor sandbox destroy my-agent
dor apikey create ci                  # mint a revocable key (printed once)
dor doctor                            # can this machine run sandboxes?
```

Connects via `DORMICE_ENDPOINT` and `DORMICE_API_TOKEN`.

## Files

Native file verbs move content as base64 inside JSON, capped at 16 MiB per
file (batches 48 MiB) with honest refusals — never a truncated file. Paths
are absolute or relative to `/home/user`; parent directories are created.
Past the cap: download from *inside* the sandbox (`execCommand` with `curl`,
in the stock image), or use the E2B files surface (`files.read/write`
stream uncapped; `uploadUrl()` / `downloadUrl()` mint signed URLs).

## Lifecycle policy — three knobs

Set at creation (overrides while acquiring an existing sandbox are not
applied — change an existing sandbox's policy with `updatePolicy`, a
patch that never wakes and never resets the idle clock); all count
seconds since last activity, ordering freeze ≤ stop ≤ archive:

| Knob | Default | `null` means |
| --- | --- | --- |
| `freezeAfterSeconds` | 10 minutes | — (freezing is always on) |
| `stopAfterSeconds` | 3 days | never stop |
| `archiveAfterSeconds` | 7 days when S3 is configured, else never | never archive |

The pattern Dormice was built for — one permanent sandbox per agent:

```ts
await client.acquireSandbox(`agent-${userId}`, {
  policy: { stopAfterSeconds: null },
});
```

It parks at frozen (~5 MiB resident) and wakes in ~50 ms — never a cold
start; frozen processes suspend mid-flight and resume exactly where they
were. Commands and file operations reset the idle clock; observation
(`listSandboxes`, metrics) never wakes or warms anything. A background
process does **not** keep its sandbox warm — a resident sandbox means
"ready whenever the agent returns", not an unattended 24×7 workload.

## Gotchas

- **Keep the API token hexadecimal.** The Python E2B SDK validates
  `e2b_[0-9a-f]+` client-side; other characters fail before any request.
- **Bring a patient HTTP client.** `execCommand` sends response headers
  only when the command finishes — legally hours later. Node's undici ships
  a hidden ~5-minute header timeout; disable it (the native SDK already
  does) or long commands die at exactly 300 s with an error that looks like
  the server's fault.
- **Sandboxes run untrusted code, contained.** Everything runs as a
  non-root user (uid 1000) inside gVisor, with passwordless `sudo` for
  installing system packages (E2B's convention; `sudo apt install` works,
  but survives only until the next cold wake — persistent tooling belongs
  in a template or under `/home/user`). The stock image ships Ubuntu
  24.04, Python 3.12, Node 24, git, ripgrep, and jq.

## Learn more

Docs (served as `/llms.txt`, `/llms-full.txt`, and per-page `.md` on the
project site; sources on GitHub):
[quickstart](https://github.com/BitMiracle-AI/Dormice/blob/main/website/content/docs/quickstart.mdx) ·
[lifecycle](https://github.com/BitMiracle-AI/Dormice/blob/main/website/content/docs/lifecycle.mdx) ·
[E2B SDKs](https://github.com/BitMiracle-AI/Dormice/blob/main/website/content/docs/e2b-sdks.mdx) ·
[HTTP API](https://github.com/BitMiracle-AI/Dormice/blob/main/website/content/docs/http-api.mdx) ·
[files](https://github.com/BitMiracle-AI/Dormice/blob/main/website/content/docs/files.mdx) ·
[resident agents](https://github.com/BitMiracle-AI/Dormice/blob/main/website/content/docs/resident-agents.mdx)
