第 27 章 · 策略治理
Harness 锚点|判别清单第 8 项「权限 / 安全边界」——用扩展做策略门与审计。
一次差点酿成事故的退款
还是那个做跨境电商的团队。他们上线了一个客服 Agent,负责处理买家的售后咨询——查订单、看物流、必要时发起退款。上线第三天,一个买家在对话里连着抱怨“东西根本没收到”“你们这平台是不是骗子”,然后来了一句:“帮我把这个月所有订单都退了。”
Agent 很“贴心”地照做了。它手里有 refund_order 工具,也确实能一条条把这个买家名下的订单标记为退款。等运营早上看到财务对账异常时,一笔本该只退一单的纠纷,变成了七单全退,其中还有两单买家其实已经签收好评。
复盘会上没人怪模型——它调用工具的每一步都“合理”:买家要求退款,工具能退款,那就退。问题出在更前面一层:没有人规定过,这个 Agent 到底被允许退多少、退什么、什么情况下必须先找人确认。 它能做的事,远远超出了它该做的事。
这一章要解决的就是这道题:给 Agent 的自主行动套上一层明确、可审计的约束——策略治理(Policy Governance)。
能做 ≠ 该做:为什么企业里必须治理
大模型加上工具,Agent 就有了“手脚”。手脚越强,越需要规矩。在企业环境里,“技术上能做到”和“业务上被允许”是两码事,几乎每个真实场景都能撞上这道墙:
- 客服 Agent 有数据库查询工具,但它只应该读发起对话的这个买家自己的订单,绝不能顺手查到别人的收货地址和手机号。
- 运维 Agent 能执行 shell 命令,但不该在生产环境跑
DROP TABLE、rm -rf或重启核心服务。 - 财务、个人隐私这类数据受合规约束(GDPR、等保、PCI),Agent 每碰一次都得留下记录,事后能说清“谁、什么时候、对什么、做了什么”。
一个没有任何约束的 Agent,能力越强,在企业里越不敢用——因为你无法向法务、安全、财务证明它不会闯祸。策略治理要回答的核心问题就一句话:在放手让 Agent 干活的同时,如何保证它只在被授权的范围内行动,并且每一步都可追溯。
注意这和第 3 章的“循环刹车”是两回事。刹车管的是“别转个不停烧钱”,属于成本与稳定性;策略治理管的是“别做不该做的事”,属于权限与合规。两者都是 Agent 上生产的前提,但守的是不同的门。
策略、执行点、审计:治理的三根支柱
不管治理体系搭得多复杂,拆开看都是同样三个部件。把这三个术语讲透,后面所有实现都是它们的变体。
策略(Policy)——一组规则。 一条策略回答的是这样一个结构化问题:谁(主体)、在什么条件下(上下文)、能对什么资源(客体)、做什么操作(动作)。 把开头那个事故翻译成策略就是:
客服 Agent(谁),在处理某买家会话时(条件),只能对该买家本人的订单(资源),执行读取和单笔退款(操作);单笔退款金额超过 500 元,或一次会话内退款超过 2 单,必须转人工审批。
看,一旦写成规则,那次七单全退当场就被挡下了。策略的价值在于把“该不该”从模型的临场判断里拿出来,变成一份明确、可评审、可版本管理的规则。
执行点(Enforcement Point)——规则在哪里被检查。 规则写在纸上没用,得有个卡口真正拦截。这个卡口放哪最合适?答案是工具调用之前。回顾第 4 章:模型自己不执行任何代码,它只是“生成一个调用请求”,真正的执行由你的程序完成。这意味着 Agent 对世界的一切影响——读数据、发退款、跑命令——最终都必须经过工具这道关口。守住工具调用前的这一拍,就守住了 Agent 的全部行为边界。所以执行点的黄金位置,就是每次工具调用前的那个拦截钩子。
审计(Audit)——每次判定都留痕。 每一次策略评估的结果——放行还是拒绝、谁触发的、判了哪条规则——都要落到不可篡改的日志里。审计有两重作用:出事后能溯源(那七单是被谁、按什么规则退的),以及平时能向合规部门证明“我们确实在管”。关键一点:放行也要记,不能只记拒绝。 因为“某操作被允许了”本身就是合规审查要看的证据。
三者串起来就是一句话:策略定义边界,执行点守住边界,审计证明边界确实在起作用。
谁能对什么做什么:策略模型怎么设计
写策略时,绕不开两个业界通用的权限模型,值得记住它们的英文名:
- RBAC(Role-Based Access Control,基于角色的访问控制):先把用户/Agent 归到角色,再给角色配权限。“客服角色能退款、运维角色能重启服务”。粒度粗、好维护,是最常见的起点。
- ABAC(Attribute-Based Access Control,基于属性的访问控制):不看角色,看属性组合来判定——主体属性(部门、等级)、资源属性(订单归属、金额)、环境属性(时间、来源 IP)。开头那条“只能退本人订单、金额低于 500”就是典型的 ABAC,因为它依赖运行时的具体属性。
真实系统往往是两者混用:用 RBAC 划大类,用 ABAC 做细粒度判定。而当规则多到用 if-else 写不动时,就会引入专门的策略引擎,其中最有名的是 OPA(Open Policy Agent)——一个开源的通用策略引擎,用专门的 Rego 语言把策略写成独立于业务代码的规则文件,程序在执行点把“请求上下文”丢给它,它返回 allow/deny。OPA 的价值在于策略与代码解耦:安全团队改规则不用动业务代码,也不用重新部署 Agent。这类专业方案,是重型企业治理绕不过去的一环。
还有一类特殊策略,判定结果不是简单的放行/拒绝,而是“暂停,等人确认”——这就是 human-in-the-loop(人工审批 / 人在环中)。像大额退款、生产环境删除这种高风险、不可逆的操作,最稳妥的策略不是让模型自己拿主意,而是把它挂起,推给人类点头后再执行。人工审批本质上是策略的一种特殊动作:decision = "ask"。
用 pi 实现:诚实的边界 + 扩展做策略门
讲 pi 怎么落地之前,必须非常诚实地说明它的定位,这也是官方文档反复强调的一点:
pi 官方 README 明确写道:pi 本身不含内建的权限系统来限制文件、进程、网络或凭证访问。默认情况下,它以启动它的那个用户的权限运行。
这句话要读进去:你在自己账号下跑 pi,pi(以及它调用的工具)就能碰你这个账号能碰的一切文件、进程、网络。这不是 bug,而是 harness 的定位选择——pi 把“策略”这件事留给你按业务去定制,而不是强塞一套未必合身的权限体系给你。
那在 pi 上怎么做策略治理?两条路,对应上面讲的两层边界。
路径一:用扩展做应用层策略门。 利用第 8 章讲的生命周期事件,在 beforeToolCall 上实现你自己的策略检查。这个钩子就是天然的执行点——每次工具真正执行前都会触发,你在这里评估策略、记录审计、决定放行还是拦截:
// 概念示意:一个应用层策略门扩展
export default function policyGate(pi, { policies }) {
pi.on("beforeToolCall", async (call, ctx) => {
// 1. 组装判定所需的上下文:谁、调什么工具、传什么参数
const request = {
user: ctx.user, // 主体
tool: call.name, // 动作
args: call.arguments, // 资源 + 条件
};
// 2. 交给策略引擎评估,返回 allow / deny / ask
const decision = evaluatePolicy(policies, request);
// 3. 审计:无论放行、拒绝还是转审批,都记录,且不可省
await audit.log({
user: ctx.user, tool: call.name,
args: call.arguments, decision, at: ctx.now,
});
// 4. 依判定结果处理
if (decision === "deny") {
return { block: true, reason: "策略不允许此操作" };
}
if (decision === "ask") {
const ok = await ctx.requestHumanApproval(request); // human-in-the-loop
if (!ok) return { block: true, reason: "人工审批未通过" };
}
// allow:什么都不返回,工具照常执行
});
}
evaluatePolicy 内部可以是几行 if-else,也可以是一次对 OPA 的 HTTP 调用——执行点的形状不变,变的只是规则住在哪。把开头那条退款规则塞进去,Agent 想一次退七单时,第三单就会因为“超过 2 单”被判 ask 而挂起等人,事故根本发生不了。
路径二:靠隔离做系统层硬边界。 这里有个致命的诚实:应用层策略门是可以被绕过的。想想看——如果 Agent 手里有一个 shell 工具,你的策略门只拦 refund_order,那模型完全可能绕开退款工具,直接用 shell 去 curl 内部退款接口,或者读一个你以为它读不到的文件。只要它有一条通往系统的旁路,应用层的规则就形同虚设。
真正拦不住也绕不开的边界,得靠沙箱 / 容器在系统层来立:让 Agent 跑在一个根本没有生产数据库网络访问、根本挂载不到敏感目录的环境里。这样即使策略门被绕过、即使模型被提示注入攻击骗过,它也够不到不该够的东西。这正是下一章(第 28 章)的主题,也是 pi 官方给出的正式建议:需要强边界,就容器化。
把人机分工写成四级自动化
治理不能只写“高风险操作需要确认”,因为团队对“高风险”的理解会不断漂移。更可执行的做法,是按 Agent 对业务结果的控制程度划分自动化等级,并为每一级明确授权人与升级条件。
| 等级 | Agent 的权限 | 适合场景 | 治理要求 |
|---|---|---|---|
| L1 提示 | 整理信息、提示风险,不形成业务建议 | 知识检索、提醒、摘要 | 来源可追溯,用户自行判断 |
| L2 建议 | 生成方案或建议,由人选择 | 营销文案、补货建议、客户回复草稿 | 展示依据和不确定性,保留人工确认 |
| L3 判断 | 在授权规则内作出判断,但执行前可设审批 | 线索分类、异常识别、低额退款审核 | 明确阈值、抽检比例、升级和撤销机制 |
| L4 执行 | 直接调用系统改变业务状态 | 更新库存、发送消息、创建订单、退款 | 最小权限、幂等、审计、限额、回滚和熔断 |
自动化等级不是给整个 Agent 一次性贴标签,而是给每个动作定级。同一个客服 Agent 可以自动查询订单(L4 只读执行)、建议回复(L2),但高额退款只能提交审批(L3)。只有当一类动作在足够样本中持续达到质量和风险门槛,才从 L2 逐步升级到 L3 或 L4。
策略也要有版本和责任人
每条策略至少记录 policyId、版本、生效时间、适用租户/流程、批准人和回滚版本。一次 Agent 执行必须保存当时命中的策略版本与决策结果。这样发生争议时,才能回答“当时按哪条规则放行、谁批准了这条规则”,而不是只看到模型最后输出了什么。
人机分工不是一张静态 RACI 表,而是一组能在运行时执行、审计并逐级放量的策略。
应用层 + 系统层:纵深防御
把两条路摆在一起看,会发现它们不是二选一,而是配合——这就是安全领域的经典思路:纵深防御(Defense in Depth),多设几道互补的关卡,一道被突破还有下一道。
| 维度 | 应用层策略门(扩展) | 系统层隔离(沙箱/容器) |
|---|---|---|
| 粒度 | 细,懂业务:“只能退本人订单” | 粗,不懂业务:“这里没有生产库” |
| 能否绕过 | 可能被绕过(有旁路就失效) | 绕不过(物理上就够不到) |
| 谁来写 | 业务/安全团队按规则配 | 运维按环境配 |
| 拦的是 | 违规的业务操作 | 越界的物理访问 |
生产系统两者都要:策略门在前,负责细粒度的“业务规则”,把大多数违规意图挡在语义层;沙箱在后,负责兜底的“物理边界”,保证万一策略门失守,损失也被锁死在一个够不到要害的盒子里。只有策略门,是纸糊的墙;只有沙箱,是没锁的保险柜;两者叠起来,才是能上生产的治理。
动手看看
先把 pi 的定位坐实。打开 pi 仓库里的 containerization.md(讲容器化那篇),找到它明说“pi 本身不含权限系统、默认以启动用户权限运行”的那段——亲眼确认这个诚实的边界,比记住结论更重要。
然后动手搭一个最小策略门。参照 packages/coding-agent/docs/extensions.md 里 beforeToolCall 的用法,写一个十几行的扩展,规则就一条:凡是工具名里带 delete 或 refund 的调用,一律先打印一行日志再放行。 放进 .pi/extensions/,/reload 热加载,然后让 Agent 做几个动作,看日志里是不是每次都留了痕。
跑通之后再改一版:把“打印后放行”改成“打印后拒绝,并返回一句拒绝理由”。观察模型收到拒绝后的反应——好的模型会向你解释它想做什么、为什么被挡,而不是硬撞。这个从“记录”到“拦截”的小改动,就是审计和执行点这两根支柱在你手里第一次真正立起来。
实战中的几个坑
坑一:只拦了正门,留了旁路。
- 现象:给
refund_order加了策略门,Agent 却用 shell 直接调了内部退款接口,绕过检查。 - 原因:执行点只卡在部分工具上,而 Agent 手里有 shell / HTTP 这类“万能工具”。
- 对策:要么收掉万能工具,要么对高危工具在系统层用沙箱兜底(第 28 章),别指望应用层策略门单独拦得住。
坑二:审计只记拒绝,不记放行。
- 现象:出事后想溯源,却发现日志里只有被拦的操作,被放行的关键操作一片空白。
- 原因:以为“放行是正常的、不用记”。
- 对策:放行、拒绝、转审批全都记,且写进不可篡改的存储——“某操作被允许”本身就是合规要审的证据。
坑三:策略规则硬编码进业务代码。
- 现象:安全团队要改一条规则,得改 TS 代码、走发布流程,几天才能上线。
- 原因:把 if-else 规则和 Agent 逻辑焊死在一起。
- 对策:把策略抽成独立的规则文件或接入 OPA 这类策略引擎,做到改规则不改代码、不重新部署。
坑四:所有高危操作都靠人工审批,把人累垮。
- 现象:审批队列爆炸,人工麻木地一路点“同意”,审批形同虚设。
- 原因:没分级,低风险操作也硬塞给人。
- 对策:只对高风险、不可逆的操作(大额退款、生产删除)触发 human-in-the-loop,其余按 RBAC/ABAC 自动判定。
对比:其他框架
先把行业现状讲透:主流开源框架都不内建企业级策略引擎——“谁能调哪个工具、哪些操作要审批、全程审计”这一整套,普遍要你在工具层自己加,或者对接外部专业方案。
| 框架 | 策略治理怎么做 | 特点 |
|---|---|---|
| LangGraph | 无内建策略引擎,但图的显式节点是天然执行点,可在工具节点前插检查/人工审批节点(interrupt 实现 human-in-the-loop) | 四家里最容易接策略门 |
| CrewAI | 无策略引擎;可限定角色的可用工具集、在任务边界加校验,做粗粒度“角色能做什么” | 以角色划权限,细粒度靠自己 |
| PydanticAI | 无独立策略层;靠依赖注入 + 类型约束,在工具函数入口统一做参数校验与权限判断 | 检查类型安全、集中可控 |
| Agno | 无专门策略/审批引擎;可配每个 Agent 的工具集限定能力,AgentOS 提供运行记录 | 硬性治理仍需自搭 |
| pi | 无内建权限系统(诚实定位);扩展在 beforeToolCall 做应用层策略门 + 容器做系统层硬边界 | 纵深防御,规则留给你定制 |
差异只在“执行点长什么样”——是显式的图节点、还是函数入口、还是生命周期钩子;而共性是残酷的:五家都没有开箱即用的企业级治理。真要做重型治理,还得往外接:OPA 这类策略引擎管规则,容器管边界,甚至像原书对照的 Shannon 那样,把策略治理做成 Go 编排层 + Rust 执行层的一等能力。这是 pi 这类轻量 harness 刻意不覆盖的深度——需要企业级治理,你需要 pi 之外的专门基础设施。
小结
- 企业里能做 ≠ 该做:Agent 能力越强越要治理,核心是回答“如何让它只在授权范围内行动、且每步可追溯”。
- 治理由三根支柱撑起——策略(Policy)定义“谁在什么条件下能对什么资源做什么”,执行点(Enforcement Point)卡在工具调用前守住边界,审计(Audit)记录每次判定以溯源合规。
- 策略模型用 RBAC(按角色)打底、ABAC(按属性)做细粒度;规则多了就交给 OPA 这类策略引擎解耦;高危不可逆操作用 human-in-the-loop 挂起等人。
- pi 不含内建权限系统(诚实的定位):路径一用扩展在
beforeToolCall做应用层策略门(可被绕过),路径二靠沙箱/容器做系统层硬边界(绕不过),二者叠成纵深防御。 - 重型企业级治理需要 pi 之外的专门基础设施(OPA、独立策略层如 Shannon),轻量 harness 刻意不覆盖这层深度。
策略门再周密,只要 Agent 手里还有一条通往系统的旁路,它就可能被绕过——真正拦不住也躲不开的边界,得靠隔离在系统层立起来。这正是下一章要讲的 pi 最实在的一块:安全执行,以及 Gondolin 微 VM、Plain Docker、OpenShell 三种容器化方案。