scope不是最终权限:Agent 能触达什么,和用户最终能不能做,为什么必须分开?
关键词:Agent 工具权限、Agent scope、Reach 与 Authority、业务系统授权、AI Agent 越权
假设一套商城系统已经存在下面这个接口:
POST /refunds
它可以创建退款申请。
现在,团队先后接入了三个 Agent:
- 面向消费者的售后助手;
- 面向商家的运营助手;
- 面向财务人员的内部助手。
三个 Agent 都可能在某些情况下需要使用退款能力,但它们显然不应该拥有完全相同的行动范围。消费者只能为自己的订单发起售后,商家只能处理自己门店的订单,财务人员也不能因为职位更高就绕过订单状态、退款上限和企业审批制度。
如果我们在接口上声明:
scope: refund.request.create
它到底意味着什么?
它是否等于“这个 Agent 可以退款”?是否等于“当前用户有退款权限”?是否已经说明了可以退哪一笔订单、多少钱、属于哪个租户?
都不是。
scope 真正适合回答的是一个更靠前的问题:
在当前 Agent 路由或场景中,这项业务能力是否允许进入它的触达范围?
至于当前主体能否对这笔具体订单执行退款,仍然必须由掌握真实业务状态的系统在调用时决定。
这就是 Agent 行动治理中一个很容易被混淆、也极其重要的分层:
Reach 决定 Agent 最多能触达什么,Authority 决定当前主体此刻究竟能不能做。
1. 三个相似问题,实际上属于三层
讨论 Agent 权限时,人们经常把下面三件事合并成一句“接口已经鉴权”:
| 问题 | 所属层次 | 典型依据 |
|---|---|---|
| 系统是否存在这项 API? | 接口层 | OpenAPI、路由、网络地址 |
| 这项能力能否进入当前 Agent 的可见和可调用范围? | Agent-facing 治理层 | 显式声明、scope、路由 allowlist、运行时策略 |
| 当前主体能否对当前业务对象执行这次操作? | 业务授权层 | 身份、角色、租户、资源归属、业务状态和实时规则 |
这三层会在一次调用中连续出现,但它们不能互相替代。
API 存在,不代表它应该成为 Agent 工具。
能力被当前 Agent 看见,不代表任何用户都可以调用它。
用户拥有某类业务权限,也不代表每一组参数、每一笔订单和每一种状态都允许执行。
如果把它们全部压缩成一个布尔值,系统最终只能在两个极端之间摇摆:要么为了安全只开放查询,要么把真实业务权限错误地交给 Agent 平台或模型。
2. 为什么 scope 不可能成为最终业务权限
最终授权通常依赖调用发生时才能确定的事实。
以退款为例,业务系统可能需要判断:
- 行动主体是谁,是否仍然处于有效登录或授权状态;
- 主体属于哪个企业、门店或部门;
- 订单是否属于当前主体能够管理的范围;
- 订单当前是待发货、已完成还是已经退款;
- 可退金额还剩多少;
- 当前操作是否超过岗位、金额或时间限制;
- 是否存在冻结、争议、风控或合规状态;
- 审批结果是否仍然有效,并且是否对应同一组参数。
这些事实会随时间变化,也可能存在于订单、账户、权限、风控和审批等多个系统中。
一个静态声明无法替业务系统持续掌握它们,更不应该尝试复制整套企业授权逻辑。
因此,当运行时判断:
scope_allowed = true
它只能说明当前 Agent 场景允许考虑或暴露这项能力,不能推出:
business_authorized = true
把前者当成后者,会形成一个危险的捷径:只要 Agent 通过了工具白名单,就仿佛获得了操作所有业务对象的通行证。
真正可靠的链路必须在请求到达业务系统后,再根据可信主体和实时业务状态完成最终授权。
3. ACC 中的 scope 到底声明什么
在 ACC v1 中,scope 是必填字符串,推荐使用点分隔的稳定能力标识,例如:
order.read
staff.search
refund.request.create
它是路由 allowlist 和治理策略引用的一项稳定单元。
一个 OpenAPI operation 可以这样声明:
paths:
/refunds:
post:
operationId: createRefundRequest
summary: Create a refund request.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [order_id, amount]
properties:
order_id:
type: string
amount:
type: number
x-agent-capability:
version: 1
enabled: true
scope: refund.request.create
risk:
level: high
subject:
required: true
approval:
when:
- param: amount
op: ">"
value: 1000
execution:
readonly: false
idempotent: true
这里的 scope 做了两件事:
- 给这项 Agent-facing 能力一个可以稳定引用的治理标识;
- 允许不同路由、场景或运行时策略决定是否把它纳入当前能力范围。
它没有做下面这些事:
- 没有声明谁是当前用户;
- 没有声明用户属于哪个租户;
- 没有判断用户能否操作某一笔订单;
- 没有替代风险、审批、审计和执行约束;
- 没有授权业务系统跳过自己的权限校验。
scope 是能力范围的稳定坐标,不是最终授权结论。
4. 为什么 operationId 还不够
既然 OpenAPI 已经有 operationId,为什么还要增加 scope?
因为两者服务的目的不同。
operationId 主要用于唯一标识一个 OpenAPI operation,方便生成客户端、函数名或调用代码。它通常靠近接口实现。
scope 则是治理策略引用的能力语义单元。它需要尽可能稳定,并能够跨越具体路由名、函数名或代码重构继续表达同一类业务能力。
例如,接口实现可能从:
operationId: postRefundApplication
重构为:
operationId: createRefundRequest
如果业务语义没有变化,它仍然可以保持:
scope: refund.request.create
这样,客服助手、商家助手和财务助手的能力 allowlist 不需要因为内部方法改名而整体迁移。
反过来,如果两个接口虽然技术名称相近,但业务后果不同,它们也不应该仅因为 operationId 相似就共享同一治理含义。
5. 同一套 API,如何在不同 Agent 场景中形成不同 Reach
ACC 不规定运行时必须使用“路由”这个产品概念。实现可以使用路由、产品、租户、场景、静态策略或其他明确记录的上下文。
为了便于说明,可以假设运行时维护三组 allowlist:
customer_service_agent:
- order.read
- logistics.read
- ticket.create
- refund.request.create
merchant_operations_agent:
- order.read
- inventory.adjust
- product.update
- refund.request.create
finance_agent:
- settlement.read
- refund.review
- refund.approve
这时,同一份业务 OpenAPI 可以根据当前场景呈现不同的工具集合。
能力是否进入 Agent 视野,可以抽象为:
可见能力
= operation 已显式启用
∩ scope 非空且被当前运行时策略允许
∩ 要求可信主体时,主体已经存在
∩ operation 具备足够的参数 Schema
这个交集决定的是 Reach。
当售后助手选择 refund.request.create 并生成订单号与金额后,业务系统仍然需要判断:当前主体是不是订单本人、订单是否允许退款、金额是否有效、审批是否成立。
这一步才是 Authority。
6. 为什么有最终授权,仍然需要 Reach 控制
有人可能会问:既然业务系统最终还会拒绝越权请求,为什么不把所有工具都交给 Agent,完全依赖后端授权?
因为最终授权和 Reach 控制降低的是不同风险。
Reach 缩小模型能够尝试的行动空间
如果客服 Agent 根本看不到员工删除、结算配置和库存覆盖工具,它就不会因为模糊意图、错误上下文或提示注入而选择这些能力。
Reach 提高工具选择质量
候选工具越贴近当前任务,模型越容易在相似接口之间做出正确选择。最小能力集合既是安全边界,也是降低工具歧义的工程手段。
Reach 让场景差异可以被检查和审计
组织可以清楚回答:匿名助手、客户助手、商家助手和内部助手分别被允许接触哪些业务能力,以及能力范围何时发生过变化。
Authority 保证绕过 Agent 运行时也不能越权
工具隐藏永远不能成为业务系统放弃授权的理由。攻击者可能绕过 Agent 直接请求 API,运行时也可能配置错误。最终授权必须在业务边界继续成立。
两层并存并不是重复建设,而是纵深防御:
Reach:减少 Agent 可以提出的行动
Authority:决定业务系统真正接受的行动
7. 为什么不把所有动态权限都编码进 scope
另一种常见冲动,是把租户、角色、金额和业务状态全部塞进 scope:
tenant_42.finance_manager.refund.create.amount_under_1000
这种命名看起来很精确,实际上会快速失控。
首先,它把会变化的业务事实固化进静态契约。用户调岗、阈值变化、订单状态变化,都可能要求重新生成 scope。
其次,它会导致 scope 数量爆炸。每个租户、角色、金额区间和资源状态组合都可能产生一个新字符串。
再次,它把本应由业务系统掌握的规则复制到 Agent 治理层。两边一旦更新不同步,就会产生互相矛盾的授权判断。
最后,它损害可移植性。一个标准字段如果必须理解每家企业的数据库、组织结构和状态机,就不再是跨实现契约,而会变成另一套私有策略语言。
更稳妥的分工是:
scope: refund.request.create
声明稳定的能力范围;运行时决定当前 Agent 场景是否允许触达;业务系统根据主体、租户、订单和实时规则完成最终授权。
8. 设计 scope 时,哪些命名更可靠
ACC v1 推荐点分隔格式,但没有规定全球统一的能力词典。具体命名仍由契约作者和部署组织维护。
工程上,一个好用的 scope 通常具有四个特点:
- 描述业务能力,而不是页面按钮;
- 在代码重构后仍然尽量稳定;
- 不包含用户、租户和订单等动态实例标识;
- 粒度足以区分后果不同的操作,但不复制整套业务规则。
例如:
| 较清晰 | 容易产生问题 | 原因 |
|---|---|---|
order.read |
order_page_button_3 |
页面实现不是稳定业务语义 |
staff.search |
admin |
角色名称没有说明具体能力 |
refund.request.create |
refund_everything |
过宽标识无法支持最小能力集合 |
inventory.adjust |
tenant_42_inventory_adjust |
动态租户不应固化进通用能力标识 |
refund.approve |
refund.create.under_1000 |
动态阈值通常属于审批或业务策略 |
命名不必追求一次覆盖所有未来场景,但必须能让策略作者、运行时实现者和审计人员对同一字符串形成一致理解。
9. 精确匹配与前缀匹配属于谁
运行时可能希望这样配置:
order.*
来允许一组订单相关能力。
ACC v1 允许实现支持精确匹配或前缀匹配,但具体匹配规则属于运行时策略,不属于 ACC 核心语义。
这一区分很重要。
如果一个实现把 order.* 解释为字符串前缀,另一个实现把它解释成层级通配符,第三个实现支持更复杂的策略表达式,它们的行为可能不同。部署方必须记录自己的匹配语义,不能因为都使用了 scope 字段,就假设所有运行时天然共享同一套通配规则。
可移植的部分是 scope 作为稳定治理单元;如何组织 allowlist、是否支持前缀以及冲突时如何处理,是运行时策略。
10. 一条完整链路应该如何分工
一个实现 ACC 的运行时,可以按照下面的顺序处理能力:
读取 OpenAPI operation
-> 解析 x-agent-capability
-> 校验 version、enabled 和 scope
-> 应用当前运行时的 scope allowlist
-> 要求主体时,确认可信主体已经存在
-> 只把通过暴露检查的能力交给 Agent
-> Agent 选择能力并生成参数
-> 按 OpenAPI 校验参数
-> 评估风险、审批意图和执行约束
-> 携带可信主体调用业务系统
-> 业务系统在调用时执行最终授权
-> 记录治理决定、结果或失败
这条链路里,模型可以提出“调用哪个能力、使用什么参数”,但它不能自行决定:
- 自己拥有哪些 scope;
- 当前路由允许哪些 scope;
- 谁是可信主体;
- 风险是否可以降低;
- 审批是否已经发生;
- 业务系统是否应当授权。
治理元数据必须来自可信契约与运行时策略,最终业务权限必须由业务系统掌握。
11. 五个常见误区
误区一:scope 已允许,所以业务系统可以不再鉴权
这是最危险的误解。scope 只管理 Reach,不能替代调用时的业务授权。
误区二:把角色名直接当作能力
finance_manager 说明了一个组织角色,却没有说明 Agent 究竟能读取、创建、审批还是删除什么。能力标识应尽量表达可治理的业务动作。
误区三:把租户和资源实例写进 scope
tenant_42.order_1001.read 会让静态能力标识随着动态数据无限增长。租户归属和资源权限应由可信上下文与业务系统验证。
误区四:让模型根据对话生成或修改 scope
模型输出属于不可信提议。它可以选择已被允许的工具,不能扩大自己的能力范围。
误区五:默认所有实现都支持相同的 * 匹配
ACC 不标准化通配策略。运行时必须公开并一致执行自己的匹配规则。
12. 一份最小检查表
设计 Agent 工具范围时,可以逐项确认:
- 每个 scope 是否表达稳定业务能力,而不是页面、角色或动态对象?
- 当前 Agent 场景是否维护明确的 scope allowlist?
- allowlist 使用精确匹配还是前缀匹配,是否已经记录?
- scope 被允许时,是否仍然要求必要的可信主体?
- 模型输出是否无法修改 scope 与 allowlist?
- 业务系统是否会在调用时重新校验主体、租户、资源和实时状态?
- 绕过 Agent 运行时直接请求 API 时,最终授权是否仍然成立?
- 日志能否解释某项能力为什么被暴露、隐藏、允许或拒绝?
- scope 命名发生变化时,是否有明确迁移策略?
如果这些问题没有答案,系统拥有的往往不是清晰的能力边界,而是一组看起来像权限、实际上职责混杂的字符串。
13. scope 的价值,在于把两类决定分开
Agent 要进入真实业务系统,既不能只依赖模型自律,也不能把企业全部权限规则复制到一份工具契约中。
一个可持续的分工应当是:
- 契约声明这是什么能力;
- 运行时根据
scope决定当前场景能否触达; - 可信身份链提供行动主体;
- 风险与审批机制处理外部决策;
- 业务系统根据实时事实完成最终授权。
scope 的意义并不是取代这些层,而是让它们不再混成一句含糊的“有权限”。
它为 Agent 的行动范围提供稳定坐标,同时把最终决定权留在最了解业务事实、也必须承担业务责任的系统中。
因此,判断一项能力是否安全,不应该只问:
这个 scope 是否被允许?
还必须继续问:
当前主体是否经过可信解析?当前参数是否有效?业务系统此刻是否真正授权这次行动?
前一个问题控制 Reach,后一个问题确认 Authority。
只有把二者同时保留,Agent 才能既获得足够的行动能力,又不会因为“看得见”而被误认为“做得到”。
延伸阅读
- ACC 规范:https://agentcapability.org/docs/spec/
- ACC 概念与边界:https://agentcapability.org/docs/concepts/
- ACC 实现者指南:https://agentcapability.org/docs/implementer-guide/
- ACC GitHub:https://github.com/agent-capability/agent-capability-contract
ACC 是 A2B 场景下的开放能力声明契约。它定义可移植的治理语义,但不替代任何业务系统的最终授权,也不赋予某个实现产品特殊地位。
更多推荐




所有评论(0)