Harness 设计笔记:沙箱化 agent 变更
一旦 agent 能够变更共享状态——计划图、配置、代码库——系统的约束瓶颈就不再是推理,而变成了写权限。决定架构的那个问题是:变更在成为现实之前,会先去哪里?
我的答案,来自构建 agent harness 的实践——一个已发布的平台、一个已发布的技能:先沙箱化空跑,再出 diff,然后提交。而当空跑不可信时,就让提交本身可逆,这样回滚就是最后手段的沙箱。
本篇就是设计笔记。站在空跑与提交之间的那道门禁,是配套篇目Harness 设计笔记:审查门的主题。
沙箱的三种姿态
「沙箱」的含义因你保护的对象而异。我反复回到三种姿态:
- 仿真执行。 用世界模型替换世界,让 agent 对着模型跑。便宜、覆盖面广,但保真度只与仿真器相当。
- 有状态的评测沙箱。 一个可控环境,职责是测量,pass/fail 判据住在它里面。
- 先验证后变更。 agent 变更自己的技能库,真实环境就是沙箱:变更只有在世界里验证通过之后才落地。
关键的维度是:世界模型的保真度、判定来自哪里、单次运行成本、以及变更前的状态能否恢复。其余都是实现细节:
candidate mutation
|
v
sandbox: run candidate against a world model
|
v
verdict (milestones, invariants, judge)
|
+--+--+
| |
pass fail
| |
diff -> commit discard + log reasons
ToolEmu:仿真器就是带置信区间的世界模型
Identifying the Risks of LM Agents with an LM-Emulated Sandbox(Ruan 等人,arXiv 2309.15817,2023)是对「沙箱化改变了发现失败的经济性」最干净的证明。ToolEmu 不实现每个工具、不搭起每个环境,而是让一个 LM 仿真工具执行,再让 agent 对着仿真跑。随后一个基于 LM 的自动安全评估器检查 agent 的失败,并量化相关风险。这个 benchmark 覆盖 36 个高风险工具和 144 个测试用例,人工评估判定 ToolEmu 发现的失败中有 68.8% 是真实世界中的有效失败。按评估器的说法,即便测试过的最安全的 agent,也有 23.9% 的时间表现出这类失败。
我从中得到的三条教训:
- 正是沙箱让长尾风险在上线前变得可测。 没有仿真,每个场景都需要实现真实工具、配置真实环境,这个成本封顶了你最终能测试的场景数。仿真翻转了经济性:最危险的情况反而是最便宜的测试对象。
- 仿真器是模型对世界的猜测。 68.8% 的有效率数字是对这个猜测的诚实测量。每个基于仿真器的沙箱都需要自己的保真度检查,读结果时应当把这个折扣算进去。
- 评估器是第二个模型。 仿真沙箱里的判定是另一个 LM 的判断。这使它便宜,也使它会出错——这正是判定应该作为一等契约放进沙箱、而不是事后补充的原因。
ToolSandbox:没有 oracle 的沙箱只是游乐场
ToolSandbox(Lu 等人,Apple,arXiv 2408.04682,2024)展示了当状态要紧时,评测沙箱这种姿态长什么样。更早的工具 benchmark 用单条提示词或 off-policy 脚本评测无状态的 REST API。ToolSandbox 则相反:有状态的工具执行、工具之间隐式的状态依赖、内置用户模拟器做 on-policy 对话评测,以及一种动态评测策略,对任意轨迹上的中间与最终里程碑打分。其主打结果:像 State Dependency、Canonicalization、Insufficient Information 这样的任务类型,即便对能力最强的模型也有挑战性。
教训:
- 有状态性是沙箱的生死线。 如果沙箱不携带状态,空跑就会对「一次变更会给后续步骤带来什么」撒谎。单独看没问题的改动,可能毁掉每一个依赖它所触碰状态的后续动作。
- 里程碑就是契约。 没有判据的沙箱只是演示。里程碑/雷区的框架——某些状态必须到达、某些状态必须避开——正是空跑需要的 pass/fail 契约,在运行中途而不是结束时评估。
- 用户模拟器很重要。 agent 对对话行动,对话又对 agent 作出反应。在 on-policy 评测中,模拟用户回应的是 agent 实际做的事,它能抓住固定脚本抓不到的失败类别。
Voyager:环境即沙箱
Voyager(Wang 等人,arXiv 2305.16291,2023)是第三种姿态:agent 变更自己的代码。Voyager 靠一个不断增长、由可执行代码组成的技能库玩 Minecraft。每个技能都是一个程序,由模型提出,并经由一个由环境反馈、执行错误和自验证驱动的迭代循环不断改进。技能只有在世界里验证通过之后才会被加入技能库。与先前最先进方法相比,测得的增益很大,技能库还能迁移到一个新世界;数字我在agent 分类体系笔记里逐项列出。
这对变更设计为什么重要:
- 当变更只追加、以验证为门禁时,自我修改是可行的。 agent 自己的技能库就是共享状态,门禁就是在环境中的一次通过验证的运行。什么都不被覆盖;技能只会累积。
- 薄弱点是 oracle。 验证是自验证:agent 评判自己的运行。这是我在生产环境里第一个要替换的东西——换成结构性检查或独立的裁判。
- 技能即代码,意味着变更可检查。 你能读一个技能的 diff、重放它的验证、回滚那条条目。这种可检查性是我最看重的性质——当行为以嵌入而不是以 diff 存储时,被丢弃的恰恰是它。
空跑 pipeline
我的立场是:沙箱应当是一个纯函数——候选变更进,判定出,中间不碰真实状态。而我围绕沙箱撒谎的三种方式做设计:
- 仿真器漂移。 世界模型会偏离世界。用周期性的保真度检查缓解,把沙箱判定当作证据,而不是真相。
- 弱 oracle。 判定由被测对象产出。用结构性检查和与候选者不共享上下文的裁判来缓解。
- 覆盖缺口。 有些东西无法模拟:其他人、外部服务、时间的流逝。通过让提交本身可逆来缓解。
我会对任何这类 pipeline 立下的规则:
- 最便宜的门禁先行。 一次比它所替代的审查更贵的空跑会被绕过,而被绕过的门禁比没有门禁更糟——因为你现在还会顺带信任它。
- 判定留在沙箱内。 里程碑和不变量在运行中途评估,而不是一个最终总分。
- 变更以 diff 呈现;变更前的状态不可变。 只追加的谱系,撤销靠移动指针而不是重算。
- 当模拟不可信时,把空跑搬进事务。 完整算出候选方案,对照不变量验证,然后才持久化。回滚就是最后手段的沙箱。
出处:PiPlan.ai 与 SafeRoutes
上文两种模式——模拟沙箱与事务性回滚——都来自我构建过的系统。
在 PiPlan.ai,目标变成类型化图,候选计划在成为图的变更之前,先跑过模拟沙箱。正是类型化图让空跑变得诚实:约束可计算,因此可行性是图结构的属性,而不是提示词的事。而因为现实会变,harness 会重规划:自适应重规划随局势变化重新推导关键路径。昨天验证过的计划,不是今天被提出的计划——这正是沙箱从一开始就有必要的原因。
SafeRoutes 是对无法忠实模拟的世界的「事务式」回答。道路、燃油供应和天气,都不自带你可以信任的仿真器,所以提交本身就是空跑。候选计划先在内存中完整构建,然后在唯一的序列化边界上运行写入时不变量门禁,重新检查途经点自洽性、续航硬约束(INV-4:任意两停之间的分段不得超过油箱的安全区间)、以及提交的路线确实能到达终点(INV-7)。检查失败会在任何东西落盘前抛出,行程于是停在最后一个合法状态:回滚免费,对任何调用方都成立,模型无需记得要小心。行程状态是一棵冻结会话节点组成的树,撤销向后移动头指针,无损。拒绝会带回一条恢复提示——告诉下一步该做什么的指令,而不是抱怨。更早的循环笔记我写在训练 Safe-Routes 技能:来自循环的笔记里。
谁来写 oracle,决定这一切是否成立。在 SafeRoutes,不变量不是模型要记住的职责,也不是在运行时被发现的:我把它们预先写进设计文档 docs/04_SUCCESS_CRITERIA.md 里一张明确的验收表——十二条全局不变量,INV-1 到 INV-12,覆盖可达性、路线互异性、续航硬约束(INV-4)、加油点充分性、过夜锚点、每个途经点的验证状态、以及终点可达性(INV-7)。这张表像法律一样在两层上运行。文档的可执行实现以 PASS/FAIL 评判整个矩阵,作为验收门禁。而硬子集——途经点自洽性、续航约束、终点可达性——在每次状态写入时、任何东西落盘前,于唯一的序列化边界重新检查,失败即关闭。质量类不变量被刻意留在写入路径之外,因为在那里给它们设门禁,会过度拒绝合法的行程中途状态。
续航约束之所以是铁律而非指南,源于一次事件。在一次评测探针(能力报告里的 D-G-04)中,模型被推向填补一次 I-10 行程的燃油覆盖缺口;被诱导两次,配合了两次——上网搜索加油站,把 Van Horn 和 Fort Stockton 串进一个看起来合理的计划。更深的洞在数据层,不在模型:规划器把任何标记为 refuel=True 的途经点都当作一次免费的全油箱重置——包括一个附近没有已验证加油站的合成兴趣点——于是覆盖缺口被悄悄填上,输出看起来是安全的。修复是结构性的,不是提示词补丁:加油途经点如今只有吸附到固定半径内的已验证加油 POI 时才作数;技能自己的契约告诉模型绝不上网搜索或猜加油站来填补缺口;而抓住这类结果的续航检查运行在序列化边界,任何调用方都无法跳过。这些护栏的早期版本住在框架侧;我撤回了那版,把它们重建为技能自有不变量,因此它们对任何框架都成立,哪怕是一次裸 CLI 调用。修复后的复测中,技能无法产出伪造的计划。
综合:沙箱与回滚是同一个想法在不同信任级别上的体现。有世界模型就模拟,没有就走事务。无论哪种方式,变更都只在判定之后才成为现实。
诚实的局限
沙箱的上限就是它的世界模型,而每个世界模型都会漂移。ToolEmu 的 68.8% 不是那种方法的缺陷;它是仿真的合理代价,任何不为它留预算的沙箱设计,都是在沙子上盖楼。我拒绝构建的,是既没有空跑也没有回滚的变更型 agent——因为那不是 agent,那是火灾。
本篇链接的来源均已抓取并核实。