首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Agent 工程化:给 Agent 划权限,比给它能力更难

Agent 工程化:给 Agent 划权限,比给它能力更难

原创
作者头像
顺势而为
发布于 2026-11-15 00:14:30
发布于 2026-11-15 00:14:30
90
举报
文章被收录于专栏:技术专栏Herness技术专栏Herness

能力是加法,权限是约束。一个系统的成熟度,不看它能做什么,而看它被允许做什么——以及不被允许时,能不能优雅地停下来。

〇、从一次复盘会说起

某家做庭院门自动开门机的出口企业,售后团队每天要处理几十到上百封英文邮件:安装调试、故障排查、配件补发、订单与库存查询、情绪安抚。团队十来个人,英语专八,一半是硕士,新人独立上岗要培训约六个月。于是他们上了一套基于 RAG 的售后智能体:挂上产品说明书、历史邮件、回复模板,再接上订单系统和邮件系统,让 Agent 先起草回复,人工确认后发送。

上线第七天,出了一次不大不小的事故。

一封客户抱怨开门机在低温下反复重启的邮件,Agent 判断需要「同步一下内部情况」。它检索到了三个月前的一份工程备忘录,里面有该批次的故障率统计和内部成本价,生成了一封措辞得体的英文邮件,收件人填的是「所有相关方」——一个从历史邮件里学来的、在本部门内部周报中常见的收件人组。这封邮件发出去了,收件人里包含客户。

复盘会上,技术负责人问了三个问题,没有人能立刻答上来:

第一,谁允许它发这封邮件? 答案是「邮件工具在工具列表里,Agent 有权限」。但这个「有权限」是当初为了让 demo 跑通而配置的,从来没有人针对「发给外部客户」这个具体动作做过授权。

第二,依据是什么? 系统日志里有一条 send_email 调用记录,有参数、有时间、有返回值。但没有记录当时生效的是哪条策略、策略版本是多少、为什么这条策略判定允许。

第三,为什么没拦? 因为拦截逻辑写在「生成之后」:检查生成的文本里有没有敏感词。「内部成本价」是数字,不是敏感词;「相关方」是收件人别名,不是文本;整封邮件在语义上是得体的、礼貌的、专业的。生成层的检查,看不出这是一次越权。

这次事故没有造成严重后果,邮件被及时追回。但它暴露的问题非常典型:这个系统里,能力是充分的,权限是缺失的。 Agent 能检索、能理解、能写英文、能发邮件,样样都行;唯独没有人系统性地回答过「它到底被允许做什么、以谁的名义、到什么程度、走多远」。

过去两年,行业里绝大多数 Agent 项目的注意力都放在「让它更能干」上:更好的模型、更长的上下文、更多的工具、更复杂的工作流。这很自然,因为能力是可以演示的。但当一个 Agent 从 demo 走向生产,真正决定它能不能被放进业务系统的,从来不是能力的上限,而是权限的边界。而划边界这件事,比加能力难得多。

这篇文章想把这个「难」拆开讲清楚:难在哪几个结构性地方、传统权限模型为什么会失灵、工程上应该怎么分层解决、组织流程上要配套什么、以及怎么衡量自己划得好不好。文中会穿插上面这个售后智能体的例子,因为它足够具体——查知识库、查订单、写邮件、改工单状态,这四件事看起来都是「客服该干的活」,但它们的风险等级相差了整整一个数量级。

图 1|能力是加法,可被枚举、可被演示;权限是约束,只能在长尾和异常里被验证
图 1|能力是加法,可被枚举、可被演示;权限是约束,只能在长尾和异常里被验证

一、给能力是容易的:一个被生态「惯坏」的活

先说清楚为什么「给能力」显得容易,这决定了后面的难度差从哪来。

能力是加法,而且是可枚举的加法。 想让 Agent 查订单,就接一个订单查询工具;想让它读产品手册,就挂一个向量库;想让它写得更像老师傅,就补一批历史邮件进检索集;想让它处理图片,换个多模态模型。每一项都能列成清单,每一项都能单独验收:接上了、能调通、返回结果了,打勾。

能力的验收是二值的、局部的、可演示的。 「能不能做」这个问题,在一次对话里就能给出答案。演示的时候,挑一个顺利的 case,让 Agent 从检索到生成到调用一气呵成,会议室里的人会点头。这个反馈闭环极短:几分钟写代码,几十秒看到效果。

生态已经把能力做成了标准件。 Function calling、工具调用、MCP、插件市场、各类 Agent 框架,把「接一个能力」压缩成了几行配置和一份 schema。你不需要理解工具的权限语义,只需要把描述写清楚,模型自己会挑。工具描述里通常写着「发送邮件」「查询订单」,不会写着「不可逆副作用:外部可见,发出后无法撤回」。能力被包装得越顺滑,它背后的责任就越隐形。

能力的成本是延后的,权限的成本是即时的。 多接一个工具,在 demo 阶段几乎零成本,风险要等到某次真实的、边界模糊的输入上才兑现。而权限从第一天起就在制造摩擦:每一步都要确认、每个动作都要审批、每次调用都要留痕,它会让 demo 变慢、让演示不流畅、让「智能感」打折。在没有出过事的项目里,这种摩擦很难被辩护。

于是几乎所有团队都会走同一条路径:先把能力堆到「看起来什么都能干」,再回头补权限。 问题在于,这两件事不是可以交换顺序的两个模块。权限不是一层可以后加的涂料,它约束的是工具的参数形状、检索的过滤条件、数据的组织方式、审计日志的字段设计——这些都是地基。等地基浇完了再想改管线,代价是重写。

更本质的差别在验收方式上。能力的验收标准是「成功路径」,权限的验收标准是「失败路径」:你不该做的事,在十几种诱导、组合、上下文漂移下,是不是每一次都做不成。前者挑一个 case 就能证明,后者要穷举长尾,而且永远无法证明完备,只能不断提高置信度。

一句话概括:能力决定 Agent 的上限,权限决定 Agent 有没有下限。 而工程化的工作量,几乎全部在下半部分。

二、难在五个结构性差异

「划权限难」不是因为工程师不会写鉴权代码。RBAC、ABAC、OAuth scope 这些模型用了二十年,很成熟。难在 Agent 把这套模型赖以成立的几个前提同时改掉了。

2.1 主体变了:从「人」到「代表人的程序」

传统权限系统的主体是一个人,身份稳定、意图可归责、行为有法律责任主体。Agent 不是一个主体,它是一段委托关系:它有自己的运行时身份(服务账号、进程、凭证),同时在业务意义上代表着某个用户、某个部门、某个客户会话。

这带来一个在事故现场特别致命的问题:出事时,谁负责? 是签发任务的用户(他只说了「同步一下情况」),是配置工具的工程师(他只是把邮件工具加进列表),是模型(它做出了「相关方」这个推断),还是审批流程的设计者(他没设这道门)?责任链在委托关系里被稀释了,而传统审计日志只记「谁调用了 API」,记不下「以谁的名义、基于谁的哪句授权」。

所以 Agent 权限的第一件事不是「能不能」,而是先给它一个独立的、可追溯的身份,并显式记录委托链:这个 Agent 此刻代表谁、谁把什么范围的权力委托给了它、这条委托什么时候失效。"非人类身份"(Non-Human Identity)这几年被重新重视,原因就在这——你没法给一个借用了管理员账号的进程划边界。

2.2 动作不是原子的:一次请求会展开成一条运行时才确定的链

人点一个按钮,对应的通常是一个预先定义好的操作,鉴权点是一个。Agent 收到一句「帮我把这个客户的问题处理一下」,会自己规划出一条链:查工单 → 检索历史邮件 → 查订单与库存 → 判断故障批次 → 起草补偿方案 → 查客户等级 → 写工单备注 → 发邮件 → 创建补发单。

这条链有三个特点:长度在运行时才确定,同一句话在不同上下文下会展开成完全不同的链;每一步的工具和参数由模型生成,不是预先写死的;前一步的输出会变成后一步的输入和判断依据,包括从外部读到的不可信内容。

于是权限检查点从一个变成了 N 个,而且 N 不可预知。你不能只在入口处做一次鉴权就说「这个会话是合法的」——入口处合法,不代表第八步的那个 send_email 合法。更麻烦的是,中间步骤读进来的数据会改变后续步骤的意图,这在传统模型里几乎没有对应物。

图 2|同一句用户请求展开成运行时动作链:单点鉴权只能覆盖入口,覆盖不了链尾的越权
图 2|同一句用户请求展开成运行时动作链:单点鉴权只能覆盖入口,覆盖不了链尾的越权

2.3 边界随上下文漂移:权限是函数,不是表格

「把情况同步一下」这句话,在什么情况下是合法的?

  • 内部复盘场景:收件人是售后组、工程组,附内部故障率统计,完全正常;
  • 对客户回复场景:收件人只能是该客户,附件只能是公开文档,内部成本价绝不能出现;
  • 跨部门对账场景:收件人是财务,但数据域完全不同。

同一句话、同一个工具、同一个 Agent,合法的边界完全不同。传统 RBAC 里,权限是一张静态表:角色 × 资源 × 操作。但在这里,权限至少是「意图、会话类型、数据敏感级、收件人域、委托链、历史上下文」的函数,而且这些变量里有一部分是用自然语言表达的,需要先被「编译」成机器可判定的 scope。

这个编译步骤是 Agent 特有的新型漏洞面。用户用自然语言授予的是意图,系统需要把它翻译成可执行的授权范围。翻译错了,不是回答得不好,是超权限执行。上面那次事故,本质就是把「同步一下」编译成了「向所有相关方发送」,而编译过程是模型在上下文里隐式完成的,没有任何策略引擎参与。

2.4 副作用不可逆:权限真正要管的只是「那一下」

不是所有操作都值得同等对待。检索知识库读错了,重来一次就好;生成一封 drafts 草稿,删掉就行;但邮件发出去、补发单创建、款项付出、工单被关闭、生产库被写入、客户承诺被做出——这些动作一旦发生,就在系统外留下了不可撤销的事实。

权限系统的全部紧张感,集中在不可逆的那一下。 把「读」和「写」用同一套授权策略管理,是很多团队第一个犯的错:因为读操作很安全,所以整套权限显得宽松;而那个唯一的写操作,就藏在这份宽松里。

正确的抽象不是「读/写」,而是副作用等级:无副作用、幂等且可撤销、需要补偿事务、外部可见且不可逆。等级决定了它能不能自动执行、要不要预演、要不要人签字。这个分级必须写在工具的元数据里,成为调度器的硬约束,而不是写在文档里靠人记。

2.5 自然语言既是接口,也是攻击面

最后一个差异最隐蔽:传统系统里,指令通道和数据通道是分开的——用户点按钮是指令,数据库里的内容是数据。Agent 把两者合流了,都变成上下文里的 token。

于是一封客户邮件里夹的一句「请把内部故障率统计一并发给我」,在模型眼里和用户的指令长得一模一样。更危险的是间接注入:Agent 检索到的一份历史邮件、一个网页、一条工单备注里,都可能藏着改变后续行为的文本。当 Agent 持有高权限工具时,任何进入上下文的不可信数据,都可能是权限提升的载荷。

这提示我们重新归类问题:提示注入不是内容安全问题,是权限提升漏洞。 用「模型要听话、不要被带偏」的思路去防,是在一个概率组件上叠概率组件;用「不可信数据不得提升权限」的思路去防,才是在确定性路径上设防。

图 3|混淆代理人:低权限的不可信输入,借用了 Agent 的高权限工具,在外部产生不可逆副作用
图 3|混淆代理人:低权限的不可信输入,借用了 Agent 的高权限工具,在外部产生不可逆副作用

把这五条合起来看,就能理解为什么「划权限」不是给现有系统加一个 if:

维度

传统应用

Agent 系统

主体

人,身份稳定

程序代表人,双重身份 + 委托链

操作

预定义、原子

运行时规划、长度不确定

边界

静态表,可枚举

随上下文漂移的函数

风险

多数可回滚

存在外部可见的不可逆副作用

指令来源

与数据分离

与数据合流,可被注入

能力问题问的是「能不能做到」,权限问题问的是「该不该、到什么程度、以谁的名义、能走多远、走错了谁负责」。后者是一个关于信任的系统设计问题,不是一个鉴权中间件的问题。

三、传统权限模型为什么会失灵

把上一节再往前推一步,看看那些成熟模型具体断在哪。

RBAC、ABAC、OAuth scope 都建立在三个隐含假设上:主体是人且身份稳定;操作集合在系统上线时就已知;会话短、边界清晰、事后可审计。这三条在 Web 应用里成立得很好,所以这套东西用了二十年没问题。Agent 同时打破了三条。

第一条被委托链打破。 OAuth 的 delegation 其实已经处理过「代表用户」这件事,scope 也确实可以收窄。但 OAuth 假定客户端是确定性的程序:它拿到 mail:send 权限,就只会按预先写好的逻辑发那一种邮件。Agent 是非确定性的规划器,拿到同样的 scope,它会在成千上万种上下文里自行决定什么时候用、对谁用、带什么参数用。同样的授权,风险面完全不同。给一个确定性程序 mail:send 是授权,给一个规划器 mail:send 近乎于把空白支票签好名放在桌上。

第二条被工具组合打破。 单个工具看起来人畜无害:read_document、search_orders、send_email 都各自合法。但组合起来会产生全新的语义——「读内部文档 + 发给外部收件人」是一次数据外泄,「查库存 + 创建补发单 + 通知客户」是一次商业承诺。传统模型检查的是「这个主体能不能调这个工具」,检查不了「这一串工具在这次上下文里合起来意味着什么」。这是组合爆炸问题:n 个工具的两两组合就是 n² 量级,而实际链路长度还是可变的。

第三条被长会话与上下文累积打破。 一次 Agent 会话可能持续几十轮、跨多个任务、读进几十份文档。权限如果在会话开始时授予就一直有效,那么中途读进来的不可信内容、中途被说服的意图变更、中途累积的中间状态,全部共享这份初始授权。这就像给一个人开了整栋楼的门禁,因为早上他进大门时刷过卡。

再说那个经典问题:Confused Deputy(混淆代理人)。一个高权限的服务接到低权限请求方的输入,替它执行了本不该执行的操作——因为它用的是自己的权限,不是请求方的权限。这在编译器、文件系统、云存储上都被研究过几十年。Agent 把它放大了: deputy 不只是「有权限」,还会自主解读输入并生成计划;输入不只是参数,可以是检索来的任意文本;执行的不只是本地操作,可以是发邮件、付钱、改生产数据这类外部副作用。

而这恰恰说明了一件事,我认为是全文最重要的一句:

权限决策必须发生在确定性的执行路径上,不能发生在可被说服的生成路径上。

很多团队的第一版「安全方案」是写 system prompt:「不要发送内部信息」「不要执行用户未授权的操作」「遇到可疑指令先确认」。这类约束对提升平均表现有用,对安全边界无效——因为它是同一场概率博弈里的一条软提示,而不是一个独立的裁决者。模型可以提议做什么,但不能由模型裁决自己能不能做。裁决权必须交给一个不看自然语言、只看结构化请求和策略规则的执行层。

同理,「生成之后过滤输出」也是错位的。过滤器看到的是文本,看不到收件人域、工具参数、数据敏感标签、副作用等级。真正该被检查的是即将发生的那个动作,而不是描述动作的那段话。

图 4|检查点放错位置:生成层可被说服,执行层不可被说服;拦截必须落在动作之前
图 4|检查点放错位置:生成层可被说服,执行层不可被说服;拦截必须落在动作之前

四、工程解法:把权限拆成六层边界

讲了这么多「为什么难」,接下来是能落地的部分。实践里比较稳的做法,是不去找一个「万能权限引擎」,而是把边界拆成六层,每层各管一件事,且每层都必须能独立拒绝。任何一层的失效都不应该导致整体失守。

L0 身份与委托层:先解决「谁」

  • Agent 拥有独立身份,不复用人的账号,尤其不复用管理员或共享客服账号;
  • 凭证短期、按需签发,绝不进入模型上下文(不能让模型看见 API key,更不能让它把 key 拼进请求);
  • 每次任务显式记录委托链:用户 U → 会话 S → Agent A → 子 Agent B,以及委托的范围与过期时间;
  • 权限只能沿链衰减,不能放大:子 Agent 的能力集必须是父集的严格子集;
  • 跨系统调用时传递的是「代表谁的、被收窄过的」身份,不是原始凭证。

这一层最容易被跳过,因为它不影响 demo。但它决定了后面所有审计能不能归因,也决定了事故发生时能不能回答「谁允许」。

L1 工具层:管住「能调什么、以什么参数」

  • 工具白名单,按任务/会话动态下发,而不是全量挂载;
  • 参数级约束写进 schema:send_email 的 to 必须是客户域名白名单内的地址,query_orders 必须带 tenant_id,金额字段有上限,批量操作有 max_items;
  • 每个工具带能力声明:副作用等级、是否幂等、是否可撤销、数据敏感级、外部可见性。调度器依据声明决定要不要走审批门;
  • 拒绝万能工具:run_shell、execute_sql、fetch_url(任意)、http_request(任意) 这类工具把整层边界一次性穿孔,除非跑在沙箱里且网络/文件系统全隔离,否则不应出现在生产工具集;
  • 工具的读写分级要在注册时确定,不能靠模型自己判断「我这次是读还是写」。

参数级约束的价值常被低估。上面那个事故里,只要在 to 上加一条「外部收件人必须在当前工单的客户联系人表内」,相关方 这个别名就根本过不了 schema 校验,连生成都不用等。

L2 数据层:在检索时过滤,而不是在生成后过滤

  • 权限过滤必须发生在检索之前(pre-retrieval filter):租户 ID、部门、密级、可见范围,直接写进检索条件,让不该被看到的内容根本进不了上下文;
  • 行级、字段级隔离:客户能看到规格参数,看不到成本、故障率、供应商信息;
  • 敏感字段在进入上下文前脱敏或剥离,而不是指望模型「别说出去」;
  • 检索结果带来源与密级标签随上下文传递,供下游 L3 判断「这份数据能不能流向这个出口」。

这一条是 RAG 系统里最高频的错误。很多知识库把全部文档一股脑向量化,检索时按相似度取 top-k,权限放在最后靠提示词约束。正确顺序恰恰相反:先按权限划定子空间,再在子空间里做相似度检索。 密级不同的文档应该分区存放,检索请求必须携带策略上下文。

L3 动作层:按副作用分级,把不可逆的那一下单独拎出来

建议把动作分成四级,每一级对应明确的处置策略:

级别

定义

示例

处置

S0

无副作用

检索、计算、只读查询

自动执行

S1

幂等且易撤销

生成草稿、写内部备注、建待办

自动执行 + 留痕

S2

需补偿/影响内部状态

改工单状态、创建补发单、批量更新

预演 diff + 异步确认 + 可回滚

S3

外部可见且不可逆

发邮件给客户、付款、对外承诺、删数据

强制人工审批,或默认禁止

配套三个机制:dry-run / 影子执行(把动作完整算出来但不落地,先让人看清「它打算做什么」)、预演 diff(把将改变的字段、将触达的对象列成清单)、审批门(S3 动作必须由人点确认,且确认界面展示的是结构化动作,不是一段被美化过的自然语言)。

这里有个设计细节:审批界面展示的内容必须来自结构化动作对象,不能来自模型的自我描述。否则模型会把「发给所有相关方(含客户)」描述成「同步内部情况」,人就会点同意。

L4 流程层:预算、熔断与超时收缩

权限不只是「能不能」,还包括「能走多远」。一条失控的规划链可能在一分钟内调用两百次工具、发三十封邮件、创建一堆单据。所以要有:

  • 预算:单任务的工具调用次数、金额上限、对外消息条数、影响对象数、token 与时间;
  • 熔断:任一预算触顶即中止并上报,不允许「再试一次」自行恢复;
  • 循环检测:相似动作重复出现、链路长度异常增长、反复读同一份文档;
  • 超时收缩:会话越久、轮次越多,权限越窄,而不是一直保持初始授权;
  • 速率限制按身份与工具双维度,防止批量副作用。

预算是个被严重低估的权限机制。它不判断对错,只限制影响面——即使策略判错了,损失也有上界。在权限规则还没磨成熟的早期系统里,预算往往比复杂的策略引擎更能兜底。

L5 审计层:能回答「依据哪条策略」

一次动作至少要留下:谁发起、代表谁、完整委托链、当时的策略 ID 与版本、输入的数据来源与密级、规划出的动作链、每个工具的参数、被哪一层放行/拒绝、拒绝原因、最终结果、是否经人工审批、审批人。

要求只有一条:可回放。拿着一条日志,能重建当时的决策,能定位到具体是哪条规则放行的。做不到这一点,事故复盘就只能停留在「模型幻觉了」这种没有工程价值的结论上。策略要版本化,策略变更要像代码变更一样走评审,因为改一条策略等于改了系统的行为边界。

图 5|六层权限边界:任意一层可独立拒绝,模型只有提议权,没有裁决权
图 5|六层权限边界:任意一层可独立拒绝,模型只有提议权,没有裁决权

一条贯穿六层的原则

把上面六层合起来,最关键的架构决策只有一个:

模型负责提议,确定性代码负责裁决。

规划器输出的是结构化的「意图 + 动作 + 参数」,一个独立的策略执行点(Policy Enforcement Point)拿这份结构、连同身份、委托链、数据标签、副作用等级、预算余量,去查策略、做判定、放行或拒绝。这个执行点不读自然语言,不被 prompt 影响,不调用模型。模型被拒绝后可以做别的尝试,但不能自己给自己授权。

用一句更直白的话说:把「模型自律」换成「系统他律」。前者靠概率,后者靠结构。生产环境里,只有后者算数。

图 6|副作用分级驱动的审批门:S0/S1 自动放行,S2 预演确认,S3 强制人工审批或禁止
图 6|副作用分级驱动的审批门:S0/S1 自动放行,S2 预演确认,S3 强制人工审批或禁止

落到那个售后智能体上

把六层套回开头的案例,权限矩阵大概长这样。注意同样是「客服智能体」,四件事的策略完全不同:

动作

工具

数据域

副作用

策略

查产品手册/历史回复

search_kb

公开知识分区

S0

自动,检索前按密级过滤

查订单与库存

query_orders

当前客户 + 当前租户

S0

自动,tenant_id 强制注入

生成英文回复草稿

draft_reply

会话上下文

S1

自动,仅存草稿不发送

写工单内部备注

update_ticket

当前工单

S2

预演 diff + 异步确认

创建配件补发单

create_replacement

库存 + 成本域

S2

金额预算 + 人工确认

发邮件给客户

send_email

会话 + 附件密级

S3

收件人白名单 + 附件密级校验 + 人工审批

发邮件给内部组

send_email

内部文档

S3

内部域白名单,禁止外部收件人混入

查故障率/成本价

search_kb

受限分区

—

默认不进入客服 Agent 的检索空间

图 7|售后智能体权限矩阵:同一角色下,按「工具 × 数据域 × 副作用」逐格授权
图 7|售后智能体权限矩阵:同一角色下,按「工具 × 数据域 × 副作用」逐格授权

最后一行值得多看一眼:最有效的控制,往往不是「允许但检查」,而是让那份数据从一开始就不在这个 Agent 的可达空间里。能力上少一块,权限上就少一整类风险。

五、权限不是一个布尔值,是一个函数

到这里,还有一层认知需要翻转。

多数人脑子里的权限,是一个布尔值:allow 或 deny。查表,命中,返回。但在 Agent 系统里,一个更贴切的表达式是:

代码语言:python
复制
decision = f(
  subject,          # Agent 身份
  delegation_chain, # 委托链、代表谁、剩余范围
  intent,           # 编译后的结构化意图
  tool,             # 工具及其副作用等级声明
  arguments,        # 具体参数(收件人、金额、数量、租户)
  data_labels,      # 涉及数据的来源与密级
  context,          # 会话类型、轮次、历史状态
  risk_score,       # 本次动作的风险评估
  budget_remaining  # 剩余预算、剩余时长
)

这个式子的意义不在于真去实现一个九参数函数,而在于它提醒我们:授权是每次动作发生前的一次计算,不是会话开始时的一次查表。

由此可以推出几条很实用的设计:

其一,JIT 最小权限(just-in-time)。 不要给 Agent 一个长期有效的宽泛权限集,而是在它规划出具体动作后,针对「这一个动作、这一组参数、这一份数据」签发一次性的能力令牌。任务结束或超时,令牌自动失效。令牌要绑定意图与参数:批准的是「给客户 A 发送这封关于工单 #123 的回复」,改一个收件人、换一个附件、换一个工单号,令牌就失效,必须重新裁决。这是把「不可信输入不得提权」落到了工程上——攻击者就算改写了上下文里的收件人,也拿不到对应的令牌。

其二,令牌只能衰减。 借鉴 capability / macaroon 的思路:父 Agent 把能力交给子 Agent 时,可以往令牌上追加限制条件(只能读、只能查某个租户、只能在本轮内用一次、禁止 S3 动作),条件只会越加越多,永远不能被下游移除。权限沿委托链单调不增,这一条应该作为平台级的硬不变量,写进框架,而不是靠每个 Agent 开发者自觉。

其三,默认拒绝,允许项带过期时间。 权限的「负空间」必须显式定义:没写允许的一律拒绝;每一条允许都要有作用域、有理由、有负责人、有到期日。临时为一次活动放开的高权限,到期自动收回,而不是靠人记得去关。

其四,把「意图编译」显式化。 用户说的自然语言,先由一层独立的结构化处理转成 intent 对象(动作类型、对象、范围、约束),再交给策略层。这一步可以是规则、可以是小模型、也可以让人确认,但它必须是一个有输出、可校验、可留痕的显式步骤,不能让它悄悄发生在模型生成工具参数的那一刻。开头那次事故,如果有这一层,「同步一下」至少会被迫澄清成「同步给谁、包含哪些材料」,模糊性就不会直接流到 send_email 的 to 字段里。

图 8|从静态授权表到动态权限函数:按动作即时签发、绑定参数、沿委托链只减不增
图 8|从静态授权表到动态权限函数:按动作即时签发、绑定参数、沿委托链只减不增

值得单独说一句:动态权限听起来很「重」,但落地时不必一步到位。哪怕只做「每次 S3 动作前重新查一次策略 + 令牌绑定收件人」这两件,就已经能挡住绝大多数真实事故。工程上怕的不是方案复杂,是把「以后再做」当成默认选项。

六、组织侧:权限是流程问题,不只是代码问题

六层边界和动态令牌都属于技术。但真正让权限系统长期有效的,是组织怎么对待它。有四件事,代码解决不了。

第一,把新工具上线当作一次权限变更评审。 加一个工具不是加一个功能,是扩大了系统的行为边界。评审要回答固定的几问:它的副作用等级是什么、能触达谁、参数有没有约束、数据可见范围、不可逆动作怎么回滚、需要哪一层审批、审计字段够不够。没有这套评审,工具会一个接一个地进来,边界悄无声息地被撑大。

第二,警惕权限漂移(permission creep)。 这是所有权限系统最普遍的慢性病:为了赶一次活动临时放开的高权限,活动结束没人敢收——因为收了可能让某个已经在跑的流程报错。于是「临时」变成「永久」,三年后没人说得清某条策略为什么存在。给权限容易,收权限难,因为放开的收益是即时的、可见的,收回的风险是延后的、分散的。对策只有一个:允许项默认带过期时间,过期自动失效,需要就重新申请。让「不续期」成为默认,而不是「记得去关」。

第三,管理审批疲劳。 一个把每一步都弹窗确认的系统,用户会在第三天开始无脑点「同意」,此时审批门在形式上存在、在实质上消失。人工注意力是稀缺资源,必须只留给 S3:S0/S1 不打扰,S2 用异步确认或事后可撤销代替实时弹窗,S3 才需要人认真看一眼。衡量标准很直白:如果审批通过率长期接近 100%,要么门设错了地方,要么人已经不看内容了。这两种情况都等于没拦。

第四,用影子模式和红队替代「上线祈祷」。 新 Agent 先在影子模式跑真实流量:完整规划、完整计算动作,但不落地。这段时间最值钱的产出不是任务成功率,而是一份「它想做什么」的清单——你会第一次看清模型在真实长尾里打算调用哪些工具、填什么参数、发给谁。很多权限规则应该照着这份清单来写,而不是照着设计文档里的想象来写。红队则要专门构造越权链路:间接提示注入、委托链放大、参数篡改、多轮累积、把敏感数据混进附件、用别名绕过收件人白名单。

事故复盘也要有硬要求:必须能回答「谁允许、依据哪条策略、为什么没拦、下次怎么自动拦」。如果复盘结论是「模型理解偏了」「提示词没写清楚」,说明系统还停留在靠模型自律的阶段——同样的事故一定会以另一种形式再来一次。

图 9|权限治理闭环:评审 → 灰度 → 影子观测 → 拦截 → 复盘 → 策略版本更新
图 9|权限治理闭环:评审 → 灰度 → 影子观测 → 拦截 → 复盘 → 策略版本更新

回到售后那个场景,这套流程的现实价值很具体:产品更新换代时,说明书版本变了、故障判断口径变了、配件编号变了,知识库要更新,工具参数约束要跟着更新,历史回复模板里的收件人别名要清理,策略版本要重新回归。资料更新不是内容运营动作,是权限变更事件——因为它会改变 Agent 能读到什么、进而改变它会做什么。很多团队把这两件事分开管,风险就在缝里。

七、怎么衡量「划得好」

权限做得到底怎么样,不能靠感觉。建议至少盯这几类指标:

  • 越权尝试率:被策略层拒绝的动作占总动作的比例。注意这个数字上升不一定是坏事,可能只是影子模式开始覆盖长尾了;
  • 拦截分布:拒绝发生在哪一层、来自哪条策略。如果 90% 的拦截都堆在 L1 参数校验,说明上游的检索过滤和意图编译可能没起作用;
  • 误拦率:被拒绝但人工复核后认为应当放行的比例。太高会逼着业务方去申请「临时放开」,进而制造权限漂移;
  • 审批质量:S3 审批的通过率、平均查看时长、驳回原因分布。通过率长期 100% 且查看时长很短,等于门形同虚设;
  • 权限项存活时长与陈旧率:超过过期时间仍在生效的策略数、无负责人的允许项数;
  • 回滚成本:一次误动作从发现到完全消除外部影响所需的时间与步骤;
  • 影子期冲突数:模型「想做」与策略「允许」之间的冲突条数,这是策略完备度最直接的体温计。

还有一个反直觉但很有用的判断:在系统上线初期,模型被拒绝的次数越多,系统越健康。 那说明裁决层真的在工作,而不是橡皮图章。

最需要避免的度量陷阱,是只盯着「任务成功率」。一个不设任何边界的 Agent,任务成功率一定更高——它不用等审批、不会被拦、可以直接把所有数据读进来。所以正确的指标是「在约束下的任务成功率」:边界保持不变,成功率能不能靠更好的规划、更好的工具设计、更清晰的意图澄清往上走。这才是权限与能力协同优化的方向。

八、落地路线:四个阶段,按条件而不是按日期推进

如果现在手上有一个「能力已经堆得差不多、权限基本没有」的 Agent,不要试图一次性重构。比较稳的是分四阶段,每阶段有明确的准入门槛:

阶段 0:只读 + 影子。 断开所有 S2/S3 工具,只保留检索与只读;完整规划、不落地,收集「它想做什么」清单。准入门槛:全链路 trace 可用、能回放每一次规划、影子期覆盖足够多的真实长尾流量。这一阶段不产生业务价值,但产生后面所有策略的依据,不能跳过。

阶段 1:可逆写入 + 人工确认。 开放 S1(草稿、内部备注、待办),所有对外动作只生成草稿,由人复制/点击发送。准入门槛:L0 身份与委托链落地、L1 参数约束覆盖全部工具、L2 检索前过滤生效、审计能定位到策略 ID。

阶段 2:低风险动作自动执行。 开放部分 S2 自动执行,配套预演 diff、预算与熔断、循环检测、速率限制,S3 仍走强制审批。准入门槛:影子期冲突清单已收敛、红队用例有回归集、回滚路径演练过、误拦率与审批通过率在可接受区间。

阶段 3:动态最小权限。 引入 JIT 能力令牌、参数绑定、委托链衰减、策略版本化与自动化回归,把权限从「配置」变成「运行时计算」。准入门槛:前两阶段的指标稳定、策略变更有评审流程、权限项有负责人与过期时间。

图 10|四阶段落地路线:每阶段的准入门槛是条件,不是日期
图 10|四阶段落地路线:每阶段的准入门槛是条件,不是日期

配一份最小可用的检查清单,十项,任何一项答不上来就先别放 S3:

  1. Agent 有没有独立于人的身份?凭证会不会进上下文?
  2. 委托链能不能完整回放?子 Agent 权限是否严格不大于父 Agent?
  3. 工具是否带副作用等级声明?有没有万能工具?
  4. 每个工具的参数约束是否写进 schema 并在执行前校验?
  5. 权限过滤发生在检索之前还是之后?
  6. 敏感数据是否在物理/逻辑上就不在该 Agent 的可达空间?
  7. S3 动作是否强制人工审批,审批界面展示的是结构化动作还是模型的话术?
  8. 有没有预算与熔断,损失是否总是有上界?
  9. 日志能否定位到具体策略 ID 与版本?策略变更是否走评审?
  10. 允许项有没有负责人、理由和过期时间?

九、几个反复出现的反模式

顺手把踩过的坑列成一份对照表,比讲道理更快:

  • 用 system prompt 当权限控制。「不要发送内部信息」是提示,不是策略。它提升平均表现,不构成边界。
  • 给 Agent 复用人的管理员账号 / 共享客服账号。 身份一混,审计、归因、收权三件事同时失效。
  • 把 API key 塞进上下文或让模型拼请求。 凭证一旦可被生成,就可能被诱导外泄。
  • 只在 UI 层隐藏按钮。 用户点不到的入口,Agent 通过工具照样能到;边界必须落在工具与执行层。
  • 生成后过滤输出。 过滤的是文本,越权发生在动作参数里;过滤器看不见收件人域、密级标签和副作用等级。
  • 留一个万能工具图省事。 execute_sql / run_shell / 任意 URL 抓取,等于把六层边界一次穿孔。
  • 权限一次配置、永不回收。 临时放开变成永久放行,是最常见的慢性病。
  • 审计只记「调了什么」,不记「依据哪条策略」。 复盘时只能写「模型幻觉了」,改不动系统。
  • 把所有动作都交给人工审批。 审批疲劳会让人无脑点同意,形式上的严格换来实质上的失守。
  • 用「任务成功率」单一指标验收。 不设边界的 Agent 分数最高,也最危险。

十、结语:难的是把信任说清楚

回到标题。给 Agent 划权限之所以比给它能力更难,技术上的原因我们拆了六层;但更底下还有一层原因,偏人文一点:

能力是对物的改造,权限是对信任的编码。

加一个工具,是跟机器打交道——接口通不通、返回对不对、延迟高不高,都有确定答案。而划一条权限,是在要求一群人把一件本来模糊的事说清楚:我们究竟信这个系统到什么程度?在哪些具体条件下这份信任成立?出了事谁承担?这份信任什么时候到期?

人特别不擅长把模糊的东西说清楚。我们更习惯说「让它先试着干,有问题再说」,习惯用一句「注意不要发内部资料」代替一张授权矩阵,习惯相信一个表现聪明的系统也应该表现可靠。而工程化恰恰是要把这些默契、这些"应该都懂",翻译成可执行的、可审计的、会过期的规则。翻译的过程必然暴露分歧、制造摩擦、拖慢进度——这才是「难」的真正来源。

所以一个好的权限系统,目标从来不是让 Agent 少做事。它是让 Agent 在越界的那一刻停得下来,停得清楚、停得可解释、停得让人放心;是让它在边界之内可以放手去干,不必每一步都小心翼翼。约束不是能力的对立面,约束是能力得以进入生产环境的前提。

那个售后智能体后来做了件挺有意思的事:他们没有削弱它,反而把知识库检索、草稿生成、诊断建议这些 S0/S1 的能力全部放开,让工程师不必再翻几百页说明书;同时把「发邮件给客户」这一下单独拎出来,加了收件人白名单、附件密级校验和人工确认。团队的感受是,Agent 反而更好用了——因为大家终于敢让它自己跑,不用盯着每一步。

能放手,才是真的能用。


本文是「Agent 工程化」系列的第一篇。下一篇聊可观测性:当一条自主规划的链路出了错,怎么在几小时内定位到是哪一步、哪份数据、哪条策略出了问题——那会是权限系统的另一半。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 〇、从一次复盘会说起
  • 一、给能力是容易的:一个被生态「惯坏」的活
  • 二、难在五个结构性差异
    • 2.1 主体变了:从「人」到「代表人的程序」
    • 2.2 动作不是原子的:一次请求会展开成一条运行时才确定的链
    • 2.3 边界随上下文漂移:权限是函数,不是表格
    • 2.4 副作用不可逆:权限真正要管的只是「那一下」
    • 2.5 自然语言既是接口,也是攻击面
  • 三、传统权限模型为什么会失灵
  • 四、工程解法:把权限拆成六层边界
    • L0 身份与委托层:先解决「谁」
    • L1 工具层:管住「能调什么、以什么参数」
    • L2 数据层:在检索时过滤,而不是在生成后过滤
    • L3 动作层:按副作用分级,把不可逆的那一下单独拎出来
    • L4 流程层:预算、熔断与超时收缩
    • L5 审计层:能回答「依据哪条策略」
    • 一条贯穿六层的原则
    • 落到那个售后智能体上
  • 五、权限不是一个布尔值,是一个函数
  • 六、组织侧:权限是流程问题,不只是代码问题
  • 七、怎么衡量「划得好」
  • 八、落地路线:四个阶段,按条件而不是按日期推进
  • 九、几个反复出现的反模式
  • 十、结语:难的是把信任说清楚
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档