第十章 Tool 与权限治理:能力越大,责任越大

核心命题:Tool 是 Harness 将 Agent 与真实世界连接的接口。每一个 Tool 都同时是能力的扩展和风险的引入。权限治理的本质是:用最小必要权限集定义操作集合,在保留足够能力的同时,将不可逆副作用的暴露面与灾难性成本降至最低。

10.1 Tool 作为操作算子

从问题空间理论看,Tool 就是操作算子。操作集的设计决定了 Agent 能走到哪些状态,也决定了解空间的可达边界。提供 apply_patch 而非 raw shell exec,是用操作集设计代替事后验证的典型范式——在能力层建立约束,而非在检查层。

两种操作集构造思路

白名单思路(允许列表):明确列举 Agent 可以执行哪些操作,其余均禁止。产生边界清晰、可验证的操作集,代价是初期设计成本较高——需要提前枚举允许的操作。收益是系统行为的可预测性:可达状态的上界是可分析的。

黑名单思路(禁止列表):明确列举 Agent 不可以执行哪些操作,其余均允许。初期开发阶段更灵活,但随工具与能力增加,黑名单迅速难以维护——遗漏的禁止项可能形成高风险的操作组合(10.3 节详述)。

最小操作集原则(对应最小权限原则):在满足任务可达性的前提下,选择最小的操作集。两重工程价值:缩小分支因子 bb,直接压缩搜索复杂度;缩小意外副作用的暴露面,降低不可逆操作的风险。

可达性验证:操作集设计确定后,对典型任务执行可达性验证——从初始状态出发,仅使用当前操作集,是否能在 Q/T/C 约束内到达目标状态?验证失败意味着操作集过度约束,需要放开部分能力。可通过在受控环境中运行 Agent 并观察卡点来近似验证,不需要形式化证明。

Tool 风险分级表

风险级别特征示例默认策略
只读不改变状态,可自由调用文件读取、搜索自动允许
幂等写可重复执行,结果一致格式化、排序自动允许
非幂等写改变状态,可撤销文件编辑、API 调用需要 Plan 步骤确认
不可逆写不可撤销的破坏性操作删除、发送邮件需要人工确认

决策理论视角:工具风险分级的期望损失依据

风险分级表的背后,是一个在不确定性下的不可逆决策问题。每次工具调用可以用期望损失严格重述:

E[损失]=P(误调用)×不可逆程度×影响范围E[\text{损失}] = P(\text{误调用}) \times \text{不可逆程度} \times \text{影响范围}

这个公式使”为什么高风险工具要人工确认”从经验直觉转化为可计算的决策依据。三个因子各自承担明确的工程语义:P(误调用)P(\text{误调用}) 由 Agent 在该工具上的历史误用频次估计(评估数据可量化),不可逆程度由该操作能否被回滚决定(取值范围 [0,1][0, 1],0 表示可完全回滚、1 表示完全不可逆),影响范围由操作触及的状态规模刻画(局部文件 vs. 单服务 vs. 全局基础设施)。三者相乘构成期望损失。

由此推出两个直接结论:只读操作的不可逆程度为零,无论误调用概率多高,E[损失]=0E[\text{损失}] = 0,自动允许合理;不可逆写操作的不可逆程度为 1,E[损失]E[\text{损失}] 完全由误调用概率与影响范围决定——删除一个临时测试文件与删除生产数据库的”影响范围”相差数个量级,应当适用根本不同的确认策略,而非同等对待所有”不可逆操作”。这也是为什么风险分级表的四档(只读 / 幂等写 / 非幂等写 / 不可逆写)不是平均切分概率轴,而是沿着”不可逆程度 × 影响范围”这一组合轴的非线性切割。

10.2 副作用的不可逆性:非对称成本

副作用的危险在于其不对称性:错误执行一个破坏性操作只需一瞬间,但恢复可能需要数小时甚至不可能。Harness 应当对”副作用的可逆性”进行显式建模:读操作和写操作、幂等操作和非幂等操作,应当适用不同级别的审批机制。

权限设计模板:一个工具权限声明的标准格式,包含:操作类型、副作用分类、可逆性标注、触发人工确认的条件。

委托代理视角:最小权限原则的理论根基

工具权限架构是激励约束集合(Incentive Constraints)的工程实现:允许列表定义”哪些行为是可接受的”,与机制设计中可行机制集合的定义完全同构。

最小权限原则在委托代理理论中有比安全工程直觉更深的理论根基:最小权限是在监督成本约束下最优合约设计的必然推论。能力越多,行为空间越大,完全监控所需的成本越高,道德风险(Agent 选择非委托人最优路径)的概率越高。因此,最优合约应当将 Agent 的能力集约束到完成委托任务的最小必要集——不是”信任不足”的体现,而是”监督成本有界”下的理性设计。这也解释了为什么”增加工具能力”不应是默认策略:每新增一个工具,不只是增加了功能,也增加了监督成本和道德风险暴露面。

10.3 MCP 与工具生态:从单工具到工具网络

当 Agent 可以访问数十个工具时,权限治理升级为生态治理。工具之间的交互会产生组合失效模式——单个工具独立运行时安全,组合后可能引发意外后果(例如:读取工具 + 写入工具 + 外发工具的组合,可能在无明确授权的情况下完成数据泄露链路)。工具的组合性带来失效模式的组合爆炸:n 个工具的潜在交互路径是 O(n²) 量级,必须转向基于原则的权限框架,而非逐一枚举的白名单。

FMEA 在工具生态中的应用:对每个工具识别:(a)失效模式(工具被错误调用的方式);(b)失效影响(对系统状态的破坏程度);(c)可检测性(Hook 能否在发生时捕获)。高影响 × 低可检测性的工具,需要最严格的权限控制。

安全性与对齐讨论:工具权限治理是操作层安全,但更广泛的安全问题——Agent 的目标对齐、对抗性鲁棒性(Prompt 注入攻击)——同样是 Harness 工程师的责任。最小权限原则在对抗性场景中有双重价值:既限制了 Agent 的误操作范围,也限制了攻击者通过 Prompt 注入获得的最大权限。

章末案例剖析

同一个代码修复 Agent,两种权限架构设计方式——一种基于直觉列举”Agent 可能需要什么”,另一种基于本章的期望损失公式和 FMEA 原则系统化推导——在相同任务需求下产生了截然不同的风险暴露面。设计过程本身即是案例的核心:权限边界不是约束工程师想象力的枷锁,而是可分析、可验证的系统设计决策。

案例中的工具风险评级、误调用概率、影响范围、对比表的工具数量与分支因子压缩比例(约 70%)均为基于作者工程实践的说明性示例,旨在量化呈现设计 A 与设计 B 在风险结构上的差异,不构成具体场景下的实测基准。读者应将此视为推导框架的演示,实际项目需用本团队的评估数据重新标定每个因子。

任务设定:一个 Python 后端服务的 CI 流水线持续失败(3 个测试用例,涉及数据库查询逻辑和 GitHub Webhook 处理)。Agent 被要求诊断失败原因、修复代码、提交 PR,并通知相关团队。Agent 需要访问三类资源:代码仓库(Git 管理的文件系统)、PostgreSQL 数据库(用于验证查询逻辑)、外部服务(GitHub API、Slack、Jenkins CI)。


设计 A:直觉驱动的权限清单

从”Agent 完成任务需要哪些工具”出发,直觉性地列举工具集:

文件系统工具:
  read_file(path)           — 读取源码文件
  write_file(path, content) — 写入修复后的代码
  delete_file(path)         — 删除临时文件
  execute_shell(command)    — 执行测试、查看日志

数据库工具:
  db_query(sql)             — 执行任意 SQL(含 SELECT 和 DDL)
  db_execute(sql)           — 执行写操作

外部 API 工具:
  github_create_pr(...)     — 创建 Pull Request
  github_merge_pr(pr_id)    — 合并 PR
  github_comment(...)       — 评论 Issue
  slack_post(channel, msg)  — 发送 Slack 通知
  jenkins_build(job, ...)   — 触发 CI 构建
  jenkins_deploy(svc, ver)  — 部署到生产环境

这是一份看似合理的工具清单——每一个工具都有对应的任务需求场景。但它基于白名单的直觉枚举,缺乏对每个工具的风险结构分析。


设计 B:基于风险原则的系统化推导

第一步:期望损失分级(§10.1 决策理论视角)

对每个工具应用期望损失公式 E[损失]=P(误调用)×不可逆程度×影响范围E[\text{损失}] = P(\text{误调用}) \times \text{不可逆程度} \times \text{影响范围},识别真实风险等级。表中”初始分级”一列对应 §10.1 风险分级表的四档(只读 / 幂等写 / 非幂等写 / 不可逆写),用以将具体工具映射到默认策略;标注为”⚠ 候选禁止”的工具是 E[损失]E[\text{损失}] 极高且无法在参数层有效约束的工具,需进一步分析是否完全移除。

工具P(误调用)P(\text{误调用})不可逆程度影响范围E[损失]E[\text{损失}]初始分级
read_file0(无副作用)0只读
write_file(代码文件)低(Git 可回滚)局部非幂等写
delete_file高(Git 外文件不可恢复)不可逆写
execute_shell极高(任意系统操作)全局极高⚠ 候选禁止
db_query(SELECT)00只读
db_execute(INSERT/UPDATE)非幂等写
db_execute(DDL:DROP/ALTER)极高全局不可逆写
github_create_pr低(PR 可关闭)局部非幂等写
github_merge_pr高(合并不可撤销)不可逆写
slack_post(任意频道)中(公开信息无法收回)非幂等写
jenkins_build(仅测试)低(构建可取消)局部幂等写
jenkins_deploy(生产部署)极高(生产变更)全局极高⚠ 候选禁止

两个候选禁止项的分析

execute_shell:对”代码修复”任务,Shell 执行能力的合法用途是运行测试命令(pytest)。但 Shell 的操作集是无界的——它将 Agent 的有效操作集从受控的工具列表扩展为任意系统命令。在委托代理视角(§10.2),提供无界 Shell 等价于签署了一份”允许 Agent 做任何系统操作”的空白合同,监督成本趋近无穷。处置:不提供通用 Shell;替换为目的受限的工具 run_tests(test_pattern),仅允许在 pytest 框架内运行指定路径的测试。

jenkins_deploy_to_prod:代码修复 Agent 的职责链终点应是”提交 PR 并触发测试”,而非”部署到生产”。生产部署是独立的批准决策,应由专门的部署流程(含人工门控)处理,不应作为代码修复 Agent 的可达操作。处置:从工具列表移除,不出现在任何权限层级中。

第二步:组合失效分析(FMEA,§10.3)

单个工具的风险分析是必要但不充分的。设计 A 中即使每个工具看似合理,组合后仍会出现高风险链路:

组合一:write_file + execute_shell

  • 失效模式:Agent 写入包含特殊字符的修复代码,随后调用 execute_shell 运行测试。Shell 命令中的路径拼接可能被代码内容注入(如文件名含 ; rm -rf /tmp/*)。
  • 失效影响:文件系统破坏
  • 可检测性:低(Hook 难以在执行前解析 Shell 命令的语义)
  • 风险评级:极高 → 强化了移除 execute_shell 的决策

组合二:db_execute(无 DDL 过滤)+ github_merge_pr

  • 失效模式:Agent 在测试修复期间修改了数据库 Schema(认为是”临时调试表”),然后直接合并 PR 推送代码。Schema 变更未经 Migration 文件管理,代码与数据库状态不一致。
  • 失效影响:生产数据库 Schema 与代码不同步,可能导致服务崩溃
  • 可检测性:中(可通过 Schema 变更检测,但 PR 合并后才发现)
  • 风险评级: → 需要将 DDL 从 db_execute 中分离,并移除 github_merge_pr

组合三:slack_post(任意频道)+ read_file(无路径限制)

  • 失效模式:Agent 读取了含数据库凭证的配置文件(.envconfig.yaml),在 Slack 通知中引用了文件内容片段(“以下是当前数据库配置…”),将凭证发送到团队公开频道。
  • 失效影响:凭证泄露
  • 可检测性:极低(内容审查需要语义分析,难以自动化)
  • 风险评级:极高 → 需要对 read_file 的路径范围和 slack_post 的频道做双重限制

组合四:github_merge_pr + jenkins_build(若 build 触发部署)

  • 失效模式:Agent 合并 PR 后触发 Jenkins 构建,若 Jenkinsfile 中存在合并到主分支自动部署的逻辑,则间接触发了生产部署——即使 jenkins_deploy_to_prod 未直接暴露,也通过组合实现了等效操作。
  • 失效影响:未经审批的生产部署
  • 可检测性:中(需要分析 Jenkinsfile 的分支逻辑)
  • 风险评级:github_merge_pr 必须移除;jenkins_build 需限制为仅在测试 Job 上触发

第三步:最小权限架构

综合期望损失分级和 FMEA 分析,确定最终的最小权限集:

层级一:只读工具(自动允许,无需确认)

read_file(path)
  约束:path 必须在仓库根目录下(正则白名单)
  约束:禁止访问 .env、*secret*、*credential* 路径

list_directory(path)
  约束:path 必须在仓库根目录下

db_query(sql)
  约束:Hook 校验 sql 语句类型 = SELECT(AST 解析,非字符串匹配)
  约束:查询目标库为只读副本,不连接主库

层级二:幂等/低风险写工具(Plan 步骤内自动执行)

run_tests(test_pattern)
  定义:内部固定为 pytest {test_pattern},不接受任意 Shell 参数
  约束:test_pattern 必须匹配 tests/ 目录下的合法路径

github_create_pr(title, body, head_branch)
  约束:base_branch 固定为 staging(不可由 Agent 指定)
  约束:禁止 base = main 或 base = production

github_comment_issue(issue_id, comment)
  约束:comment 内容不得包含文件路径模式(防止路径泄露)

层级三:不可逆写工具(需人工确认后才可执行)

write_file(path, content)
  触发人工确认的条件:
    - path 匹配 *.yaml、*.toml、Dockerfile、requirements.txt(基础设施文件)
    - content 长度超过 500 行(大规模变更)
  自动允许的条件:
    - path 在 src/ 或 tests/ 下的 *.py 文件,且变更行数 ≤ 50 行

delete_file(path)
  触发人工确认的条件:始终(删除操作无例外)
  自动允许的条件:仅允许删除 __pycache__/、*.pyc(正则白名单)

slack_post(channel, message)
  约束:channel 固定为 #dev-bots(不可由 Agent 指定)
  触发人工确认的条件:message 超过 500 字符(防止大段内容意外发布)

jenkins_build(job)
  约束:job 必须在白名单 {"run-unit-tests", "run-integration-tests"} 内
  触发人工确认的条件:job 不在白名单(应触发拒绝,而非人工审批)

层级四:完全禁止(不提供,不出现在工具描述中)

execute_shell         — 用 run_tests 替代
db_execute            — 禁止任何数据库写操作(含 DDL 和 DML)
github_merge_pr       — PR 合并由人类通过 GitHub 界面操作
jenkins_deploy_to_prod — 部署不在 Agent 职责范围内
slack_post(任意频道)   — 已在层级三约束频道

两种设计的结构对比

维度设计 A(直觉驱动)设计 B(原则驱动)
工具总数12 个9 个(含 2 个替换工具)
完全禁止项04(Shell、DB 写、PR 合并、生产部署)
有路径/参数约束的工具07
触发人工确认的工具0(全部自动执行)3(write_file 部分场景、delete_file、slack_post)
凭证泄露风险(read_file + slack_post 组合)高(无路径限制 + 任意频道)封闭(路径白名单 + 固定频道)
数据库 DDL 误操作风险高(db_execute 不区分语句类型)封闭(DB 写操作完全禁止)
意外生产部署风险存在(jenkins_deploy 直接可调用;PR 合并可触发 CD)封闭(deploy 禁止;merge 禁止;build 限白名单)
分支因子 bb(Agent 的有效操作集规模)~12 × 高参数自由度~9 × 受约束参数(有效 bb 压缩约 70%)

最小权限边界的确定逻辑总结

设计 B 的最终边界由三层分析叠加确定:

  1. 单工具 E[损失]E[\text{损失}] 筛查:移除期望损失极高且无法在参数层约束的工具(Shell、生产部署)
  2. 组合 FMEA:识别组合失效路径,通过移除链路中的关键节点(PR 合并、任意频道 Slack)断开危险链
  3. 任务必要性验证:对每个候选工具追问”如果不提供这个工具,任务是否仍可完成?“——若答案是”是”,则不提供;若答案是”否”,则保留但加约束

这三层分析的顺序是刻意设计的:第一层处理绝对风险(无论任务如何都不应提供),第二层处理组合风险(单独看安全但组合危险),第三层处理冗余能力(任务不需要的工具不应存在于工具集中)。任何满足一层过滤条件的工具,不能以”任务可能需要”为由被保留——这是最小权限原则的实操含义:“Agent 用得上”不是保留工具的充分理由;“没有这个工具任务必然无法完成”才是