BitMiracle-AI/Dormice
GitHub: BitMiracle-AI/Dormice
Dormice 是一个自托管的、E2B 兼容的 AI agent 沙箱平台,让沙箱在单台机器上永久存活且空闲零成本。
Stars: 279 | Forks: 26
# 睡鼠
[](https://github.com/BitMiracle-AI/Dormice/actions/workflows/ci.yml)
[](LICENSE)
**AI agent 沙箱的 SQLite** —— 一个自托管的 AI agent 沙箱平台。一台机器,永久存活的沙箱,空闲成本为零。
## 核心理念
云端沙箱平台会对沙箱存在的每一秒收费,因此它们的沙箱都是一次性的。Dormice 颠覆了这一点:你在自己已经付费的机器上运行它,沙箱就是**永久的** —— 它们闲置的时间越长,保持运行的成本就越低。
- **`acquireSandbox(userKey)` 就是全部的心智模型。** 幂等操作:相同的 key 总是会回到相同的沙箱,无论它之前处于什么状态。没有沙箱 → 创建;已冻结 → 唤醒;已停止 → 启动;已归档 → 恢复。
- **空闲即是免费。** 沙箱会自动冷却 —— `active → frozen → stopped → archived` —— 逐级降级,而任何 acquire 操作都能将它们恢复。在真实硬件上测量:冻结操作可将持有 1 GiB 内存的空闲沙箱降至约 5 MiB 的常驻内存,而唤醒它只需约 50 毫秒。
- **像单一二进制文件一样部署。** 一个守护进程,一个 SQLite 账本,一个端口。不需要 Kubernetes,也不需要外部数据库。
- **[兼容 E2B](#e2b-compatibility)**:只需更改两个 URL,官方 `e2b` SDK 即可针对 Dormice 工作。
## 安装
在纯净的 Ubuntu/Debian x86_64 主机上(以 root 身份)运行一条命令:
```
curl -fsSL https://raw.githubusercontent.com/BitMiracle-AI/Dormice/main/deploy/install.sh | bash
```
如果到常规软件源的网络连接较慢,可以添加 `-s -- --mirror cn`。
安装程序是幂等的 —— 重新运行它会升级代码并修复偏差,而且永远不会轮换你的 API token。它最后会运行 `dor doctor`,这是一系列只读检查 —— 其中三个会启动一个真实的 gVisor 容器 —— 以此决定安装是否真正成功;`dor doctor` 也可以随时单独重新运行。
## 快速开始
`@dormice/sdk` 是原生 TypeScript 客户端。(尚未发布到 npm —— 首个版本正在排队发布;在本仓库中,`pnpm build` 可以生成它。)
```
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 data
```
通信使用的是纯粹的 HTTP RPC(`POST /acquireSandbox`、`POST /execCommand` 等),因此在 SDK 无法触达的地方 `curl` 同样有效,而 `dor` CLI 负责运维端的操作:`dor sandbox ls / exec / push / pull / rebuild / destroy`,以及 `dor doctor`。完整的原生接口文档位于 [`packages/sdk`](packages/sdk/README.md),这些流程的可运行版本位于 [`examples/`](examples/)。
## Agent 技能
Dormice 的用户是 agent,因此手册以一种 agent 可以安装的形式提供 —— 即开放 [Agent Skills](https://skills.sh) 格式中的技能:
```
npx skills add BitMiracle-AI/Dormice
```
只需一个文件 [`skills/dormice/SKILL.md`](skills/dormice/SKILL.md),就能教编程 agent 掌握全部接口:如何连接,如何在 E2B SDK / 原生 API / CLI 之间进行选择,如何运行命令、移动文件以及设置生命周期策略。文档站点同样对 AI 友好 —— 其构建过程会生成 `/llms.txt`、`/llms-full.txt`,以及每个文档页面对应的纯净 markdown `.md` 副本。
## E2B 兼容性
守护进程在两个前缀(`/e2b/api`、`/e2b/envd`)上使用 E2B 协议进行通信。**官方 `e2b` 包** —— 未经修改,直接来自 npm —— 只需更改两个 URL 和一个 API-key 前缀即可针对 Dormice 运行;迁移应用程序仅仅是更改配置,而不需要修改代码:
```
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',
});
```
下方的所有内容均由黑盒 e2e 测试套件*通过官方包*进行验证,并且已经在真实的 Docker + gVisor 守护进程上测试通过:
| 接口 | 说明 |
| --- | --- |
| `Sandbox.create` / `connect` / `kill` / `list` | `list` 按状态和元数据进行过滤,并支持分页 |
| 超时 | `timeoutMs` 和 `setTimeout()` 是真实的绝对截止时间;`lifecycle: { onTimeout: 'pause' }` 会挂起沙箱而不是销毁它 |
| `pause()` / 恢复 | 显式暂停;`connect` 会将其恢复,文件保持完好 |
| `commands.run` | 实时流式传输(`onStdout` / `onStderr`),后台运行 + `connect` / `disconnect`、`sendStdin`、`kill`、`list`,支持每个命令独立的 `envs`、`user: 'root'`;非零退出码会抛出 SDK 自身的 `CommandExitError` |
| `pty.*` | `create` / `sendInput` / `resize` / `kill` —— 真正的交互式 bash |
| `files` 读取 / 写入 | 文本或字节流式传输,没有人为的大小限制 —— 磁盘配额即是上限 |
| `uploadUrl()` / `downloadUrl()` | 签名 URL:强制校验过期时间,被篡改的签名会返回 401 |
| `files.list` / `exists` / `makeDir` / `rename` / `remove` | 类型化的错误符合 SDK 的预期 |
| `files.watchDir` | 流式事件,以及 Python 同步 SDK 使用的轮询监视器 API |
| `getHost(port)` | 带有流量唤醒的端口代理(设置 `DORMICE_SANDBOX_DOMAIN`) |
| `getMetrics()` | 一次实时采样;观察永远不会唤醒已冻结的沙箱 |
与托管产品相比有意的差异:
- **没有模板构建。** 模板是命名的镜像:使用 docker 构建一个镜像,通过 `dor template add` 注册它,`Sandbox.create('name')` 就会解析它(未注册的名称会如实返回 404)。托管产品的构建流水线(`e2b template build`)未被实现。
- **冻结会保留进程。** 已冻结沙箱的进程会在执行过程中暂停并恢复 —— 任何触碰都会在约 50 毫秒内唤醒沙箱。
- **永久性仍然是默认设置。** 通过 E2B 接口创建的沙箱会像 E2B 语义要求的那样获得真实的截止时间;截止时间永远不会强加给原生创建的沙箱。
- **`metadata.externalId`** 将 `Sandbox.create` 变成了 Dormice 的幂等 acquire:相同的 key 会返回相同的沙箱,而不是创建一个新的。
- **未实现**,并在网络通信中如实返回 `unimplemented` 的功能包括:`Process/StreamInput`(JS SDK 从不调用它)和基于 xattr 的文件元数据。守护进程报告的 envd 版本为 `0.6.1`,因此 SDK 本身会在客户端侧禁用这些功能。
## Web 控制台
守护进程在 `http://127.0.0.1:3676/console` 提供了一个小型的 Web 控制台 ——
只需使用 API token 登录一次,它就会变成一个 httpOnly 会话 cookie;token 本身永远不会存储在页面可以读取的任何地方。控制台显示每个沙箱及其生命周期实时状态(与 SDK 看到的 `/listSandboxes` 相同),打开每个沙箱的详情视图,创建沙箱(同样的幂等 `acquire`,带有生命周期控制选项),释放它们,并且包含一个连接页面,其中提供了指向你自身端点的每个客户端(E2B SDK、原生 SDK、CLI)的复制粘贴代码片段。
守护进程仅监听 127.0.0.1,因此要从另一台机器访问它是你显式做出的选择,有两种方式:
- **SSH 隧道**(私有,零设置):
`ssh -L 3676:127.0.0.1:3676 root@host`,然后打开
`http://127.0.0.1:3676/console`。
- **反向代理**,同时代理控制台、API 和 E2B 接口 ——
例如 Caddy,一旦你给它分配了域名,它就会自动处理 TLS 证书:
your-domain.example {
reverse_proxy 127.0.0.1:3676 {
flush_interval -1
}
}
`flush_interval -1` 很关键:流式命令输出是逐帧写入的,缓冲代理会在最后将其变成一大块数据输出。
任何暴露在 localhost 之外的内容都应该使用 HTTPS —— API token 和会话 cookie 会包含在每一个请求中。
## 冷归档(S3,可选)
设置四个 `DORMICE_S3_*` 变量,空闲的沙箱就会迈出降级的最后一步:在停止一周后(可通过 `archiveAfterSeconds` 针对单个沙箱进行调节),磁盘数据将使用 `tar` + `zstd` 打包,发送到任何兼容 S3 的存储桶(AWS、Cloudflare R2、MinIO、处于 S3 兼容模式的阿里云 OSS),并在本地释放空间。下一次 `acquireSandbox` 会立即返回 `{ status: 'restoring', progress }`,并在磁盘数据恢复后切换为 `ready` —— 缓慢的唤醒是如实反馈的,绝不会是无声的卡死。
```
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 需要此项
```
如果未设置,该功能将切实不存在:沙箱会永远停留在 `stopped` 状态,而要求归档的策略会被拒绝,而不是被静默存储。存储桶必须存在;主机需要安装 `zstd`(`install.sh` 会安装它,`dor doctor` 会检查它)。
## 主机先决条件(docker 执行器)
守护进程本身可以在任何运行 Node 22+ 的地方运行,并为开发提供了一个内存中的模拟执行器。运行**真实的**沙箱需要一台如上所述准备好环境的 Linux 主机 —— `install.sh` 会自动完成所有工作,并且 `dor doctor` 会对其进行验证,但以下是背后的实际情况:
- **Docker + gVisor (`runsc`)**,以及 root 权限(循环挂载,cgroup 写入)。
- **Swap,且 `vm.swappiness=100`。** 冻结过程会将空闲沙箱的内存挤入 swap;gVisor 将沙箱内存作为共享内存持有,在默认的 swappiness 下内核会拒绝将其交换出去 —— 在真实硬件上测量的结果是:在默认值下回收了 0 字节,在 100 时回收了 99.5%。请注意,一些云镜像(例如阿里云)默认配置了 `vm.swappiness=0`;请使用 `sysctl vm.swappiness` 检查*有效*值,而不是查看配置文件。
- **网络强化不是可选的。** 沙箱会运行不受信任的代码:阻止容器访问云元数据服务(`iptables -I DOCKER-USER -d 169.254.0.0/16 -j DROP`,并持久化保存)并禁用容器间通信(在 `daemon.json` 中设置 `"icc": false`)。守护进程被设计为仅绑定到 127.0.0.1,故意不提供控制开关;将其暴露出去是反向代理的工作。
- **一台机器,一个守护进程。** 守护进程通过其账本旁边的锁来强制执行这一点,如果账本与机器的实际情况不匹配,则拒绝启动。
## 什么时候 Dormice 不是合适的工具
如果遇到以下情况,请选择其他工具:
- **你需要集群。** 一台机器,一个守护进程,这是设计使然 —— 这正是其简单性的来源。多机器分片是未来的方向(schema 中已经包含了相关字段),而不是当前的功能。
- **你想要托管服务。** 没有任何托管内容,也没有 SLA。那是 E2B 的产品,并且它在这方面做得很好。
- **你的威胁模型要求硬件虚拟化。** 沙箱使用的是 Docker + gVisor —— 一个用户态内核,这是刻意为之的选择:冻结要求沙箱必须是进程,而“随处安装”排除了对 KVM 的要求。如果只有 Firecracker 级别的 VM 隔离才能满足你,那么这种权衡不适合你。
- **你的工作负载不是 Linux。** 沙箱是 Linux 容器。
## 仓库布局
pnpm monorepo:
| 路径 | 它是什么 |
| --- | --- |
| `packages/shared` | 协议 schema (zod) —— 网络类型的唯一事实来源 |
| `packages/server` | 守护进程:Fastify + SQLite 账本 + 生命周期引擎 |
| `packages/sdk` | `@dormice/sdk` —— 针对原生 API 的 TypeScript 客户端 |
| `packages/cli` | `dormice` 命令行工具(简称为 `dor`) |
| `packages/console` | Web控制台:React SPA,由守护进程在 `/console` 路径提供服务 |
| `e2e` | 黑盒测试套件:启动构建好的守护进程,通过网络对其进行驱动测试 |
| `examples` | 可运行的演示:原生 SDK、官方 `e2b` 包、常驻 agent |
## 开发
```
pnpm install
pnpm build # the e2e suite boots the built daemon, so build comes first
pnpm typecheck
pnpm lint
pnpm test
```
## 许可证
[Apache-2.0](LICENSE)
## 商标声明
E2B 是其各自所有者的商标。Dormice 是一个独立项目,与 E2B 没有关联、未经其认可或赞助。对 `e2b` 包的引用仅描述了与其已发布 API 的互操作性。
标签:AI基础设施, MITM代理, 容器技术, 沙箱环境, 自动化攻击, 自托管, 请求拦截, 资源调度