Skip to content

CH 11 · 工具与沙箱

全文字数3434 字预估耗时约 15 分钟前置CH 03–05 已跑通难度可照做

本章目标

前几章你已经见过 Agent 干活:它会自己读文件、跑命令、查网页,涉及敏感操作还会弹窗问你。这一章把"工具"和"沙箱"这两个零件拆开看——工具是怎么被调用的、中间经过哪些检查;沙箱是怎么把 Agent 圈在边界里的、撞到边界会怎样。看懂这两个机制,你就知道它为什么"敢"动你的电脑,以及哪些地方它其实动不了。

工具:Agent 的"手"

dsh 里,Agent 自己动不了任何东西,它所有的"动手"都是通过**工具(tool)**完成的——读文件、跑命令、查网页,每一个都是一件工具。CH 08 说过 tools 是"厨具柜+把关",这里把它拆细一点。

一个工具在系统里长这样:

部分干什么
schema面向模型的"使用说明":名字、描述、参数(JSON Schema)
执行函数真正干活的那段代码
输出声明干完活必须返回什么结构
调度元数据能不能并行、超时多久、怎么在界面上展示

关键在第一条:模型眼里只有一个工具的名字、描述和参数——执行函数、输出声明、超时、并行标记,全部对模型不可见。这是安全的第一层:模型知道"有这个工具、参数这么填",但不知道它内部怎么实现,也没法越权访问执行逻辑。

一次工具调用:一条可扩展的流水线

模型说要调一个工具后,不是直接执行,而是走一条可扩展的流水线。官方把这条流水线做成了每个环节都能被插件拦截、增强的结构——这又是"一切皆插件"(图中省略了一个可选的 finalizeContent 环节,它是工具自己持有的收尾回调,不影响主线):

一次工具调用:从模型请求到权威结果(示意)

按顺序走:

  1. 模型请求:模型发出 tool/call(工具名 + 参数)。参数先做一次校验,不合法直接抛错(INVALID_ARGS),根本不会执行。
  2. pre-execute:第一个检查关卡。这里决定一个调用是 allow(放行)/ deny(拒绝)/ ask(问你)——你在界面里看到的审批弹窗,就发生在这一层。
  3. guard:单调守卫,只能更严不能放松,防止某个环节偷偷放宽边界。
  4. execute:真正执行。沙箱就挂在这一步——命令真正跑之前,先套一层文件壳(见下)。
  5. post-execute:检查结果,需要时可以替换结果。
  6. result:产出权威结果,回填给模型,进入下一轮。

每一步都能被插件挂上钩子——这也是为什么后面写插件时,你可以做一个"拦截某些工具调用"的插件(CH 21 会讲钩子)。读懂这条流水线,你就知道审批、沙箱、日志这些"安全件"都装在什么位置。

沙箱:给命令套一层"文件壳"

CH 04 讲过权限三档(read-only / workspace-write / danger-full-access),那是站在你操作界面时的视角。这里看机制:沙箱只管文件系统效果,网络和进程可见性不在它的管辖范围内

官方把沙箱设计成"策略"和"后端"两层分开:

沙箱:策略说边界,后端来执行(示意)

  • 策略(SandboxPolicy):每次调用都重新解析一次——模式 + 工作区根目录。工作区根从当前会话的 cwd 派生。
  • 后端(SandboxProvider):把命令包装成当前平台下的受限进程。三个平台各有实现——Linux 用 bwrap / Landlock(内核级无特权访问控制),macOS 用 Seatbelt,Windows 用 ACL 受限令牌运行器。

几个值得记住的设计:

fail-closed(失败即关闭):这是它安全性的关键。如果当前环境没有可用的沙箱后端,系统会直接报 SANDBOX_UNAVAILABLE 错误,绝不静默降级成"不带沙箱裸跑"。宁可拒绝执行,也不冒险放开。

danger-full-access 不套壳:只有受限模式(read-only / workspace-write)会走沙箱包装。全权限模式直接 spawn 原始命令,不做文件隔离——这也是为什么切第三档前界面会二次确认。

强制完整性分 full / partial:大多数时候后端能管住全部承诺的文件效果(full);但在较旧的 Linux 内核 ABI 或 Windows 的某些边界上,只能管住一部分(partial),要求绝对保证的场景必须知道这一点。普通日常使用,默认的 workspace-write 就够稳。

动手:亲眼看看

第 1 步:在轨迹里看一次工具调用

在 Web UI 跑一个会动手的任务(比如 CH 05 那种总结仓库的活),跑完切到轨迹标签,点开任意一条 TOOL 行。右侧面板有四个关键页签,正好对应上面说的工具结构:

  • Schema:这个工具的"使用说明"(名字、描述、参数)
  • Payload:这次实际传的参数
  • Result:返回的结果
  • Summary / Timing:摘要和耗时

下图就是一次真实调用:左侧时间线选中一条 TOOL 行(web_search),右侧面板展开全部页签,底部还能看到整轮统计——注意中间还有两条 pwsh 命令因为网络问题失败了,Agent 随即换 web_search 继续,这正是"工具出问题会换路"的真实写照:

轨迹里一条工具调用的详情:左侧选中 TOOL 行,右侧展开 Schema / Payload / Result 页签

第 2 步:看一次审批

在默认的 workspace-write 权限下,让 Agent 往工作区写个文件。我这里让它往 E:\software-workspace\doubaowork\doubao 下新增一个打招呼文件——这个目录不在当前工作区里:

让 Agent 写工作区外的文件:指令输入

注意,实际发生的是两步

  1. 第一次写入直接撞墙——轨迹里出现 Write · Error: [sandbox: file access denied under workspace-write mode]
  2. Agent 意识到目标在工作区外,主动申请提权,pre-execute 那层的 ask 弹窗这时才弹出:把沙箱升级到 danger-full-access,并说明原因。底部两个按钮——拒绝允许一次

提权审批弹窗:拒绝 / 允许一次

点"允许一次",它只放行这一次写入;点"拒绝",它就得换方案。

第 3 步:切到 read-only 再看

在输入框敲 /permission,切到 read-only(输入后界面会显示 permission · preset read-only),再让 Agent 写个文件。结果和上面很像,但有一处关键差别

read-only 模式下写入被拒:报错 + 提权申请

  1. 第一次写入同样撞墙——但报错不同:Write · Error: [sandbox: file access denied under read-only mode]
  2. Agent 同样会申请提权——但这次目标是 escalate sandbox to workspace-write,而不是 danger-full-access(它只需要一个普通写入权限,没必要一步拉满)。

对比三种"被拦"就清楚了:不管在哪个权限档位,写不进去时 Agent 都会先撞一次墙,再弹窗问你。差别在报错信息(workspace-write mode / read-only mode)和它申请的下一档权限(danger-full-access / workspace-write)。"拒绝"按钮始终在——这就是沙箱设计的核心:Agent 可以"请求",但"给不给"永远是你说了算。

这一章你学到了什么

能自己完成下面几条,就算过关:

  • [ ] 能说出一个工具由哪几部分组成,以及哪些对模型可见、哪些不可见
  • [ ] 能画出工具执行的流水线(模型请求 → pre-execute → guard → execute → post-execute → result),说出审批和沙箱各挂在哪一层
  • [ ] 能解释沙箱"只管文件系统效果"是什么意思,以及为什么
  • [ ] 能说清 fail-closed:没有沙箱后端时系统会怎样(报 SANDBOX_UNAVAILABLE,不裸跑)
  • [ ] 能在轨迹里点开一次工具调用,看懂 Schema / Payload / Result
  • [ ] 能说清权限档位不同时,写入被拒的报错(workspace-write mode / read-only mode)和 Agent 申请的提权目标(danger-full-access / workspace-write)分别是什么
  • [ ] 能说明白:写不进去时 Agent 会先撞墙再弹窗,但"拒绝"永远在你手里

Open Source · MIT · Community Driven