CloudSandbox 架构设计

在手机上(iOS / Android)驱动一台远程电脑运行 dsh(DeepSeek Harness),完成编码等任务。本文描述当前已部署的实现:dsh(大脑)是我们服务器上 Agent Pool 容器里的一个进程,模型的工具(持久 bash、读写文件)在每会话一台的腾讯云 AGS 沙箱(手)里执行,工作区随时有快照可以重建。§3 用实测数据给出一次完整对话的流程、耗时和开销。
更新:2026-11-02 · 对应代码:仓库根目录 · 接口细节见 docs/api.md · 方案演进见 docs/agent-pool-design.html

1. 概览

大脑和手分开dsh 是共享容器 cs-pool 里的一个进程(worker),持有对话和模型 key;它的 ctx.fs / ctx.subprocess 换成我们写的插件,经 E2B 协议把工具打到 AGS 沙箱。沙箱里没有 dsh,也没有任何 key。
大脑现成、可替换池里常驻 2 个启动好的 dsh,会话拿一个只要约 0.04s。所有 worker 共用一份 dsh 会话日志,任何一个死了,下一条消息换一个接着聊(实测杀掉全部 worker,对话不丢)。
手按需创建、空闲暂停、可重建只聊天的会话不建沙箱。第一次用工具时才建(约 2.4s);空闲 2 分钟先快照、再带内存暂停,进程、文件、shell 变量都在,下次用工具约 2s 恢复;暂停 24 小时后只留快照;沙箱丢了从快照重建。
网关是唯一入口移动端只连网关:REST 发命令,SSE 收事件。网关经 envd 持有 dsh 的 stdio(它就是 ACP 客户端),也是沙箱的唯一管理者:dsh 只能凭会话级 token 向网关要租约。
用户 = 设备(QIMEI36)QIMEI36 只是标识,不是凭证。设备用 App Key 注册换取随机 token,首次注册即绑定。他人的会话一律 404。

三家对标产品(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 msread 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)
envdE2B 开源的沙箱内守护进程(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. 系统结构

客户端 iOS / Android对话 · 审批 · 文件 网页测试台嵌入网关,同源 我们的服务器 · 9.135.181.73 · Docker(/state 盘) gatewayGo · systemd · :8081 HTTP · 鉴权 · SSE 扇出 设备 · token · 配额 会话 · 事件 · Agent Pool ACP 客户端 · 审批挂起 hands.Broker · 快照 文件接口(经 envd) 无状态:重启后从磁盘恢复 Agent Poolcs-pool · 大脑 envd 0.5.14127.0.0.1 · 不鉴权 dsh worker × N每会话一个 · 常驻 2 个预热 cube-runtime · fs-cubesubprocess-cube · 持久 bash worker 无状态:日志在宿主机共享目录 宿主机磁盘/state/cloudsandbox/data-hands devices/<qimei36>.jsontoken 哈希 sessions/<id>/meta.json归属 · dsh 会话 id · hands 记录 sessions/<id>/events.jsonl时间线(给 App 回放) sessions/<id>/workspace.tgz工作区快照 dsh/--workspace--/<uuid>/所有 worker 共用 ⇄ /root/.dsh DeepSeek 官方 APIdeepseek-flash OpenAI 兼容 · 前缀缓存命中约 95% key 只在 Pool 的 dsh 进程里 腾讯云 AGS · 广州 工具沙箱每会话一台 · 手 cs-tools-022 · 2C4G · 盘 20Gi · 公网出网 envd 0.10.2每实例访问令牌 · HTTPS 49983-<id> /workspace · bash · 进程git · python · pytest · node · rg 没有 dsh,没有任何 key nonce 文件:/var/lib/cloudsandbox/nonce AGS 控制面(云 API) StartSandboxInstancePause(Memory) · ResumeUpdateSandboxInstance · Stop REST SSE stdin stdout ①租约 HTTPS ②E2Benvd ③文件接口:列目录 · 上传 · 下载(Range)· 删除 ④建 · 暂停 · 恢复 · 停

图 1 工具沙箱模式。蓝:客户端与网关之间的唯一通路;绿:网关经 Pool 容器里的 envd 持有每个 dsh worker 的 stdio;紫:dsh 直连模型;橙:工具调用与沙箱管理。① worker 的插件凭自己的 token(只在分配给某个会话期间有效)向网关要租约;② 拿租约直连沙箱的 envd 执行工具;③ App 的文件接口经网关读写沙箱,已归档时直接读快照;④ 沙箱的生命周期(含快照、归档、重建)只由网关管理。本地工具模式下没有右半边,每个会话一个容器,/workspace 是宿主机目录 bind mount 进去。

组件

组件位置职责
gatewaycmd/gateway进程入口;配置只来自环境变量(systemd EnvironmentFile,0600);每分钟回收空闲会话,每 30 秒检查一次该暂停的沙箱;SIGTERM 时 flush 所有 dsh、暂停所有沙箱再退出
HTTP APIinternal/gateway/http.go路由、三种凭据的鉴权、请求日志(从不记录 Authorization)、SSE 续传与心跳、内部租约接口
会话internal/gateway/session.go状态机、轮次、审批挂起、事件追加与扇出、拉起与关闭 dsh;给 dsh 传 CS_HANDS_URL / CS_HANDS_TOKEN
管理器internal/gateway/manager.go按归属查找会话、配额与名额、启动时从磁盘加载并修复中断的轮次;按 token 找会话发租约
Agent Poolinternal/gateway/pool.go预热 worker、分配给会话(ACP 流量和租约 token 都转到这个会话)、补齐、worker 死掉时丢弃;Pool 容器第一次使用时重启或按新镜像重建
工作区快照internal/gateway/snapshot.go在沙箱里 tar 打包并拉回、还原到新沙箱、已归档时只读地列目录和下载(只把 tar 当数据流读,从不在宿主机解包)
Brokerinternal/handshands.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/acpNDJSON 上的 JSON-RPC 2.0;把 dsh 的审批请求交给上层异步回答
容器运行时internal/sandboxDocker:每会话一个容器(本地模式)或一个 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. 第 1 轮(冷):“写 wordfreq.py 的 top_words(text, n),再写至少 4 个 pytest 用例并跑通”。新会话:dsh 是建会话时从池里拿的预热 worker,沙箱要在第一次用工具时创建。
  2. 第 2 轮(热):紧接着发“加一个可选参数 stopwords,补两个测试,重新跑 pytest”。
  3. 第 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 末尾。

热轮次墙钟(改代码 + 跑测试)10.5–13.9s模型占 87–93%
新会话拿到 dsh0.04s池里预热的 worker;以前冷启动 2.4s
第一次用工具额外耗时≈ 3.2s建沙箱 2.0–2.2s + 第一个 shell 约 1s
暂停后第一轮额外耗时≈ 1.9s恢复 1.7–1.8s + 校验,shell 状态还在
三轮对话的模型费用0.014–0.023 元高峰翻倍 · 缓存命中 97%
三轮对话的 AGS 费用≈ 0.08 元大头是空闲到暂停的 2 分钟

3.1 流程

App gateway dsh(Pool worker) 工具沙箱 DeepSeek POST /prompt 202 {turnId},立即返回 仅当会话还没有 dsh(新会话 / 空闲 2 小时后 / worker 死掉后) 取预热 worker · session/new|resume 约 0.04s;池空时现起一个约 2.1s · SSE:status starting → idle(带 startup) session/prompt 每一轮开始:dsh 找 AGENTS.md / .git(每步约 17 次 stat) 租约 ?ifExists=1 没有沙箱 → 204:当作空工作区,不创建 有、但已暂停 → 网关恢复它:1.7–2.2s(第 3 轮) 循环:每一步 = 一次模型请求 + 0..n 个工具调用(本例每轮 2–5 步) 请求(上下文前缀命中缓存) 首 token 0.4–1.8s,p50 0.75s;整步 0.7–7.9s session/update SSE 思考 / 回复,约 80ms 一片 SSE tool_call 只有本会话第一次用工具(或上传文件)时 POST 租约 StartSandboxInstance → 等 RUNNING → 写 nonce 2.1–2.6s(第 1 轮那条 bash 显示 3.0–4.7s 就是因为它) {sandboxId, token},30s 内复用 envd:stat · 读写文件 · 起命令(1–6 次往返) read 30–100 ms · write / edit 100–150 ms · bash 160–500 ms SSE tool_call_update 工具结果拼进上下文,回到循环开头再请求模型 输出 token 是模型费用的大头(§3.4) bash 是持久 shell:cd / export 跨调用、跨轮、跨暂停保留 最后一步:只有回复,没有工具 prompt 结果 SSE turn_end 之后:用过工具 → 后台快照 /workspace;空闲 2 分钟 → 再快照 → 带内存暂停;暂停 24 小时 → 停掉只留快照 dsh 空闲 2 小时 → session/close,杀掉 worker · 断线:带 Last-Event-ID 重连补发 · 取消:POST /cancel

图 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 实测时间线

0s 2s 4s 6s 8s 10s 12s 14s 16s 18s 20s 22s 第 1 轮 · 冷20.7s 建沙箱 bash 写代码 1425 tok 模型 14.6 · 建沙箱 2.2 · 工具 3.5 · 其他 0.4 第 2 轮 · 热13.2s 读改 710 tok 改代码 1184 tok 模型 12.3 · 工具 0.8 第 3 轮 · 暂停后4.4s 恢复 恢复 1.7 · 模型 1.9 · 工具 0.4 模型:等首 token 模型:生成 工具(AGS 沙箱) 建 / 恢复沙箱 网关 / 校验

图 3 第 2 次实测的三轮,横轴是从 App 发出消息算起的秒数。每段模型请求里,浅色是等首 token,深色是生成。第 1 轮的第一条 bash 前面先建沙箱;第 3 轮开头的橙色是恢复暂停的沙箱,发生在 dsh 找 AGENTS.md 时,所以在第一次模型请求之前。三轮里都看不到 dsh 启动:worker 是建会话时从池里拿的。

3.3 耗时拆解

轮次墙钟首个输出模型工具沙箱建 / 恢复其他步数 · 工具数
第 1 轮(冷)11.7–22.7s1.5–3.6s7.2–18.1s1.9–3.5s建 2.0–2.2s0.4s4–5 步 · 4–6 个
第 2 轮(热)10.5–13.9s1.6–5.2s9.1–12.8s0.7–1.1s—0.2–0.4s4–7 步 · 5–7 个
第 3 轮(暂停后)4.4–4.6s3.1–3.4s1.9–2.0s0.4s恢复 1.7–1.8s0.3–0.4s2 步 · 1 个
第 3 轮(未暂停,对照)2.5s1.3s1.9s0.4s—0.2s2 步 · 1 个

“首个输出”是 App 收到第一条思考 / 回复 / 工具事件的时刻。“其他”是网关、ACP 往返和 dsh 自身的处理。dsh 的“启动”一列不见了:建会话时已经从池里拿到了 worker(日志里 action=warm totalMs=37)。

工具样本耗时中位说明
read660–130 ms99 ms路径与 stat 按“代”缓存;同一步内并行读两个文件
write698–227 ms128 ms写临时文件 → 一条命令 chmod 并原子发布 → stat
edit795–127 ms115 ms先读、校验版本,再按 write 发布
bash(持久 shell)12133–865 ms381 ms含命令本身的执行时间(pytest 约 0.2–0.5s)。命令经 PTY 发送,结束靠轮询输出里的结束标记,所以比一次性 bash 多约 0.1s
glob1918 ms在沙箱里起 rg
bash(会话的第一条)33.1–3.5s= 建沙箱 2.0–2.2s + 在沙箱里起第一个持久 shell 约 1s + 命令

结论

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 轮530,2081,1122,0910.0101 / 0.0202 元
第 2 轮436,0961,5901,9900.0103 / 0.0205 元
第 3 轮221,120357620.0010 / 0.0021 元
合计1187,4243,0594,1430.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 轮(连续)约 31s120–150s150–180s0.040–0.047 元
第 3 轮约 4s120–150s125–155s0.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 元)

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:预热几个 worker2 个(约 360 MiB)同时来的新会话超过它,多出的那个现起一个,约 2.1s
何时建沙箱第一次用工具时(懒)收到第一条消息就并行预建,可以把第一次用工具的约 3.2s 藏在模型第一步的思考里;代价是只聊天的会话也付至少一段尾巴(约 0.035 元)
何时恢复沙箱每轮开头找 AGENTS.md 时App 打开会话、用户开始输入时就预恢复,把 2 秒藏到打字时间里;或者暂停时把“查找”当作缓存命中,只有真正用工具才恢复
沙箱规格2C4G1C2G 运行费减半(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 在跑而沙箱还没建。对话和数据在任何状态下都保留。

大脑:会话的 dsh · 状态字段 status dsh 空闲 2 小时(GATEWAY_IDLE_STOP):session/close(flush)→ 杀掉 worker;worker 死掉也回到这里 创建 stopped没有 dsh · 不占名额 starting取 worker 0.04s / 现起 2.1s idleworker 已分配 running轮次进行中 发消息 / 预热 resume / new session/prompt turn_end 手:工具沙箱(AGS)· 字段 sandbox.hands.state · 事件 hands none没建过 · 工作区为空 starting建 2.1–2.6s running0.94 元 / 小时 paused带内存 · 0.25 元 / 天 archived只有快照 · ≈ 0 首次工具 就绪·nonce 空闲 2 分钟先快照 任何租约:恢复 1.7–2.5s 暂停24 小时 再用工具:新建沙箱并从快照还原(restore) 实例丢失 / nonce 不符→ 重建并还原快照(reset) 用过工具的一轮结束后、文件接口改动后:后台快照 删会话:StopSandboxInstance(任何状态),快照随会话目录删除

图 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 字段和服务日志。

路径耗时(1 格 = 0.5s) 00.5s1s1.5s2s2.5s3s 热:dsh 在跑直接发 session/prompt ≈ 0 空闲休眠后唤醒docker start · session/resume 2.28s 网关重启或崩溃后docker restart · session/resume 2.39s 新建会话docker run · session/new 2.51s(用户感知) 同时新建 6 个每个会话的耗时 ≈ 3.05s 容器(run / start / restart) dsh 启动:Node 加载插件,到应答 initialize session/new 或 resume 网关与 HTTP

图 5 本地工具模式下 dsh 各启动路径的分段耗时。envd 就绪、拉起 dsh 进程各只需 1–2ms,在图上看不见。工具沙箱模式下从池里拿 worker 约 0.04s,图上就是第一行的“≈ 0”。

路径样本容器dsh 启动会话合计
新建会话(create)4 + 30.28–0.35s1.98–2.08s0.05–0.06s2.32–2.43s;用户从创建到可用 2.48–2.60s
空闲休眠后唤醒(start)40.23–0.24s1.95–2.00s0.06s(resume)2.26–2.30s
网关重启 / 崩溃后(restart)40.36–0.39s1.92–1.95s0.06s(resume)2.37–2.41s
同时新建 6 个2 轮 × 60.38–0.54s2.12–2.33s0.05s用户感知 2.95–3.16s
同时新建 8 个(上限 6)1 轮6 个在 2.96–3.16s 内就绪,另外 2 个立即被拒绝(server busy)
休眠(stop)4close + 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 后到 PAUSEDS3
单次 envd 往返p50 21–32 msS2
停止(删会话)API 立即返回,实例 STOPPEDsmoke

结论:约 85% 的 dsh 启动时间花在 Node 加载 dsh 和它的约 250 个插件包上;容器本身 0.2–0.4s。恢复对话不比新建慢(session/resume 只多约 10ms)。启动耗时远小于模型耗时。并发启动有轻微争用(6 个同时,每个慢 0.5–0.6s),名额检查原本有竞态,现在启动一开始就占住名额(TestAdmitIsAtomic)。

4.3 实现约束

规则原因
停 dsh 前必须 session/closedsh 只持久化已 flush 的内容;session/close 是 flush 屏障(附录 A)
从 stopped 恢复时先 session/resume,失败再 session/new 并发出 error 事件恢复失败不应让用户卡住,但必须告知上下文丢了
网关重启后首次使用会 docker restart 容器旧网关拉起的 dsh 可能还活着,不能出现两个 dsh 写同一会话
envd 的输出流绑定 context.BackgroundSDK 用调用方 ctx 控制整条流,单次超时会掐断会话
dsh 经 /bin/sh dsh-acp 启动,脚本以 exec 结尾envd 按 pid 发 SIGKILL,包装进程会让 dsh 变孤儿
名额在检查的同时占住(Manager.admit)只数已经起来的 dsh,会让并发启动全部越过上限
先拿 hand 的锁,再拿 Session.muBroker 的保存、通知回调在持有 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 KeyX-App-Key,仅注册接口注册设备环境变量;内置于客户端
设备 token cs_…Authorization: Bearer访问本设备的会话与文件服务器只存 SHA-256;客户端存 Keychain / Keystore
Admin tokenAuthorization: Bearer列出 / 重置设备;读不到任何会话环境变量;只给管理员
hands token(内部)Authorization: Bearer,仅 /internal/hands/lease拿到本会话沙箱的租约只在网关内存和该会话 dsh 的环境变量里;网关重启即换
腾讯云 SecretId / Key网关调用云 API管理 AGS 实例服务器 gateway-hands.env(0600);不进任何容器 当前权限过大,待换

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 在列表里隐藏,直接访问 404smoke
快照里的符号链接、..、绝对路径不适用已归档时从快照读:只按条目名匹配,.. 和绝对路径的条目跳过,只能下载普通文件;从不在宿主机解包单测

工具沙箱模式下,沙箱不存在时浏览返回空列表、不创建;已归档时列目录和下载(含 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 配置留有备份。

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 之后的阶段):

AppREST + SSE 移动推送审批 / 完成提醒 CLBHTTPS gateway × N无状态 会话路由:Redis 租约 事件:Redis Stream + MySQL hands.Broker(已有) 账号:企业微信 / QQ 与现网代码同构,换存储实现 Agent Pool × M 预热 worker(已有) 跨多台机器 会话目录放 CFS 单机版已上线 AGS 工具沙箱 每会话一台(已有) 暂停 → 归档(已有) 镜像预热、配额 TDSQL-C会话 · 设备 · 事件 Redisseq · 租约 · 热事件 COS 工作区快照(现在在本机盘)任何网关副本都能还原 ACP E2B

图 6 下一步。Broker、单机 Agent Pool、沙箱与快照都已在线上;新增的是多副本网关、多机 Pool、云上存储。

能力现在(单机)下一步
大脑一个 Pool 容器,常驻 2 个预热 worker,名额 24Pool 跨多台机器;dsh 会话目录放 CFS(或分配时从 COS 取回),任何机器的 worker 都能 resume
手AGS 沙箱:懒建、2 分钟快照 + 暂停、24 小时归档、丢失重建调短暂停阈值(§3.5);镜像预热;AGS 配额
工作区快照本机磁盘,整份 tar.gzCOS;大工作区改增量(git bundle 或 rsync 式)
事件与元数据本机 events.jsonl / meta.jsonRedis 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 这类小文件密集操作会慢
bashdsh 自带的持久 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,命令走 RESTWebSocket:双向用不上,移动端重连更复杂
客户端渲染透传 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已上线 :8081make 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.zstdDSH_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 GBAGS 只拉工具镜像(同地域前 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.mdAGS 资源清单(角色、镜像、沙箱工具)与踩坑
poc/dsh-e2b-s1/README.mddsh 经 E2B 协议在 AGS 上执行工具的实验
docs/research/早期调研(对标产品、AGS 自定义镜像),已归档