工具、权限与执行边界:模型提出动作之后发生什么¶
上一篇:一次任务怎样形成 Agent Loop · 返回学习入口 · 下一篇:Session、Context、Memory 与恢复
工具让模型能读取和改变外部世界,也因此把模型不确定的输出接上了真实副作用。读源码时,你得分开查四件事:请求里有没有工具,策略放不放行,用户批不批准,操作系统最后有没有执行成功。它们分属不同层,不能压成一个布尔值,各层的失败也不能统统算成「执行失败」。
同一个动作经过五道边界¶
回到运费案例,这次模型想要跑测试。
命令真正跑起来以前,这个请求至少要过五道关。
- 工具可见性:模型输入中是否声明了
run_command; - 协议校验:工具名和参数是否符合 Schema;
- 产品策略:当前模式和规则是否允许运行该命令;
- 用户确认:风险达到阈值时是否获得即时授权;
- 执行环境:宿主、Sandbox 或远程运行器是否真的能执行。
前四道关只在审查请求有没有资格进入执行环境,第五步才会真正启动进程,产生副作用。
工具可见不等于可以自动执行¶
工具定义通常会告诉模型,这个工具叫什么、用来做什么,参数又该怎么填。
{
"name": "run_command",
"description": "在当前工作区运行命令并返回退出码和输出",
"input_schema": {
"type": "object",
"properties": {
"command": {"type": "string"}
},
"required": ["command"]
}
}
把工具告诉模型,只是允许模型照这套协议生成请求。请求能不能自动执行,还要继续查权限模式、命令内容、路径范围、网络需求和用户选择。看得见工具,不等于已经拿到权限。
反过来,工具没写进模型输入,模型即使猜到系统支持终端,也没法按约定协议发出请求。有些 Agent Harness(智能体框架)会根据当前模式筛选工具,另一些会始终把工具告诉模型,等请求出现后再按策略拒绝。下文把 Agent Harness 简称为 Harness。两种写法会用不同方式影响模型行为和 Token 用量,你得分开追。
Schema 校验保护协议,不保护业务安全¶
Schema 能拦下缺少 command、类型错误或带有未知字段的请求,却判断不了命令会不会带来危险。
参数合法,只说明 Harness 看得懂这个请求,并没有说它应当获准,因此参数校验和权限策略必须分开判断。合法不等于安全。
请求进入执行环境前,Harness 还可能给它补上工作目录、超时、环境变量和输出上限。所以正文要讲清原始参数经过了哪些改写,最后又变成什么。否则审计者只能看见模型最初吐出的文本。
产品策略怎样作决定¶
权限策略作决定时,可能会读下面这些信息。
- 工具名称;
- 命令和参数;
- 当前工作区;
- Session 模式;
- 用户或组织规则;
- 请求来源;
- 是否已有同范围授权;
- 动作是否可逆。
如果策略只返回 true 或 false,你就不知道它为什么放行或拒绝。返回值还得带上能说清原因的理由。
{
"decision": "ask",
"reason": "命令会启动本地进程",
"scope": "本次调用",
"final_arguments": {
"command": "npm test -- tests/shipping.test.ts",
"timeout_ms": 120000
}
}
不同项目会用 allow、deny、ask、always allow、workspace scope 或模式枚举来表达结果,名字像,语义却不一定一样。真要比较时,你得追到每个选项实际排在什么优先级,授权又会存多久、管多大范围。
用户确认是策略的一部分,不是 Sandbox¶
用户点下「允许」,只是表示他当下同意这个请求。这一点击扩大不了操作系统权限,也证明不了命令本身安全。确认界面可能跑在 CLI、IDE(集成开发环境)、桌面端或远程客户端,这些界面未必都能用同一种方式向用户询问。批准穿不过系统边界。
无头模式如果没有确认通道,Harness 就得明确选一条路:直接拒绝,使用事先配好的策略,或把请求交给外部审批者。如果它不出声就自动放行,安全边界就已经变了,文章必须把这个选择讲出来。
保存确认结果时,要同时绑定调用 ID 和授权范围。如果只留一句宽泛的「用户曾经允许 run_command」,后面一条完全不同的命令就可能错用这份旧授权。
Sandbox 限制已经获准的动作¶
Sandbox 管的是动作真正执行时,进程究竟能碰到什么。
- 哪些目录可读写;
- 是否能够访问网络;
- 能否看到宿主凭据;
- CPU、内存、磁盘和时间限制;
- 子进程和系统调用范围;
- 执行完成后是否保留工作区。
有的项目会很细致地询问权限,却默认让命令在宿主进程里跑。另一个项目可能把所有命令都放到远程隔离环境,产品策略却放得很宽。两种设计在防不同的风险,不能拿一把尺子量。
在运费案例里,测试命令就算获准,Environment(运行环境)仍可能返回下面的结果。
这里失败的是执行,权限并没有拒绝。模型只有看到准确错误,才能换成仓库支持的测试命令,或者如实告诉用户:环境有问题。
工具结果怎样安全地回到模型¶
工具吐出的内容可能很长,也可能夹着二进制内容或敏感数据。Harness 把结果交回模型前,通常要做下面这些处理。
- 保存退出码和错误类型;
- 截断过长输出并提供完整结果位置;
- 标记 stdout 与 stderr;
- 过滤不应进入模型的敏感字段;
- 保留原调用 ID;
- 说明动作是否实际执行;
- 把结果转换成 Provider 接受的消息格式。
只回一句「工具失败」,模型就没有线索去修正。可要是把几万行日志全部塞回上下文,Token 预算又会很快被吃光。信息太少和太多都要付出代价。Harness 得决定保留什么、截掉什么,才能让模型有足够信息继续工作,又不把上下文挤满。
四条失败路径¶
| 路径 | executed |
应记录什么 | 模型下一轮可以做什么 |
|---|---|---|---|
| 未知工具 | false |
工具名和可用工具提示 | 改用现有工具 |
| 参数不合法 | false |
Schema 错误和字段位置 | 修正参数 |
| 策略或用户拒绝 | false |
决定来源、理由和范围 | 选择低风险替代方案 |
| 环境执行失败 | true 或状态未知 |
退出码、错误、部分输出 | 修正命令或报告阻塞 |
如果这四条路都被压成同一个 error,恢复逻辑、审计者和 Eval(评测)就都判断不了副作用究竟发生过没有。
恢复时最危险的情况¶
工具已经开始执行,进程却突然崩了,Session 里就可能只有「开始」事件,没有与它配对的「结束」事件。这时别自动假定工具没执行过。写文件、发消息、支付和部署都会留下副作用,盲目重跑很可能把同一件事做两遍。
想让恢复更安全,记录里至少要留下这些信息。
- 稳定调用 ID;
- 审批后冻结参数;
- 开始执行时间;
- 执行环境标识;
- 完成、失败、取消或未知状态;
- 可用于查询或补偿的外部操作 ID。
这些记录齐了,恢复逻辑才能看工具的性质,决定去查状态、做补偿、重新执行,或者把无法自动确认的情况交给人来判断。
在真实源码中怎么找¶
读真实源码时,不要只搜 permission。找到工具请求的数据类型,再沿着它一路追下去。
- 工具定义在哪里进入模型请求;
- 模型响应怎样解析成内部 ToolCall;
- 工具注册表怎样从名称找到实现;
- 参数在何处校验或改写;
- 策略与用户确认的调用顺序;
- 执行器使用宿主、Sandbox 还是远程环境;
- 结果怎样转换并回到消息流;
- 拒绝、取消、超时和未知状态有哪些测试。
读 Claude 课程时,还要额外核对证据到哪里为止。公开 SDK 可以让我们看到回调和控制协议,但它证明不了 Claude Code 内部所有策略和执行细节都已经公开。
回到运费案例¶
读源码、改比较符和跑测试,可能各自落在不同的风险级别。一套合理流程可以自动放行工作区内的读取,动手编辑前先把差异给用户看,遇到命令再按策略判断要不要询问。真正执行时,范围始终要卡在工作区内。
「所有工具都获准」证明不了任务做对了。真正的证据是:危险请求没有越界,必要动作确实执行了,覆盖边界值的目标测试也最终通过。
练习:判断阻断发生在哪一层¶
请你给下面四种失败分层:工具名称不存在、命令被策略拒绝、用户取消确认,以及进程在 Sandbox 里读不了工作区外的文件。再说说为什么它们不能统一返回「执行失败」。
查看核对要点
它们依次属于工具解析或注册、产品策略、交互批准和执行环境能力。如果都压成「执行失败」,模型可能会反复重试一个永远不会获准的动作。恢复逻辑也判断不了副作用究竟发生过没有。现在应该能解释¶
工具是否可见、Schema 是否合法、策略是否放行、用户是否批准,以及系统最后是否执行,要分成五次判断。权限管产品愿不愿意做,Sandbox 管进程真正能做到什么。工具结果还得准确写明副作用有没有发生,下一轮才能正确处理,恢复时也才不会盲目重跑。