E2B 云沙箱
一句话版:
e2b是一组实验性 provider 组合 POC——把一整套"文件系统 + 子进程"执行世界放进 E2B 的 Linux 沙箱。E2B 只提供沙箱生命周期和两个底层 OS 适配器(fs-e2b/subprocess-e2b),与 provider 无关的消费者在这之上搭更高层能力。
它不搬 harness 进程本身,只把"可变的执行世界"挪进远端沙箱。
一、是什么
这个 POC 的目标是可移植执行世界:让文件工具、Bash、PTY、LSP 跑在同一个 E2B Linux 沙箱里。E2B 只负责两件事——沙箱的创建/销毁,以及两个 OS 适配器缝(文件系统、子进程)。更高的能力(Bash、PTY、LSP)不需要 E2B 专属 fork:
现成的
bash-local、terminal-bash、lsp-stdio把每一次执行世界操作都委托给ctx.fs和ctx.subprocess,所以挂上两个 E2B 适配器,它们的可变工作就落进同一个沙箱。
这条边界不搬 harness 进程、Cordis 对象、模型调用、agent/session 状态、会话持久化、技能、更高层协议状态,也不搬 E2B SDK 缓冲区——它们留在宿主进程。
二、包
| 包 | ctx key | 角色 |
|---|---|---|
e2b | ctx.e2b | 创建一个沙箱、准备其工作/运行时目录、暴露共享 SDK 句柄,并在超时或销毁时删除它 |
fs-e2b | ctx.fs | 在 E2B Filesystem API 之上实现文件系统缝 |
subprocess-e2b | ctx.subprocess | 在 E2B Commands 与 PTY API 之上实现可执行查找、受管进程组与 stdio、远端 spill 文件、终端会话 |
三、生命周期与所有权(e2b)
e2b 包是共享生命周期所有者:文件系统与子进程适配器注入 ctx.e2b、等待它的单一 SDK 句柄,因此栖身于同一个远端 Linux 工作树与进程世界。
- 构造即创建沙箱:解析
getSandbox()之前,服务先创建cwd和私有cwd/.dsh-e2b适配器状态目录,校验保留路径是真实目录(而非 symlink 或其他文件类型),再设为0700 - 命令 shell 隔离:每个适配器内部的 E2B 命令 shell 收到一个随机的根级
HOME,避免 SDK 固定登录 shell 在控制命令前从可变的用户家目录解析 profile 文件 - 销毁:先阻止新的句柄获取,再等待 setup 完成,最后删除沙箱。
SandboxNotFoundError意味着已超时或别的所有者已删除,视为静默 - 初始目录 setup 失败会尝试删除一次;配置的 E2B 超时约束第二次失败。provider 插件必须在此所有者之后加载、之前销毁
模型可见性:无——这个共享运行时所有者不注册任何模型可见上下文。
四、两个适配器
fs-e2b 实现 dsh-fs 的 provider 契约,无配置:先加载 e2b,再用它替换 fs-local。文件工具与 E2B 后端 Bash 进程看到同一个世界。
- 远端身份与元数据:相对路径按 POSIX 对调用方 cwd 或
ctx.e2b.cwd解析;stat/lstat/一级目录列表把 E2B 元数据投影进文件系统缝;版本是 E2B 元数据 + 每次写入扩展属性的不透明哈希 - 执行世界路径:规范目标暴露绝对 POSIX 进程路径、百分号编码
file:URI,以及 provider 自有的 containment 检查 - 有界读:
readBytes在 stat 大小处短路,流式读远端对象并在超过maxBytes时取消(FS_TOO_LARGE),宿主机不整块缓冲超限文件 - 原子写:写入先建随机 sibling 暂存目录(
0700),通过 E2B 同文件系统原子 rename 发布;createIfAbsent用远端ln -T保证原子 no-replace
subprocess-e2b 实现 dsh-subprocess 缝,用它替换 subprocess-local。
- 异步远端启动:同步缝立即返回句柄,
Sandbox.commands.run(..., { background: true })远端启动;pid在 wrapper 发布并校验进程组 id 前为-1 - Linux 进程组:quoted wrapper 用
exec setsid --wait启动每个 argv,记录真实进程组 id;终止先SIGTERM、等graceMs,再升级SIGKILL与 SDK kill 兜底 - 环境边界:一个可信控制 shell 探针解析沙箱用户登录家目录;wrapper 移除环境里的
DSH_*与凭据形状(*KEY*/*SECRET*/*TOKEN*)名字,仅恢复显式spec.env条目 - stdio 投影:远端 wrapper 把原始字节分流到可选的有界 spill 文件,每个 live chunk 以换行分隔 base64 帧回传,宿主增量还原
- 终端会话:
spawnTerminal()走 E2B 字节 PTY API,私有0600文件写入 argv 与净化后的环境,发送真实信号,terminate()清理终端会话里的每个活进程组
五、配置
- id: e2b
name: '@deepseek-ai/dsh-e2b'
config:
cwd: /home/user/workspace
timeoutMs: 300000
- id: subprocess-e2b
name: '@deepseek-ai/dsh-subprocess-e2b'
- id: fs-e2b
name: '@deepseek-ai/dsh-fs-e2b'
apiKey可选,缺省读E2B_API_KEY;它配置宿主 SDK 连接,从不装进沙箱cwd缺省/home/user/workspace,必须是绝对 POSIX 路径timeoutMs缺省五分钟,控制沙箱生命周期;到期即删除沙箱
subprocess-e2b 另有:
| Key | 默认 | 含义 |
|---|---|---|
pollMs | 20 | 远端状态/存活轮询周期(毫秒);每 tick 是一次控制面请求,调大以请求数换退出观察延迟 |
六、挂载状态
e2b 是实验 POC,opt-in:任何出厂组合(base / headless / web-app)都不默认挂载。要使用需在 profile 的 cordis 配置里显式声明上面三个插件,且 fs-e2b/subprocess-e2b 必须在 e2b 之后加载。
七、已知限制
- 不是整机运行时:Cordis 服务、agent/session 状态、会话日志、LLM 请求、技能、SDK 侧缓冲区都留在宿主进程
- 沙箱状态是短暂的:销毁与超时都删除沙箱;重连、暂停/离开保留、模板、卷、快照都在 POC 之外
- 未配置部署平台:网络策略、宿主工作区同步、沙箱发现都在 POC 之外
cwd是解析约定,不是 containment:适配器与命令可以寻址其他沙箱路径;E2B 网络访问沿用基础镜像的策略
八、验证
# e2b 默认不挂载,dump-config 里应看不到这三个 id
dsh web --dump-config | grep -iE "e2b"
# 显式挂载后,确认三个插件都在组合树里
dsh web --dump-config | grep -iE "e2b"