跳转至

实践三:增加权限判断与崩溃恢复

上一项:最小 Agent Loop · 返回课程总目录 · 下一项:独立 Eval

这一项不会再加工具,而是要把编辑工具获准、开始执行和留下记录这三个阶段分清楚。如果三个阶段共用一个成功标记,恢复器就判断不了副作用究竟走到了哪一步。最危险的情况是工具已经获准执行,却还没来得及写下能够结算这次调用的完成记录。这一步不能猜。编辑可能已经落盘,进程却在记录结果之前崩溃了。恢复器只有同时核对事件和环境里的真实状态,才能决定是继续推进、执行补偿,还是交给人工检查。

先观察权限拒绝

最小示例把 approve() 作为 Harness 的策略入口,测试会让 readtest 获准,同时拒绝 edit,然后检查以下事实:

  • 记录中出现 tool_denied
  • 不出现对应 tool_startedtool_completed
  • 虚拟源码保持原样;
  • Model 收到带原 Call ID 的拒绝结果。

这些断言只能证明应用层拒绝以后,Harness 没有调用虚拟 Tool Body,却无法证明 OS Sandbox 已经生效,因为整个示例仍跑在普通 Node.js 进程里。

为副作用写三阶段事件

真实工具至少需要:

tool_requested:模型提出动作,尚未执行
tool_started:权限已经通过,副作用可能开始
tool_completed:结果已保存,可以安全进入下一轮

如果最后一条记录是 tool_requested,通常可以认为副作用还没开始,因为 Harness 尚未放行这次执行。一旦记录停在 tool_started,日志只能证明工具已经开始跑,却说不清副作用有没有做完。这两种状态差得很远。恢复器必须先检查环境,再根据实际看到的结果决定继续、重试,还是转给人工处理。若日志里已经有 tool_completed,说明这次调用留下了可以继续消费的结果,恢复时就不该再次执行同一个动作。

运行恢复判断

示例里的 recoverPendingEdit() 不会直接相信 Session 文本,它会先读取虚拟工作区,再看实际内容决定该怎么做:

  • 仍然包含 total > 100:返回 apply_edit
  • 已经包含 total >= 100:返回 continue_to_test
  • 两种都不匹配:返回 manual_review

这里只处理一次结果确定的文本替换,所以它查看一处明确的文本差异,就能判断编辑有没有落盘。真实系统没这么简单。碰到真实 Patch(补丁)时,恢复器还要比较文件 Hash、版本、调用参数以及并发修改,免得把其他进程写入的内容错认成这次调用的结果。数据库或网络工具没有这么直观的文件状态,你还得去查幂等键、事务状态或外部系统,才能判断副作用究竟发生了没有。

自己扩展一个安全规则

为虚拟 Harness 增加「禁止编辑 tests/**」规则,并编写测试证明:

  1. 工具即使对模型可见,执行时仍会被拒绝;
  2. Pattern 使用规范化路径,不能通过 tests/../tests/ 绕过;
  3. Deny 不会写入工具完成事件;
  4. Model 得到结构化拒绝原因;
  5. 独立 Evaluator 仍检查测试文件 Hash。

什么时候不应自动恢复

  • 工作区内容与调用前后版本都不匹配;
  • 写入跨越多个文件,只确认了其中一部分;
  • 工具启动了后台进程;
  • 网络响应丢失,服务端可能已经处理;
  • 用户批准只针对已经失效的一次性上下文;
  • Session 与环境来自不同 Project 或 Server。

遇到这些情况,就该转给人工检查、启动补偿事务或使用专门的恢复器,不能只让模型「再试一次」。

下一项:让外部 Evaluator 独立判断结果