假设那份项目清单终于整理好了,你让 OpenClaw 顺手跑一遍检查。它返回一段输出,说检查已经结束。你打开自己的项目目录,却没找到预期的报告。
第一反应可能是:它是不是又忘了保存?但还有一种更基础的可能——你和它说的“这里”,根本不是同一个地方。
上一回,我们追着模型故障看它怎样接着工作。这一次,把镜头移到工具真正落地的那一刻。同一句命令,可以在 Gateway 所在的机器、某个沙箱后端,或者连接的 node 上执行。命令字符串没有变,文件、依赖和权限的含义却可能全变了。
故事是假设场景。本文沿用 v2026.5.22 对应提交 a374c3a,重点追踪 Docker 后端;其他沙箱后端不套用 Docker 的隔离结论。我们会从“命令去哪儿”一路走到“结果留下在哪儿”。
先问去哪儿,再问能不能动手
还记得前面那篇工具权限吗?工具策略回答的是:当前 Agent 能不能使用某项能力。沙箱解决另一个问题:被允许的操作,落到什么执行环境里。执行审批则继续约束具体命令。三者互相关联,却不能相互替代。官方边界说明
比如,模型拿到了 exec,不代表它能挑任意一台机器;命令进入容器,也不代表原项目一定不可写。把这两件事混在一起,最容易产生“工具受限了,所以宿主也安全”的错觉。
OpenClaw 先根据会话决定是否启用沙箱。off 不启用,all 覆盖所有会话,non-main 则比较规范化后的会话键与主会话键。这里的 main 是会话身份,不是“名字叫 main 的 Agent”;主会话别名也会先经过规范化。配置解析的默认 mode 是 off。会话判定 · 配置默认值
之后,exec 还要确定自己的目标。配置目标默认为 auto:有可用沙箱运行时就选 sandbox,没有就选 gateway。这个选择发生在工具执行层;它不会为了满足一次调用,凭空变出已经可用的容器。
更关键的是,请求里的 host 不能随便覆盖配置。当配置为 auto、当前已有沙箱时,请求改去 gateway 或 node 会被拒绝;固定目标也不允许请求换到另一个目标。若明确选了 sandbox 却没有运行时,调用方会报错,不会偷偷回落到 Gateway。目标选择算法 · 执行入口的缺失运行时检查
所以,“自动选择”和“明确要求沙箱”表达的是两种不同承诺。前者根据现有环境安排去处,后者要求这个边界确实存在。设计 Agent 平台时,这个差别值得直接显示给用户,而不是藏在一个笼统的“执行中”后面。

换了房间,桌上的文件也会变
回到那份消失的报告。假设命令确实进了 Docker 沙箱,下一步该问:容器里的工作目录,映射的是哪份文件?
沙箱上下文同时保留 agentWorkspaceDir 与 workspaceDir。前者指 Agent 的项目工作区,后者是本次沙箱使用的工作区。workspaceAccess=rw 时,后者直接采用前者;其他模式则选择沙箱目录,准备基础工作区内容,并尝试同步技能文件。这不是“把整个项目复制过去、以后自动双向同步”的承诺。工作区准备
真正决定 Docker 看见什么的是挂载参数。这个固定版本的实现尤其值得看仔细:主工作区挂到配置的容器 workdir,只有 rw 才是可写挂载;none 与 ro 都带只读标记。若允许访问项目且两份目录不同,项目还会挂到 /agent,ro 时仍然只读。挂载参数生成
| 模式 | 主工作目录的来源与权限 | 原项目的额外入口 |
|---|---|---|
none |
沙箱工作区,只读 | 不额外挂载原项目 |
ro |
沙箱工作区,只读 | 不同目录时挂到 /agent,只读 |
rw |
正常准备流程直接采用项目工作区,可写 | 此时两者相同,无需重复挂载 |
这张表只描述 OpenClaw 管理的 Docker 工作区挂载。额外 binds 可以引入其他路径,因此 none 不等于“容器绝对看不见任何宿主文件”。默认 workspaceAccess 是 none,也不能据此断言整个容器没有可写位置;默认配置还提供 /tmp 等 tmpfs。
现在故事就有了几种合理分支:检查只向终端输出,根本没创建报告;命令尝试写只读目录而失败;或者输出写进了容器的临时目录。它们需要不同的修复。最没有帮助的做法,是不看工作目录和退出状态,直接再执行一次。
如果任务确实需要修改原项目,可写挂载意味着变化会触及真实目录。沙箱限制了进程所处环境,却不会把明确开放的写权限变成“以后确认才保存”。因此,一条成功的工具结果应尽量带上实际目标、工作目录和产物路径,让人知道该去哪里核对。

还有一个细节能帮助我们分清“找不到文件”和“写不了文件”。上下文准备 Docker 用户时,若没有显式配置 user,会尝试读取工作目录的数字 UID、GID,并用它们构造容器用户;读取失败或得到无效值时保留原配置。这是在协调挂载文件的身份,不是在给模型增加权限。宿主路径存在、容器能看见路径、当前进程能写入路径,是连续的三道问题。用户身份准备
实际排查时,我会让那份报告沿着这三道问题走一遍:先看命令明确写向哪里,再看这个路径对应哪个挂载,最后看挂载与用户身份是否允许写入。若它在临时目录里,应该显式导出产物;若它没有生成,就回到命令的退出状态。无需把所有失败都归结为模型理解能力。
一个会话一间房,还是大家共用
文件的位置讲清楚了,还有一个问题:下次进来,会不会碰见上次留下的东西?
这取决于 scope。session 按会话隔开,agent 按 Agent 聚合,shared 使用共同的 scope key。当前默认是 agent;配置里还兼容旧的 perSession,但显式 scope 优先。不同会话键可以最终落进同一个 Agent 沙箱,不应把“聊天分开了”理解成“执行环境也分开了”。scope 配置 · scope key 与目录名
目录名也不是直接拼接原始会话键。实现先得到可读的安全片段,截取长度,再附上原始值 SHA-256 的前八位。这样既方便辨认,又减少不同原始键在清洗后变成同名的机会;短哈希并不提供数学上的绝对无碰撞保证。
从设计取舍看,session 更容易让任务彼此独立,却增加环境数量和初始化成本;agent 更容易复用依赖,也让该 Agent 的不同会话共享运行环境;shared 则把复用范围继续扩大。这是根据隔离粒度推导的工程取舍,不是本文测出的性能排名。
还有一处很像日常使用体验:改完配置,旧容器不一定立刻消失。Docker 实现对配置、工作区与挂载格式计算哈希,与已有容器的记录比较。配置不匹配时,正在运行且最近使用过的容器会被保留,并给出重建提示;非热容器才进入删除后重建的路径。热窗口是五分钟,正在运行却没有 last-used 记录也按热容器处理。容器复用与配置哈希
这解释了为什么“配置文件写成了新值”不足以证明“当前命令使用了新环境”。复用减少了频繁启动,也把配置生效变成了需要观察的运行状态。若产品只展示配置而不展示实际容器状态,排查很容易兜圈子。
容器有围墙,也有刻意留出的门
Docker 创建参数将抽象配置变成具体限制:默认只读根文件系统、默认无网络、移除 ALL capabilities,并加上 no-new-privileges。tmpfs、工作区挂载和明确配置的环境变量则让工具仍有完成任务的空间。PID、内存与 CPU 限制有对应参数,但只有配置满足条件时才加入;不能把“支持限制”写成“默认已经设置资源配额”。默认配置 · Docker 参数构造
elevated 又是另一条门。入口会先检查是否启用以及请求者是否被允许;通过这些条件后,目标解析会将非 node 的选择导向 gateway,node 则保留。full 还会改变审批行为。因此,不能把 elevated 笼统说成“在容器里换成 root”,也不能忽略前置授权,把它描述成模型可以随意离开沙箱的开关。elevated 与审批处理
远程 node 则提醒我们另一个容易忽略的边界:路径属于机器。实现只把本次调用明确提供的 cwd 传过去,不把 Gateway 的默认工作区硬塞给远端。Linux 上的项目绝对路径,对另一台 Windows 机器可能毫无意义。这个小分支,让“执行宿主”从配置名变成了真正的文件系统语义。
这也是为什么容器启动脚本里的安装步骤需要单独考虑。实现只在创建新容器并启动后运行 setupCommand,复用既有容器不会每次重跑它;默认无网络时,安装外部依赖也不能假定一定成功。更可预测的做法通常是把稳定依赖准备进镜像,把每次任务的输入与产物留在明确的位置。这是本文的工程建议,不是对所有项目都适用的配置模板。创建与初始化顺序
最后,把执行环境当成结果的一部分
OpenClaw 没有把所有后端都塞进 Docker 实现里。上下文通过注册的 backend factory 创建运行时,再取得运行时标识、工作目录和文件系统桥接能力。Docker 是本文追踪的一种实现;SSH 等后端需要自己的路径和环境约定。浏览器能力也会检查后端是否支持。后端注册 · 上下文装配
如果把这套思路移到自己的 Agent 平台,我会保留几个独立部件:策略判断能力是否开放,目标解析决定去处,后端管理环境生命周期,文件桥接处理路径与读写语义。结果再带回足够的执行证据。这样替换远程执行器时,不需要同时重写工具权限。
本文直接抽取固定源码的连续函数片段,去除 TypeScript 类型后,完成了 26 项局部检查,覆盖普通目标选择、elevated 的目标改写、scope 默认值、会话模式比较和工作区挂载参数。主会话别名解析、完整授权流程、Docker 实际隔离、远程 node 与文件桥接仍是静态阅读,没有声称跑过端到端环境。
那份报告最后应该去哪里找,要由实际命令和执行环境回答。助手说“跑完了”时,多补上“在哪里跑、对哪份文件生效、结果放在哪里”,往往比再长一段成功总结更有用。

