CloudSandbox 架构设计
在手机上(iOS / Android)驱动一台远程电脑运行 dsh(DeepSeek Harness),完成编码等任务。本文描述当前已部署的实现:dsh(大脑)是我们服务器上 Agent Pool 容器里的一个进程,模型的工具(持久 bash、读写文件)在每会话一台的腾讯云 AGS 沙箱(手)里执行,工作区随时有快照可以重建。§3 用实测数据给出一次完整对话的流程、耗时和开销。
更新:2026-11-02 · 对应代码:仓库根目录 · 接口细节见 docs/api.md · 方案演进见 docs/agent-pool-design.html
1. 概览
cs-pool 里的一个进程(worker),持有对话和模型 key;它的 ctx.fs / ctx.subprocess 换成我们写的插件,经 E2B 协议把工具打到 AGS 沙箱。沙箱里没有 dsh,也没有任何 key。三家对标产品(Claude Code cloud、Codex Cloud、WorkBuddy)的共识是:不把机器长期分配给用户,持久化的只有对话和代码,算力按会话分配、可回收;移动端是瘦客户端。本项目照此设计,调研见 docs/research/mobile-cloud-computer.md。最初的实现把工具和 dsh 放在同一个容器里(下表左列,仍在 :8080 运行);2026-10-30 起改为大脑与手分离(右列,:8081),原因见 §10。
两种模式
同一个网关二进制、同一个运行镜像,由 GATEWAY_HANDS_TOOL 是否设置决定。App 接口完全一样,工具沙箱模式只多了 sandbox.hands 字段和 hands 事件。
本地工具(:8080) | 工具沙箱 hands(:8081,当前方案) | |
|---|---|---|
| dsh 在哪 | 每会话一个 2C4G 容器 | Agent Pool 容器里的一个进程(约 180 MiB),常驻 2 个预热 |
| 拿到 dsh | 冷启动约 2.3s(容器 0.3s + dsh 2.0s) | 预热 worker 约 0.04s;池空时现起约 2.1s |
| bash | 一次性:每次调用都是新 shell | 持久 shell:cd、export、后台任务跨调用、跨轮、跨暂停保留 |
| 工具在哪执行 | 会话容器里,与 dsh 同机,root | 每会话一台 AGS 沙箱(2C4G,广州) |
| 每次工具调用 | 约 35 ms | read 30–100 ms,write / edit 100–150 ms,bash 160–500 ms(§3.3) |
| 第一次用工具 | 无额外耗时 | 建沙箱 2.1–2.6s,一个会话只有一次 |
| 空闲后 | 30 分钟停容器,进程丢失 | 沙箱 2 分钟快照 + 带内存暂停,进程、文件、shell 都在,恢复 1.7–2.5s;24 小时后停掉只留快照(归档);dsh 空闲 2 小时释放 |
| 出故障 | 容器挂了:对话在,进程没了 | worker 挂了:换一个接着聊;沙箱丢了:从最近的快照重建,模型会被告知 shell 已重置 |
| 审批 | dsh 默认策略(会问) | never:命令只能碰到这台一次性的沙箱 |
| 模型 key 暴露面 | 与模型的命令同机,靠 dsh 按变量名清洗;/proc 未测 | 沙箱里没有 dsh、没有 key(smoke 断言) |
| 工作区存放 | 宿主机磁盘(bind mount) | AGS 实例盘,另有快照在宿主机磁盘(用过工具的一轮后、文件接口改动后、每次暂停前更新) |
| 成本 | 只有自有服务器 | 另加 AGS 按秒计费(§3.4) |
名词
“沙箱”在本项目里可能指好几样东西,镜像又叫 dsh-sandbox,很容易混。本文统一用下表的叫法:
| 名词 | 是什么 | 归属 |
|---|---|---|
| Agent Pool / worker | 工具沙箱模式下的“大脑”:一个共享 Docker 容器 cs-pool,里面跑 envd 和许多个 dsh 进程(worker)。常驻 2 个启动并完成 initialize、还没分配的 worker;会话拿一个、在上面 session/resume,空闲 2 小时或删会话时杀掉。一个 worker 只服务一个会话。所有 worker 共用宿主机上的一份 dsh 会话日志目录 | 本项目 |
| 会话容器 | 本地工具模式下每个会话一个的 Docker 容器 cs-<会话 id>,运行 envd、dsh 和模型的命令 | 本项目 |
| 工作区快照 / 归档 | 沙箱 /workspace 的 tar.gz,由沙箱里的 tar 打包后拉回宿主机,存在会话目录。沙箱暂停超过 24 小时会被停掉,只留快照,这叫“归档”;下次用工具时新建沙箱并还原 | 本项目 |
| 运行镜像 | 会话容器的镜像,源码 image/:Node 22、dsh 0.2.1-rc.2、envd、启动器 dsh-acp、我们的 dsh 插件。标签 dsh-sandbox:latest 沿用自 PoC,与 dsh 自带的沙箱无关 | 本项目 |
| 工具沙箱(手,hands) | 每会话一台 AGS 沙箱实例,模型的 bash 和文件读写都在这里。镜像 image/tools.Dockerfile(Debian + git / python / pytest / node / rg + envd,约 1 GB),AGS 沙箱工具 cs-tools-022 | 本项目(运行在腾讯云) |
| Broker | 网关里管理工具沙箱的组件(internal/hands):懒创建、续期、暂停、带重试的恢复、丢失后重建、nonce 身份校验 | 本项目 |
| 租约 | dsh 访问自己沙箱的凭据:{sandboxId, domain, accessToken, envdVersion, epoch}。worker 用自己的 token 向网关的 /internal/hands/lease 要,这个 token 只在 worker 分配给某个会话期间有效;30 秒内复用。epoch 在每次创建或恢复沙箱时变化,持久 shell 据此重连 | 本项目 |
| dsh 插件 | image/dsh-plugins:cube-runtime(取租约、管 SDK 句柄),fs-cube / subprocess-cube(dsh 官方 dsh-*-e2b 0.1.5 的 fork,加了缓存、减少往返、终端断流重连)。由 image/dsh-hands.patch.yml 装配,同时把一次性 bash 换成 dsh 自带的持久 bash(dsh-tool-bash-persistent,跑在沙箱里的 PTY 上) | 本项目(MIT fork) |
| envd | E2B 开源的沙箱内守护进程(e2b-dev/infra/packages/envd),:49983 提供 Connect-RPC 进程 API 与文件 API。会话容器里是腾讯云官方镜像提取的 v0.5.14,不鉴权,只绑 127.0.0.1;工具沙箱里是自己编译的上游 0.10.2(要它的文件元数据),由 AGS 的每实例访问令牌保护 | 第三方(E2B) |
| AGS | 腾讯云 Agent Runtime(Agent Sandbox),托管的 CubeSandbox。控制面是云 API(ags 2025-09-20),数据面 49983-<id>.ap-guangzhou.tencentags.com | 腾讯云 |
| dsh 的 bash 沙箱 | dsh 自己的 landlock-run。本地工具模式下不配置、不依赖;工具沙箱模式下关闭(隔离边界是 AGS 的 MicroVM) | dsh |
为什么经 envd 而不是 docker exec。网关要以进程为粒度驱动 dsh:把它的 stdin/stdout 当作 ACP 通道,网关重启后还要按 pid 接回同一个 dsh。docker exec 断开后无法重新接回同一个进程的输出。工具沙箱那一侧,AGS 也只有 envd 这一个入口,所以两侧用的是同一套协议。
2. 系统结构
图 1 工具沙箱模式。蓝:客户端与网关之间的唯一通路;绿:网关经 Pool 容器里的 envd 持有每个 dsh worker 的 stdio;紫:dsh 直连模型;橙:工具调用与沙箱管理。① worker 的插件凭自己的 token(只在分配给某个会话期间有效)向网关要租约;② 拿租约直连沙箱的 envd 执行工具;③ App 的文件接口经网关读写沙箱,已归档时直接读快照;④ 沙箱的生命周期(含快照、归档、重建)只由网关管理。本地工具模式下没有右半边,每个会话一个容器,/workspace 是宿主机目录 bind mount 进去。
组件
| 组件 | 位置 | 职责 |
|---|---|---|
| gateway | cmd/gateway | 进程入口;配置只来自环境变量(systemd EnvironmentFile,0600);每分钟回收空闲会话,每 30 秒检查一次该暂停的沙箱;SIGTERM 时 flush 所有 dsh、暂停所有沙箱再退出 |
| HTTP API | internal/gateway/http.go | 路由、三种凭据的鉴权、请求日志(从不记录 Authorization)、SSE 续传与心跳、内部租约接口 |
| 会话 | internal/gateway/session.go | 状态机、轮次、审批挂起、事件追加与扇出、拉起与关闭 dsh;给 dsh 传 CS_HANDS_URL / CS_HANDS_TOKEN |
| 管理器 | internal/gateway/manager.go | 按归属查找会话、配额与名额、启动时从磁盘加载并修复中断的轮次;按 token 找会话发租约 |
| Agent Pool | internal/gateway/pool.go | 预热 worker、分配给会话(ACP 流量和租约 token 都转到这个会话)、补齐、worker 死掉时丢弃;Pool 容器第一次使用时重启或按新镜像重建 |
| 工作区快照 | internal/gateway/snapshot.go | 在沙箱里 tar 打包并拉回、还原到新沙箱、已归档时只读地列目录和下载(只把 tar 当数据流读,从不在宿主机解包) |
| Broker | internal/hands | hands.go 生命周期(懒建、续期、快照后暂停、归档、恢复、丢失后从快照重建)与身份校验;ags.go AGS 云 API;envd.go 网关侧的 envd 文件与命令客户端 |
| 文件 | internal/gateway/files*.go | 本地模式经 os.Root;工具沙箱模式经 envd(files_hands.go),隐藏 .dsh-e2b,拒绝指出 /workspace 的符号链接 |
| 设备 | internal/auth | 注册、token(只存 SHA-256)、首次绑定、30 天闲置过期、管理员重置 |
| ACP 客户端 | internal/acp | NDJSON 上的 JSON-RPC 2.0;把 dsh 的审批请求交给上层异步回答 |
| 容器运行时 | internal/sandbox | Docker:每会话一个容器(本地模式)或一个 Pool 容器(工具沙箱模式);经容器内的 envd 启动 dsh、写 stdin、读 stdout |
| 模型路由 | internal/llmroute · image/dsh-llm-patch.mjs | 把 DSH_LLM_* 变成 dsh 的 --patch;打码 |
| dsh 插件 | image/dsh-plugins · image/dsh-hands.patch.yml | 工具沙箱模式下替换 dsh 的执行世界:禁用本地 subprocess / fs-sandbox / bash-sandbox / tool-bash / permission(构建期检查,漏一条就构建失败),插入我们的三个插件和持久 bash;$DSH_HOME 与 ~/.agents 不进沙箱 |
| 测试台 | internal/web | 原生 JS,嵌入二进制;视觉对齐 dsh 自带 Web 端 |
| 运行镜像 / 工具镜像 | image/Dockerfile · image/tools.Dockerfile | 前者给会话容器,构建期自检模型路由与插件装配;后者给 AGS,推到 CCR 个人版,按 tag@sha256 固定 |
信任边界
| 边界 | 谁在里面 | 措施 |
|---|---|---|
| 公网 / 办公网 → 网关 | 任何能连上网关的人 | 除 /healthz、注册和静态页外都要设备 token;注册要 App Key |
| 设备 ↔ 设备 | 所有已注册用户 | 会话带 owner,每次查找都比对;他人会话一律 404 |
| Pool → 网关(租约) | dsh worker 及其插件 | 每个 worker 一个随机 token,只在它分配给某个会话期间映射到这个会话;worker 释放、死掉时立即作废;未知 token 一律 404;只能拿到自己那台沙箱的租约 |
| worker ↔ worker | 不同设备的 dsh 共用一个容器 | 容器里不执行任何模型的命令(本地执行的 provider 全部禁用,构建期检查);worker 不复用,不会把一个设备的 shell 状态带给另一个设备 |
| dsh → 工具沙箱 | 模型生成的命令 | 沙箱是 MicroVM,里面没有 dsh、没有模型 key、没有云凭据;envd 由 AGS 的每实例令牌保护。出网实测:公网可达(出口 106.52.159.81);云元数据服务(169.254.0.23、169.254.169.254、metadata.tencentyun.com)、我们的内网网关都不可达 |
| 快照 → 网关 | 模型打包的 tar | 网关只把它当数据流读(列目录、取单个普通文件),从不在宿主机解包;还原只在新沙箱里做 |
| 网关 → 工具沙箱(身份) | AGS 可能换掉或丢失实例 | 创建时写入 nonce,每次恢复、重连都校验;不符就拒绝(409),不会把别的沙箱当成自己的 |
| 网关 → 会话容器 | dsh(本地模式下还有模型的命令) | envd 不鉴权,只绑 127.0.0.1;本地模式读写 /workspace 经 os.Root;容器去掉 CAP_MKNOD |
| 网页测试台 | 模型写的文件 | 下载一律 attachment + nosniff;严格 CSP;只用 textContent 渲染 |
3. 一次完整对话:流程、耗时与开销
以下数据来自 scripts/measure-turn.py 对线上 :8081 的 3 次实测(2026-11-02 下午,Agent Pool + 持久 bash + 快照都已上线;服务器在办公网,AGS 在广州,模型 DeepSeek 官方 deepseek-flash)。每次实测是一个新会话里的三轮对话:
- 第 1 轮(冷):“写
wordfreq.py的top_words(text, n),再写至少 4 个 pytest 用例并跑通”。新会话:dsh 是建会话时从池里拿的预热 worker,沙箱要在第一次用工具时创建。 - 第 2 轮(热):紧接着发“加一个可选参数
stopwords,补两个测试,重新跑 pytest”。 - 第 3 轮(暂停后):等沙箱空闲暂停后再发“再跑一次 pytest -q”(第 3 次实测不等,作对照)。
脚本把三份记录按时间对齐:网关的事件日志(App 看到的)、dsh 自己的会话日志(每次模型请求的首 token、末 token、缓存命中 / 未命中 / 输出 token 数,每次工具调用的起止)、网关日志(沙箱创建、恢复、暂停)。复现:GATEWAY=http://9.135.181.73:8081 PAUSE=1 scripts/measure-turn.py,结果写到 out/。同一天上午在没有 Pool 时测过一组(dsh 每会话一个容器、一次性 bash),对照见 §3.3 末尾。
3.1 流程
图 2 一轮对话(工具沙箱模式)。发消息只返回轮次号,所有输出都从事件流到达。橙色块每个会话只发生一次(沙箱丢失或归档后再来一次,并从快照还原);其余灰底块按条件发生。
回复是流式的。dsh-acp 0.2.1 只在一次模型请求完成后才把整段思考和回复各发一次(它的 README:原始增量不上线),手机上表现为等几秒后一下子全出来。我们的 dsh 插件 acp-live 订阅 agent 循环的实时流(agent/assistant-stream),约每 80 ms 把增量作为标准 session/update 发出;网关(live.go)把 dsh-acp 随后发来的整段副本去重:一致就丢掉,只推了一部分就补后缀,对不上就整块替换(_meta["cloudsandbox/replace"])。实测第一个字从约 5.2s 提前到 1.3s,延迟不超过 42 ms,两种模式都生效。
事件流是唯一的渲染源。网关为每个事件分配会话内严格递增的 seq,同时作为 SSE 的 id:。客户端只需记住最大 seq:断线、切后台、慢消费者被踢(缓冲超过 512 条)都用 Last-Event-ID 重连,不丢、不重。dsh 的 session/update 原样透传,网关只额外产生 user_message、turn_*、status、error、permission_*、files_changed、hands。
3.2 实测时间线
图 3 第 2 次实测的三轮,横轴是从 App 发出消息算起的秒数。每段模型请求里,浅色是等首 token,深色是生成。第 1 轮的第一条 bash 前面先建沙箱;第 3 轮开头的橙色是恢复暂停的沙箱,发生在 dsh 找 AGENTS.md 时,所以在第一次模型请求之前。三轮里都看不到 dsh 启动:worker 是建会话时从池里拿的。
3.3 耗时拆解
| 轮次 | 墙钟 | 首个输出 | 模型 | 工具 | 沙箱建 / 恢复 | 其他 | 步数 · 工具数 |
|---|---|---|---|---|---|---|---|
| 第 1 轮(冷) | 11.7–22.7s | 1.5–3.6s | 7.2–18.1s | 1.9–3.5s | 建 2.0–2.2s | 0.4s | 4–5 步 · 4–6 个 |
| 第 2 轮(热) | 10.5–13.9s | 1.6–5.2s | 9.1–12.8s | 0.7–1.1s | — | 0.2–0.4s | 4–7 步 · 5–7 个 |
| 第 3 轮(暂停后) | 4.4–4.6s | 3.1–3.4s | 1.9–2.0s | 0.4s | 恢复 1.7–1.8s | 0.3–0.4s | 2 步 · 1 个 |
| 第 3 轮(未暂停,对照) | 2.5s | 1.3s | 1.9s | 0.4s | — | 0.2s | 2 步 · 1 个 |
“首个输出”是 App 收到第一条思考 / 回复 / 工具事件的时刻。“其他”是网关、ACP 往返和 dsh 自身的处理。dsh 的“启动”一列不见了:建会话时已经从池里拿到了 worker(日志里 action=warm totalMs=37)。
| 工具 | 样本 | 耗时 | 中位 | 说明 |
|---|---|---|---|---|
| read | 6 | 60–130 ms | 99 ms | 路径与 stat 按“代”缓存;同一步内并行读两个文件 |
| write | 6 | 98–227 ms | 128 ms | 写临时文件 → 一条命令 chmod 并原子发布 → stat |
| edit | 7 | 95–127 ms | 115 ms | 先读、校验版本,再按 write 发布 |
| bash(持久 shell) | 12 | 133–865 ms | 381 ms | 含命令本身的执行时间(pytest 约 0.2–0.5s)。命令经 PTY 发送,结束靠轮询输出里的结束标记,所以比一次性 bash 多约 0.1s |
| glob | 1 | 918 ms | 在沙箱里起 rg | |
| bash(会话的第一条) | 3 | 3.1–3.5s | = 建沙箱 2.0–2.2s + 在沙箱里起第一个持久 shell 约 1s + 命令 |
结论
- 时间几乎都花在模型上。热轮次里模型占 87–93%,工具占 6–10%。最慢的一步是模型一次写出整个文件(1425 个输出 token,8.3s)。
- dsh 启动已经不在关键路径上。以前每个新会话、每次空闲唤醒都要 2.3–2.4s 起容器和 dsh;现在从池里拿预热 worker 约 0.04s,池空时现起一个约 2.1s。冷会话剩下的额外耗时只有第一次用工具时的建沙箱和起 shell(约 3.2s),只聊天的会话一点都没有。
- 暂停后的第一轮多约 1.9s,换来的是沙箱里的进程、文件、shell 变量都还在(smoke 实测
export的变量在暂停恢复后仍然有效)。恢复在 dsh 每轮开头找 AGENTS.md 时就触发,所以哪怕这一轮不用工具,只要沙箱存在且已暂停,也会被恢复。 - 首 token 很稳定。35 次请求 p50 0.74s(0.34–1.74s),DeepSeek 的前缀缓存在多步之间一直命中。
- 和上午没有 Pool 的一组对比(同一任务,3 次):冷轮次 19.2–26.4s,其中 2.4s 是 dsh 启动;热轮次 11.5–13.4s;一次性 bash 161–508 ms。差别在可测范围内主要来自模型本身的波动,结构上省掉的是 dsh 启动。
3.4 开销
价格(2026-11-02):DeepSeek deepseek-flash,元 / 百万 token,空闲时段 / 高峰时段:输入缓存命中 0.02 / 0.04,未命中 1 / 2,输出 4 / 8(官方价格页)。AGS:CPU 0.000081 元 / 核 / 秒,内存 0.000025 元 / GiB / 秒,运行中系统盘 15 GiB 内免费;暂停期间 CPU、内存停止计费,按“系统盘实际用量 + 声明内存”收 0.0021 元 / GiB / 小时(计费文档)。2C4G 运行中 = 0.000262 元 / 秒(0.94 元 / 小时)。
模型(第 2 次实测,dsh 日志里的真实 token 数)
| 轮次 | 请求 | 输入·命中 | 输入·未命中 | 输出 | 费用(空闲 / 高峰) |
|---|---|---|---|---|---|
| 第 1 轮 | 5 | 30,208 | 1,112 | 2,091 | 0.0101 / 0.0202 元 |
| 第 2 轮 | 4 | 36,096 | 1,590 | 1,990 | 0.0103 / 0.0205 元 |
| 第 3 轮 | 2 | 21,120 | 357 | 62 | 0.0010 / 0.0021 元 |
| 合计 | 11 | 87,424 | 3,059 | 4,143 | 0.0214 / 0.0428 元 |
3 次实测合计 0.014–0.023 元(高峰 0.029–0.046 元),若没有前缀缓存则是 0.10–0.11 元。每次请求都带着约 5–7k token 的系统提示和历史,但 97% 命中缓存,所以模型费用的 66–79% 是输出 token。
AGS:沙箱从建好那一刻开始按秒计费,直到空闲满 2 分钟、网关下一次检查(每 30 秒)先快照、再暂停。所以一段连续使用的成本 ≈(干活的秒数 + 120–150 秒的尾巴)× 0.000262 元。快照本身只多几百毫秒运行时间。
| 段 | 干活 | 尾巴 | 计费秒数 | 费用 |
|---|---|---|---|---|
| 第 1、2 轮(连续) | 约 31s | 120–150s | 150–180s | 0.040–0.047 元 |
| 第 3 轮 | 约 4s | 120–150s | 125–155s | 0.033–0.041 元 |
| 合计 | 275–335s | ≈ 0.08 元 | ||
| 之后若一直暂停着 | (约 1 GiB 盘 + 4 GiB 内存)× 0.0021 元 / GiB / 小时,最多 24 小时后归档 | 0.0105 元 / 小时,封顶约 0.25 元 | ||
| 归档之后 | AGS 上什么都不留;快照在我们服务器的盘上(本例约几 KB) | ≈ 0 | ||
| 一次三轮对话的总账(对话结束即删会话) | 金额 | 占比 |
|---|---|---|
| AGS 沙箱运行(含暂停前的尾巴) | ≈ 0.08 元 | ≈ 80% |
| 模型(空闲时段;高峰翻倍) | 0.014–0.023 元 | ≈ 20% |
| 我们的服务器(Pool 里的 dsh worker) | 固定成本 | — |
| 合计 | 约 0.10 元(高峰约 0.12 元) |
- AGS 的钱主要花在“等暂停”上。本例沙箱真正干活约 35 秒,却计费约 5 分钟,近九成是尾巴。这是最该调的旋钮,见 §3.5。
- 暂停着的会话有了上限。以前一个不再用的会话会一直按 0.25 元 / 天收存储费;现在暂停 24 小时后归档,每个闲置会话最多花约 0.25 元,之后在 AGS 上不花钱。
- 大脑不按次计费,而且便宜了很多。一个 dsh worker 约 180 MiB,Pool 容器限 6 核 / 12 GB,可以同时放几十个;以前每个会话独占一个 2C4G 容器,同时只能跑 6 个。
- 未计入:公网流量(AGS 计费文档未列出)、CCR 镜像仓库、暂停操作本身约 10 秒是否计运行费(文档未说明,按不计算)。
3.5 可调的旋钮
| 旋钮 | 现在 | 改了会怎样 |
|---|---|---|
GATEWAY_HANDS_IDLE_PAUSE:空闲多久暂停 | 2 分钟(每次尾巴 0.031–0.039 元) | 30 秒:尾巴 0.008–0.016 元,省约 60–75%;代价是用户两条消息间隔超过 30–60 秒,下一轮就多等约 2 秒恢复(shell 状态、进程都在)。10 分钟:尾巴约 0.16 元,几乎不会碰到恢复。建议改成 30–60 秒:恢复 2 秒相对模型的 10 秒以上并不显眼 |
GATEWAY_HANDS_ARCHIVE_AFTER:暂停多久归档 | 24 小时(最多花 0.25 元存储费) | 1–6 小时:再省一些;代价是之后再用要从快照重建(约 3s),进程和 shell 状态没了,模型会被告知 |
GATEWAY_IDLE_STOP:dsh worker 空闲多久释放 | :8081 2 小时 | 持久 shell 活在 dsh 进程里,释放就丢 shell 状态(文件不丢)。worker 只占内存,放更久成本很低 |
GATEWAY_POOL_WARM:预热几个 worker | 2 个(约 360 MiB) | 同时来的新会话超过它,多出的那个现起一个,约 2.1s |
| 何时建沙箱 | 第一次用工具时(懒) | 收到第一条消息就并行预建,可以把第一次用工具的约 3.2s 藏在模型第一步的思考里;代价是只聊天的会话也付至少一段尾巴(约 0.035 元) |
| 何时恢复沙箱 | 每轮开头找 AGENTS.md 时 | App 打开会话、用户开始输入时就预恢复,把 2 秒藏到打字时间里;或者暂停时把“查找”当作缓存命中,只有真正用工具才恢复 |
| 沙箱规格 | 2C4G | 1C2G 运行费减半(0.000131 元 / 秒),暂停费按声明内存也减少;pytest、npm install 会变慢,要实测 |
| 模型 | deepseek-flash | 换 deepseek-v4-pro:命中 7.5 倍、未命中 4.5 倍、输出 3.4 倍价格,按本例的 token 数模型费约 0.06–0.09 元(空闲时段),约为 flash 的 4 倍 |
4. 生命周期:大脑与手各一条
大脑(dsh worker)和手(工具沙箱)的生命周期互相独立:worker 可以已释放而沙箱暂停着,也可以 worker 在跑而沙箱还没建。对话和数据在任何状态下都保留。
图 4 两条生命周期。上:会话有没有分到 dsh worker;下:工具沙箱的状态。网关重启时,Pool 容器重启(所有 worker 重来),沙箱状态先记为“未知”,第一次使用时向 AGS 查询,再决定直接用、恢复还是重建。
4.1 休眠与暂停策略
| 项 | 当前策略 | 说明 |
|---|---|---|
| dsh 什么算空闲 | dsh 在跑,没有进行中的轮次、没有待处理的审批 | “最近活动”只在发消息、一轮结束、dsh 启动完成时刷新。看事件流、浏览或上传文件都不算活动 |
| dsh 多久释放 | GATEWAY_IDLE_STOP::8081 2 小时,:8080 30 分钟 | 回收器每分钟扫描一次。做的是 session/close → 杀 dsh(Pool 模式就此结束,worker 不复用;本地模式再 docker stop -t 5)。Pool 模式下 worker 便宜(约 180 MiB),而且持久 shell 的状态活在 dsh 进程里,所以放得更久 |
| 预热 | Pool 里常驻 GATEWAY_POOL_WARM 个(默认 2)启动好的 worker | 会话拿走一个就在后台补一个;预热的 worker 死了(例如 Pool 容器重启)也会补,间隔 5s 防止崩溃循环 |
| 沙箱什么算空闲 | 没有进行中的轮次,且最后一次租约已过去 GATEWAY_HANDS_IDLE_PAUSE | 默认 2 分钟,每 30 秒检查一次。轮次进行中不暂停,哪怕模型在长时间思考 |
| 沙箱暂停做什么 | 先快照 /workspace,再 PauseSandboxInstance(Memory=true) | 进程、内存、文件、环境变量都保留(S3 实测 100/100 恢复后全部还在)。暂停期间不计 AGS 的超时(2 小时)。快照失败不阻止暂停,只是这次暂停不能被归档 |
| 暂停多久归档 | GATEWAY_HANDS_ARCHIVE_AFTER,默认 24 小时 | 要求这次暂停前的快照成功、之后没被用过;然后停掉实例,只留快照(archived,存储费≈0)。再用工具时新建沙箱、还原快照,进程不保留。快照超过 GATEWAY_HANDS_SNAPSHOT_MAX(压缩后 512 MiB)的会话不归档,一直暂停着 |
| 沙箱谁来恢复 | 任何一次租约(dsh 用工具、找 AGENTS.md,或 App 上传文件) | 恢复带重试(4 次),恢复后校验 nonce。App 只浏览文件时:没建过返回空列表,已归档读快照,都不会创建沙箱 |
| 休眠后保留什么 | 对话、dsh 会话记录(宿主机);工作区(本地模式在宿主机,沙箱模式在 AGS 实例里) | 本地模式下容器里的进程会丢;沙箱模式下工具的进程在沙箱里,dsh 停了也不影响它们 |
| 名额 | :8081 全服务器同时 24 个 dsh、每设备 4 个;:8080 6 个、每设备 2 个 | 休眠中的会话不占名额。名额满时立即拒绝(server busy),不排队。Pool 容器限 6 核 / 12 GB(全部 worker 共用);沙箱数量受 AGS 配额限制 |
| 网关自身重启 | SIGTERM:flush 并杀掉所有 dsh、快照并暂停所有沙箱。崩溃:旧 dsh 会留在容器里 | Pool 容器在第一次使用时重启(镜像变了就重建),杀掉上一个网关的全部 worker;本地模式在会话首次使用时 docker restart 它的容器。沙箱记录(实例 id、nonce、快照时间)从 meta.json 恢复 |
4.2 启动耗时(实测)
dsh 一侧,工具沙箱模式(Agent Pool):会话拿预热 worker,startup.action = warm,实测 37–40 ms(只有 session/new 或 resume);池空时现起一个(cold),约 2.1s,其中 dsh 启动约 2.0s。Pool 容器本身只在网关启动时重启或重建一次。
本地工具模式(每会话一个容器),下图和下表:部署服务器(16 核 / 31 GB,TencentOS,Docker 29),运行镜像已在本机,2026-10-28 测得。网关记录每次启动的分段耗时,写进 status: idle 事件的 startup 字段和服务日志。
图 5 本地工具模式下 dsh 各启动路径的分段耗时。envd 就绪、拉起 dsh 进程各只需 1–2ms,在图上看不见。工具沙箱模式下从池里拿 worker 约 0.04s,图上就是第一行的“≈ 0”。
| 路径 | 样本 | 容器 | dsh 启动 | 会话 | 合计 |
|---|---|---|---|---|---|
新建会话(create) | 4 + 3 | 0.28–0.35s | 1.98–2.08s | 0.05–0.06s | 2.32–2.43s;用户从创建到可用 2.48–2.60s |
空闲休眠后唤醒(start) | 4 | 0.23–0.24s | 1.95–2.00s | 0.06s(resume) | 2.26–2.30s |
网关重启 / 崩溃后(restart) | 4 | 0.36–0.39s | 1.92–1.95s | 0.06s(resume) | 2.37–2.41s |
| 同时新建 6 个 | 2 轮 × 6 | 0.38–0.54s | 2.12–2.33s | 0.05s | 用户感知 2.95–3.16s |
| 同时新建 8 个(上限 6) | 1 轮 | 6 个在 2.96–3.16s 内就绪,另外 2 个立即被拒绝(server busy) | |||
休眠(stop) | 4 | close + kill 9–10ms,连同 docker stop 共 0.17–0.21s | |||
工具沙箱一侧(AGS,广州,服务器经公网访问):
| 操作 | 实测 | 来源 |
|---|---|---|
| 建沙箱(Start → RUNNING → 写 nonce) | 2.1–2.6s(5 次) | §3 实测与 smoke |
| 恢复带内存暂停的沙箱 | 1.7–2.5s(11-02 多次);S3 100 次 p50 2.3s,100/100 成功 | docs/cube-restore-report.html §9 |
| 从快照重建(丢失或归档后) | 建 2.0–2.3s,再上传并解包快照(本例几 KB,可忽略) | smoke 故障注入 |
| 暂停 | API 立即返回,约 10s 后到 PAUSED | S3 |
| 单次 envd 往返 | p50 21–32 ms | S2 |
| 停止(删会话) | API 立即返回,实例 STOPPED | smoke |
结论:约 85% 的 dsh 启动时间花在 Node 加载 dsh 和它的约 250 个插件包上;容器本身 0.2–0.4s。恢复对话不比新建慢(session/resume 只多约 10ms)。启动耗时远小于模型耗时。并发启动有轻微争用(6 个同时,每个慢 0.5–0.6s),名额检查原本有竞态,现在启动一开始就占住名额(TestAdmitIsAtomic)。
4.3 实现约束
| 规则 | 原因 |
|---|---|
停 dsh 前必须 session/close | dsh 只持久化已 flush 的内容;session/close 是 flush 屏障(附录 A) |
从 stopped 恢复时先 session/resume,失败再 session/new 并发出 error 事件 | 恢复失败不应让用户卡住,但必须告知上下文丢了 |
网关重启后首次使用会 docker restart 容器 | 旧网关拉起的 dsh 可能还活着,不能出现两个 dsh 写同一会话 |
envd 的输出流绑定 context.Background | SDK 用调用方 ctx 控制整条流,单次超时会掐断会话 |
dsh 经 /bin/sh dsh-acp 启动,脚本以 exec 结尾 | envd 按 pid 发 SIGKILL,包装进程会让 dsh 变孤儿 |
名额在检查的同时占住(Manager.admit) | 只数已经起来的 dsh,会让并发启动全部越过上限 |
先拿 hand 的锁,再拿 Session.mu | Broker 的保存、通知回调在持有 hand 锁时运行,并会取 mu |
| “查找”不能创建沙箱 | dsh 每轮都经 ctx.fs 找 AGENTS.md;fs-cube 用 ?ifExists=1 租约,先确认文件存在再读。否则每个只聊天的会话第一轮就建沙箱 |
| AGS 的超时按 2 小时设置、使用中续期 | AGS 最短 300s;暂停期间不计时,所以只需在运行时续期 |
| 一个 worker 只服务一个会话;它的租约 token 只在分配期间有效 | dsh 进程里有持久 shell 等按 agent 的状态,不能带给另一个设备;死掉或释放的 worker 的 token 不能再拿到任何沙箱 |
| Pool 容器里不能执行任何东西 | 所有设备的 dsh 共用它。hands patch 禁用全部本地执行的 provider,image/Dockerfile 构建期检查 |
持久 shell 按 lease 的 epoch 重连,沙箱换了就放弃 | 暂停会让 envd 的流静默失效(不报错、不再有数据);新沙箱里同一个 pid 是另一个进程 |
终端出问题时按“shell 被杀”结束,不从 waitForCommand 抛异常 | 异常会经输出流打崩整个 dsh(实测 dsh exited with code 1) |
5. 用户与隔离
QIMEI36 是 36 位十六进制设备标识,会出现在日志和上报里,其校验位只有 Qimei 后台能验证——它是标识,不是凭证。因此:
| 凭据 | 怎么带 | 能做什么 | 存储 |
|---|---|---|---|
| App Key | X-App-Key,仅注册接口 | 注册设备 | 环境变量;内置于客户端 |
设备 token cs_… | Authorization: Bearer | 访问本设备的会话与文件 | 服务器只存 SHA-256;客户端存 Keychain / Keystore |
| Admin token | Authorization: Bearer | 列出 / 重置设备;读不到任何会话 | 环境变量;只给管理员 |
| hands token(内部) | Authorization: Bearer,仅 /internal/hands/lease | 拿到本会话沙箱的租约 | 只在网关内存和该会话 dsh 的环境变量里;网关重启即换 |
| 腾讯云 SecretId / Key | 网关调用云 API | 管理 AGS 实例 | 服务器 gateway-hands.env(0600);不进任何容器 当前权限过大,待换 |
- 首次注册即绑定(TOFU)。已有有效 token 的 QIMEI36 再注册必须出示其 token,否则 409。只知道设备号无法登录。
- token 丢失(App 数据被清,QIMEI36 不变)由管理员重置:吊销全部 token,会话与文件保留。
- 配额(每设备):20 个会话、同时运行 2 个、单次上传 200 MB;全服务器同时运行 6 个。
- 生产化:把设备绑定到真实账号(企业微信 / QQ 登录),QIMEI36 保留为设备维度。
6. 文件
App 的文件接口(列目录、上传、下载带 Range、删除)与模型看到的是同一份 /workspace。两种模式下实现不同,接口相同:
| 威胁 | 本地模式(宿主机目录) | 工具沙箱模式(经 envd) | 验证 |
|---|---|---|---|
符号链接指向工作区外(leak → /etc/hostname) | 所有访问经 Go os.Root,内核层拒绝离开根目录,403 | 先 Stat,链接目标不在 /workspace 下就 403;即使跟过去,读到的也只是那台一次性沙箱 | 单测 + 实测 |
..、绝对路径、URL 编码绕过 | 路径规范化 + 限定在 /workspace | 单测 + 实测 | |
| 经外链目录写文件、覆盖外链 | 写前 Stat,打开后再校验 | 写入 envd,落在沙箱里 | 单测 |
| FIFO 让网关读阻塞 | O_NONBLOCK + 非普通文件拒绝 | 只下载普通文件 | 单测 |
mknod 块设备读宿主机磁盘 | 容器 --cap-drop MKNOD | 不适用(命令不在我们的宿主机上) | 服务器实测 |
| 模型写的 HTML 在测试台同源执行 | 下载 attachment + nosniff + CSP sandbox;前端 blob 强制 octet-stream | 实测响应头 | |
| 适配器的内部状态被看到 | 不适用 | .dsh-e2b 在列表里隐藏,直接访问 404 | smoke |
快照里的符号链接、..、绝对路径 | 不适用 | 已归档时从快照读:只按条目名匹配,.. 和绝对路径的条目跳过,只能下载普通文件;从不在宿主机解包 | 单测 |
工具沙箱模式下,沙箱不存在时浏览返回空列表、不创建;已归档时列目录和下载(含 Range)直接读快照;上传、删除会创建或还原沙箱(工作区要有地方放),并在之后刷新快照。
7. 模型配置与密钥
镜像不含任何模型配置。网关启动 dsh 时传入 DSH_LLM_BASE_URL / MODEL / API_KEY / THINKING_FORMAT,dsh-acp 把它们写成 dsh 自带的 --patch 覆盖文件后 exec dsh。当前是 DeepSeek 官方 API(https://api.deepseek.com,deepseek-flash);以前的内网 relay 配置留有备份。
- 必须覆盖
acp插件:acp-app 在那里写死了deepseek-official,只改agent-default-model不生效。 - key 不落盘:patch 里只有变量名
apiKeyEnv: DSH_LLM_API_KEY。 - 工具沙箱模式下,模型的命令根本碰不到 key:命令在另一台机器(AGS 沙箱)上执行,那里没有 dsh 进程、没有环境变量里的 key。smoke 每次都在沙箱里跑
env断言。本地模式的残余风险(读/proc/<dsh pid>/environ)在这个模式下不存在。 - 本地模式:dsh 按变量名清洗子进程环境(
*KEY*/*TOKEN*/*SECRET*/*PASSWORD*),所以变量名不能改;/proc未测。
8. 持久化与恢复
| 数据 | 位置 | 写入方式 | 恢复 |
|---|---|---|---|
| 设备与 token 哈希 | data/devices/<q>.json | 临时文件 + rename | 启动时全量加载 |
| 会话元数据(归属、标题、dsh 会话 id、hands 记录) | data/sessions/<id>/meta.json | 临时文件 + rename | 启动时加载;hands 记录 = 实例 id + nonce |
| 时间线 | …/events.jsonl | 每事件追加一行 | 丢弃撕裂的尾行和不连续的 seq;补 turn_end / status |
| dsh 会话日志 | Pool 模式:data-hands/dsh/--workspace--/<uuid>/,所有 worker 共用(容器内 /root/.dsh/sessions);本地模式:…/sessions/<id>/dsh/ | dsh 管理。实测一轮结束时(回复 session/prompt 之前)已写盘,所以空闲时 kill -9 不丢;session/close 是正式的 flush 屏障。每次模型请求的 token 数与时间都在里面(§3 的数据来源) | session/resume,在任何 worker 上 |
| 工作区(本地模式) | …/workspace/ | 模型 / 文件接口 | 随目录保留 |
| 工作区(工具沙箱模式) | AGS 实例的系统盘 | 模型 / 文件接口经 envd | 随实例保留(含暂停) |
| 工作区快照 | …/sessions/<id>/workspace.tgz | 沙箱里 tar -czf(排除 .dsh-e2b)→ 下载 → 临时文件 + fsync + rename。用过工具的一轮后、文件接口改动后、每次暂停前 | 沙箱丢失(reset)或归档后再用(restore):上传到新沙箱并解包;还原失败重试一次,仍失败则给空工作区并在 hands 事件里带 error。丢的只是最后一次快照之后的改动和所有进程 |
多机时,dsh 会话目录要放共享存储(CFS)或分配 worker 时从 COS 取回,快照直接放 COS;单机上都在本地盘。两套日志职责不同:events.jsonl 是给客户端回放的 ACP 投影,dsh 日志是给模型续上下文的权威记录。网关从不解析 dsh 的磁盘格式;scripts/measure-turn.py 只为测量读它。
9. 下一步:多机与上量
单机形态已经完整:大脑是预热的进程池,手是按需、可暂停、可重建的云沙箱,工作区有快照。剩下的是把单机上“本地盘 + 单进程”的部分换成能多副本的组件(docs/agent-pool-design.html 之后的阶段):
图 6 下一步。Broker、单机 Agent Pool、沙箱与快照都已在线上;新增的是多副本网关、多机 Pool、云上存储。
| 能力 | 现在(单机) | 下一步 |
|---|---|---|
| 大脑 | 一个 Pool 容器,常驻 2 个预热 worker,名额 24 | Pool 跨多台机器;dsh 会话目录放 CFS(或分配时从 COS 取回),任何机器的 worker 都能 resume |
| 手 | AGS 沙箱:懒建、2 分钟快照 + 暂停、24 小时归档、丢失重建 | 调短暂停阈值(§3.5);镜像预热;AGS 配额 |
| 工作区快照 | 本机磁盘,整份 tar.gz | COS;大工作区改增量(git bundle 或 rsync 式) |
| 事件与元数据 | 本机 events.jsonl / meta.json | Redis Stream + TDSQL-C,网关多副本,会话由 Redis 租约路由 |
| 推送 | 无 | 腾讯移动推送(完成提醒) |
10. 关键决策
| 决策 | 选择 | 放弃的方案与原因 |
|---|---|---|
| dsh 放哪 | 大脑在我们这边,工具在 AGS 沙箱(2026-10-30 起) | 最初选“dsh 和工具都在沙箱里”(理由是每次文件读写一个网络往返太慢、官方 dsh-e2b 只是 POC 质量)。实测推翻了前一条:fork 并优化适配器后每次工具调用 67–230 ms,只占轮次时间的 4–6%(§3.3)。换来的是:key 不进沙箱、只聊天不建沙箱、沙箱可以独立暂停、大脑和手分别升级 |
| 大脑怎么放 | 一个共享容器里多个 dsh 进程,预热,一个会话一个,用完即杀 | 每会话一个容器:唤醒要 2.3s、一个 2C4G 只跑一个 dsh。worker 复用给下一个会话:持久 shell 等按 agent 的状态会串到别的设备。一个 dsh 进程服务多个会话:执行世界是进程级的,会共用一台沙箱 |
| 工作区怎么持久化 | 沙箱里 tar 打包、拉回本机盘;用过工具的一轮后、文件改动后、暂停前 | 只靠 AGS 实例:丢了就全没了;直接用 S3 卷当 /workspace:git、npm 这类小文件密集操作会慢 |
| bash | dsh 自带的持久 bash,跑在沙箱的 PTY 上,断流后按 epoch 重连 | 一次性 bash:每次调用都丢 cd / export,模型要反复重设环境 |
| 工具怎么搬过去 | dsh 的执行世界接口(ctx.fs / ctx.subprocess)+ 我们 fork 的 E2B 适配器 | 直接用官方 dsh-*-e2b 0.1.5:每次工具调用 0.4–1.5s(每次解析路径都起进程、20 ms 轮询退出码),启动即建沙箱、退出即删 |
| 沙箱用什么 | AGS 托管(腾讯云的 CubeSandbox) | 自建 Cube:要运维 16 个服务,本机嵌套虚拟化下恢复只有 8/18 成功(docs/cube-restore-report.html) |
| 沙箱何时建 | 懒建;“查找”不建 | 会话创建或第一轮就建:只聊天的会话也要付钱(§3.5 有数字) |
| 空闲时沙箱怎么办 | 带内存暂停,进程保留 | 直接停:进程、dev server 都丢;恢复成本反而更高(要重建环境) |
| 工具沙箱的审批 | never | 保留审批:每条命令都要用户点,手机上体验差;而命令只能碰到这台一次性的沙箱 |
| 沙箱里的 envd | 自己编译的上游 0.10.2 | 腾讯云官方镜像只有 v0.5.14,缺文件元数据,dsh 的写入版本防护用不了 |
| 控制面怎么连 dsh | 经 envd 进程 API 直接持有 stdio | 容器内放 relay 回连:多一个进程 |
| 事件协议 | SSE + Last-Event-ID,命令走 REST | WebSocket:双向用不上,移动端重连更复杂 |
| 客户端渲染 | 透传 ACP session/update | 解析 dsh 磁盘日志:词表随版本变化 |
| 流式回复 | dsh 插件把实时增量写进 ACP 流,网关对 dsh-acp 的整段副本去重 | fork dsh-acp(1467 行,跟版本很累);改用 dsh 自己的 HTTP API(另一套协议,和现有 ACP 驱动重复);不做(用户要等整段生成完) |
| 身份 | 设备 token + 首次绑定 | 直接信任 QIMEI36:知道设备号即可读他人对话 |
| 模型配置 | 启动参数 + --patch | 打进镜像:换模型要重建镜像,key 进镜像 |
11. 现状与待办
| 项 | 状态 | 说明 |
|---|---|---|
| 对话、审批、取消、断线续传 | 已上线 | make smoke 真实模型端到端 |
| 工具沙箱模式(AGS)+ Agent Pool + 快照 + 持久 bash | 已上线 :8081 | make smoke-hands PAUSE_CHECK=1 TC_PY=… 全部通过:懒建、工具在沙箱、无 key、文件双向、预热 worker、杀掉全部 worker 对话不丢、持久 shell 跨调用 / 跨轮 / 跨暂停、云 API 停掉沙箱后从快照重建、暂停恢复后后台进程仍在、删会话即停 |
| 设备体系与隔离、文件上传下载 | 已上线 | 含符号链接逃逸实测 |
| 网关重启后恢复对话 | 已上线 | make check-restart(:8080);工具沙箱的重启恢复只有单测,线上未跑 |
| 网页测试台、Android 端 | 已适配 | 都展示工具沙箱状态、快照时间和 hands 事件;Android 单测通过,未上真机 |
| 暂停 24 小时后归档 | 已实现 | 单测覆盖;线上还没等满 24 小时验证 |
| 暂停阈值调短 | 待定 | §3.5 的取舍,等确认 |
| 腾讯云 key 权限过大 | 待换 | 换成只含 AGS 的子账号 |
| 多机 Pool、快照上 COS、多副本、推送、真实账号、HTTPS | 上量前 | §9 |
附录 A. 实测事实
来源:dsh 0.2.1-rc.2 源码与运行、poc/dsh-e2b-s1、poc/ags-lab、线上网关。
| 事实 | 影响 |
|---|---|
会话目录按 cwd 分片:$DSH_HOME/sessions/--workspace--/<uuid>/session.v4.jsonl.zstd | DSH_HOME=/root/.dsh、cwd=/workspace 必须固定,否则 resume 找不到 |
未 flush 的事件在崩溃后视为从未发生;session/close 是 flush 屏障。实测一轮在回复 session/prompt 前已写盘:空闲时 kill -9 后在另一个 worker 上 resume,模型仍记得上一轮 | 停 / 回收前先 close;worker 死掉只丢正在跑的那一轮 |
| AGS 暂停时,已打开的 envd 流不报错、不结束,只是再也没有数据;恢复后按 pid 重新 connect 一切正常(shell 的变量都在) | 长连接(持久 shell 的 PTY)必须靠外部信号(lease epoch)发现失效 |
| dsh 的终端会话是进程内的:dsh 进程退出,持久 shell 就丢了 | 持久 shell 的寿命 = worker 的寿命,所以 Pool 模式下 dsh 空闲 2 小时才释放 |
| GNU tar 在文件被读的同时被改时退出码为 1 | 快照脚本把 1 当成功 |
dsh 日志里每次模型请求都有 usage(inputTokens 为未命中部分、cacheReadTokens、outputTokens)和逐 chunk 时间 | §3 的耗时与费用都从这里算出,精确到请求 |
dsh 每轮、每步都经 ctx.fs 探测 AGENTS.md、CLAUDE.md、.git 等(每步约 17 次) | 远程化后必须缓存;且不能因此建沙箱(§4.3) |
ctx.fs 是全局的:换成远程后,读 $DSH_HOME、~/.agents 配置的插件也跟着去了沙箱 | fs-cube 的 hostPaths 把这两处按“不存在”处理 |
| AGS 对所有沙箱都报告 envdVersion 0.2.10,与镜像里实际的 envd 无关 | E2B SDK 按它禁用 stdin 等功能;cube-runtime 按配置的真实版本建句柄。应反馈给 AGS |
cordis 用 Proxy 包装服务,JS 私有字段(#x)不可用;patch 里不允许 !!js require | 插件用普通字段;路径用 process.env |
dsh-tool-fs-search 按宿主上 rg 的绝对路径起进程 | 工具镜像在同一路径放指向系统 rg 的软链接 |
| AGS 沙箱从公网 PyPI 装包只有 15–20 KB/s;Debian 的 Python 是 externally-managed | 工具镜像预装 pytest,配腾讯云 pip / npm 镜像 |
session/list 不返回当前进程中活动的会话;MCP 声明不随会话持久化 | 不能用它判断会话是否存在;每次 resume 重传 mcpServers |
| envd 不把镜像 ENV 传给子进程 | DSH_HOME/HOME/PATH 由网关显式传入 |
| 运行镜像 1.65 GB(dsh 包约 865 MB);工具镜像约 1 GB | AGS 只拉工具镜像(同地域前 20 个镜像免费预热) |
附录 B. 资料索引
| 文档 | 内容 |
|---|---|
AGENTS.md | 工程介绍、目录、命令、易错规则(给人和 AI 助手) |
docs/api.md | 移动端接口、事件格式、错误码 |
docs/handoff.md | 交接:现状、环境、历史、下一步 |
docs/agent-pool-design.html | 大脑 / 手分离的方案、spike S1–S7 结果、分期 |
docs/cube-restore-report.html | 沙箱能还原到什么程度、代价;§9 AGS 实测(时延、暂停恢复 100 次) |
scripts/measure-turn.py | §3 的测量脚本 |
poc/ags-lab/README.md | AGS 资源清单(角色、镜像、沙箱工具)与踩坑 |
poc/dsh-e2b-s1/README.md | dsh 经 E2B 协议在 AGS 上执行工具的实验 |
docs/research/ | 早期调研(对标产品、AGS 自定义镜像),已归档 |