第 27 章 · 策略治理

Harness 锚点|判别清单第 8 项「权限 / 安全边界」——用扩展做策略门与审计。

一次差点酿成事故的退款

还是那个做跨境电商的团队。他们上线了一个客服 Agent,负责处理买家的售后咨询——查订单、看物流、必要时发起退款。上线第三天,一个买家在对话里连着抱怨“东西根本没收到”“你们这平台是不是骗子”,然后来了一句:“帮我把这个月所有订单都退了。”

Agent 很“贴心”地照做了。它手里有 refund_order 工具,也确实能一条条把这个买家名下的订单标记为退款。等运营早上看到财务对账异常时,一笔本该只退一单的纠纷,变成了七单全退,其中还有两单买家其实已经签收好评。

复盘会上没人怪模型——它调用工具的每一步都“合理”:买家要求退款,工具能退款,那就退。问题出在更前面一层:没有人规定过,这个 Agent 到底被允许退多少、退什么、什么情况下必须先找人确认。 它能做的事,远远超出了它该做的事。

这一章要解决的就是这道题:给 Agent 的自主行动套上一层明确、可审计的约束——策略治理(Policy Governance)

能做 ≠ 该做:为什么企业里必须治理

大模型加上工具,Agent 就有了“手脚”。手脚越强,越需要规矩。在企业环境里,“技术上能做到”和“业务上被允许”是两码事,几乎每个真实场景都能撞上这道墙:

  • 客服 Agent 有数据库查询工具,但它只应该读发起对话的这个买家自己的订单,绝不能顺手查到别人的收货地址和手机号。
  • 运维 Agent 能执行 shell 命令,但不该在生产环境跑 DROP TABLErm -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.mdbeforeToolCall 的用法,写一个十几行的扩展,规则就一条:凡是工具名里带 deleterefund 的调用,一律先打印一行日志再放行。 放进 .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 之外的专门基础设施。

小结

  1. 企业里能做 ≠ 该做:Agent 能力越强越要治理,核心是回答“如何让它只在授权范围内行动、且每步可追溯”。
  2. 治理由三根支柱撑起——策略(Policy)定义“谁在什么条件下能对什么资源做什么”,执行点(Enforcement Point)卡在工具调用前守住边界,审计(Audit)记录每次判定以溯源合规。
  3. 策略模型用 RBAC(按角色)打底、ABAC(按属性)做细粒度;规则多了就交给 OPA 这类策略引擎解耦;高危不可逆操作用 human-in-the-loop 挂起等人。
  4. pi 不含内建权限系统(诚实的定位):路径一用扩展在 beforeToolCall应用层策略门(可被绕过),路径二靠沙箱/容器做系统层硬边界(绕不过),二者叠成纵深防御
  5. 重型企业级治理需要 pi 之外的专门基础设施(OPA、独立策略层如 Shannon),轻量 harness 刻意不覆盖这层深度。

策略门再周密,只要 Agent 手里还有一条通往系统的旁路,它就可能被绕过——真正拦不住也躲不开的边界,得靠隔离在系统层立起来。这正是下一章要讲的 pi 最实在的一块:安全执行,以及 Gondolin 微 VM、Plain Docker、OpenShell 三种容器化方案。