关键词: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 做了两件事:

  1. 给这项 Agent-facing 能力一个可以稳定引用的治理标识;
  2. 允许不同路由、场景或运行时策略决定是否把它纳入当前能力范围。

它没有做下面这些事:

  • 没有声明谁是当前用户;
  • 没有声明用户属于哪个租户;
  • 没有判断用户能否操作某一笔订单;
  • 没有替代风险、审批、审计和执行约束;
  • 没有授权业务系统跳过自己的权限校验。

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 通常具有四个特点:

  1. 描述业务能力,而不是页面按钮;
  2. 在代码重构后仍然尽量稳定;
  3. 不包含用户、租户和订单等动态实例标识;
  4. 粒度足以区分后果不同的操作,但不复制整套业务规则。

例如:

较清晰 容易产生问题 原因
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 场景下的开放能力声明契约。它定义可移植的治理语义,但不替代任何业务系统的最终授权,也不赋予某个实现产品特殊地位。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐