给 AI Agent 接入策略引擎:把权限规则从代码里独立出来
AI Agent 工具权限粒度设计 讨论过”为什么权限不能只有全给或全不给”这个问题。这篇往下走一步,讲一个具体的架构决策:当权限规则多到散落在代码各处的 if-else 里管不过来时,怎么把它们收敛成一个独立的策略引擎。这不是”要不要做权限控制”的问题,是”权限规则该长在哪里”的问题。
一、什么时候该从 if-else 升级到策略引擎
大多数团队的 Agent 权限判断最初都长这样:
def can_execute(tool_name, tool_input, context):
if tool_name == "bash" and "rm -rf" in tool_input.get("command", ""):
return False
if tool_name == "database_write" and context.user_role != "admin":
return False
if tool_name == "send_email" and context.env == "production":
return require_approval()
# ... 规则持续增加
这套模式在规则少的时候完全够用。问题在规则数量涨到几十条之后开始暴露:
- 规则和执行逻辑耦合,改一条权限规则要改代码、走 code review、重新部署,响应不了”这条规则今天必须紧急生效”的场景
- 规则之间的优先级、覆盖关系隐藏在 if-else 的书写顺序里,没人能一眼看出”这两条规则谁的优先级更高”
- 审计困难:出了问题想知道”某次操作到底是被哪条规则放行/拦截的”,只能去翻代码和日志时间戳对着猜
- 规则不能被非工程角色管理——安全团队想加一条规则,必须找工程师改代码,协作成本高
如果你的团队还没到这个规模,不需要急着上策略引擎,先把规则集中到一个模块里维护、写好单元测试就够用了。策略引擎解决的是”规则和代码分离、规则可独立审计和热更新”这个具体问题,不是规则控制的必需品。
二、策略引擎在 Agent 架构里的位置
策略引擎不替代 Agent 的工具调用逻辑,它插在**“模型决定要调用工具”和”工具实际执行”之间**,作为一道独立的判断层:
用户输入 → 模型推理 → 决定调用工具 X
↓
策略引擎评估
(读取: 工具名、参数、上下文)
↓
允许 / 拒绝 / 需要人工审批
↓
(允许才继续)工具实际执行
这个位置的关键设计原则是策略引擎只做判断,不做执行,也不感知模型内部状态——它只看到”要调用什么工具、带什么参数、当前是什么上下文(用户角色、环境、时间等)“,输出一个判定结果。这样策略引擎可以独立于 Agent 框架演进,甚至可以给多个不同的 Agent 项目复用同一套策略引擎。
三、规则怎么写:policy-as-code 的基本形态
不管自建还是用现成引擎(如 Open Policy Agent 的 Rego),规则的核心结构大同小异——条件匹配 + 判定结果。用伪代码表达一条规则:
- id: block-prod-db-write-without-approval
description: 生产数据库写操作必须人工审批
match:
tool: database_write
context.env: production
effect: require_approval
priority: 100
- id: block-destructive-shell
description: 禁止任何形式的破坏性 shell 命令
match:
tool: bash
tool_input.command: { pattern: "rm -rf|DROP TABLE|git push --force" }
effect: deny
priority: 200
- id: allow-read-only-in-dev
description: 开发环境的只读操作直接放行
match:
tool: { in: [database_read, file_read] }
context.env: development
effect: allow
priority: 50
三个要点:
priority决定冲突时谁生效,数字越大越优先——这比 if-else 里的书写顺序清晰得多,谁都能一眼看出规则间的层级关系effect至少要有三档:allow(放行)、deny(拒绝)、require_approval(转人工)——只有 allow/deny 两档会退化回”全给或全不给”的老问题- 规则本身应该是数据,不是代码——存成 YAML/JSON 或规则引擎自己的 DSL,可以脱离代码仓库独立发布、独立审计、甚至给非工程角色(安全/合规团队)编辑权限
四、自建还是用现成引擎
| 方案 | 适合场景 | 代价 |
|---|---|---|
| 自建轻量规则引擎(如上面的 YAML + 简单匹配器) | 规则模式相对固定,团队规模小,不想引入额外依赖 | 表达能力有限,复杂条件组合需要自己扩展语法 |
| Open Policy Agent(OPA/Rego) | 规则复杂、需要跨多个系统(不只是 Agent)复用同一套策略、需要成熟的测试和版本管理工具链 | 学习曲线陡,Rego 语法对工程师不算友好,团队需要投入学习成本 |
| AWS Cedar / 类似的授权语言 | 已经在用 AWS 生态、需要标准化的 RBAC/ABAC 模型 | 和特定云生态绑定较深 |
中小团队的务实建议:先用自建的轻量规则引擎跑起来,规则数量控制在可管理范围(几十条以内),等真的遇到”规则组合逻辑复杂到自建引擎撑不住”或者”要跨多个系统复用同一套策略”这两个信号,再评估迁移到 OPA 这类成熟方案,不需要从第一天就上重型基础设施。
五、审批流:require_approval 效果怎么落地
require_approval 是三档判定里最容易被简化掉、但又最重要的一档。落地方式取决于场景的紧急程度:
- 同步阻塞:Agent 暂停执行,弹出确认请求给用户(本地开发场景、IDE 内 Agent 常见),用户回复”批准”或”拒绝”后 Agent 继续或放弃
- 异步队列:Agent 把这次调用请求写入审批队列,先跳过这一步继续处理其他任务,审批通过后由外部触发器回调执行(适合后台批处理型 Agent,不能干等人工响应)
- 超时降级:审批请求超过设定时间无人处理,按预设策略自动降级为 deny,而不是无限期挂起——这条经常被忽略,结果是 Agent 卡死在等待审批的状态,谁都没意识到
六、审计:每次判定都要留痕
策略引擎相对 if-else 最大的优势就是审计能力,一定要把这个优势用起来。每次判定至少记录:
- 触发的工具名、参数摘要(不要记录完整参数,可能包含敏感信息)
- 命中的规则 ID(不是规则内容,内容变了但 ID 不变,可追溯历史版本)
- 最终判定结果和判定时间
- 判定所依据的策略集版本号(策略规则本身也该有版本管理,方便回答”这次判定用的是哪个版本的规则”)
这份审计日志在事后排查”为什么某次操作被放行/拦截”时是唯一可靠的信息源,比翻代码 git blame 快得多。