一份面向 Agent 的能力契约,应该描述到什么程度?
当开发者第一次看到 ACC(Agent Capability Contract,Agent 能力契约)里的enabled、scope、risk、subject、approval和execution,很容易继续追问:
- 既然已经有
approval,为什么不直接声明谁能审批、要几级会签? - 既然已经有
subject,为什么不把tenant_id、角色和数据权限一起标准化? - 既然已经有条件审批,为什么不支持累计金额、跨工具前置条件和事务回滚?
- 既然要让 Agent 安全办事,为什么不把整套企业策略都写进同一份契约?
这些问题都很合理。
但答案不是“ACC 还没来得及加字段”,而是:其中很多能力有意没有进入 ACC Core。
一个开放契约的目标,不是用一份越来越大的 YAML 接管企业已有的身份系统、审批平台、租户模型和工作流引擎。它应该只标准化那些能够被不同运行时一致理解、确定执行并通过 Conformance 验证的最小治理语义。
这篇文章只讨论一个核心问题:
为什么审批人、租户隔离、业务规则和事务编排明明都很重要,却不应该被塞进 ACC 核心?
我们也会结合 BailingHub 的实现说明:标准没有定义某项能力,不等于产品不能实现;产品实现了某项能力,也不等于它自动成为开放标准的一部分。
一、先看一份“什么都想管”的巨型注解
假设我们准备给退款接口增加 Agent 能力声明,并试图一次性把所有规则写进去:
# 下面是刻意设计的反例,并不是 ACC 字段x-agent-capability:enabled:truescope:refund.executeapproval:approver_role:finance_managerlevels:2delegates_allowed:truetimeout:24hescalation_to:finance_directortenant:source:jwt.claims.tenant_idisolation:row_leveltable_field:merchant_idpolicy:expression:amount <= order.refundable_amountdata_source:mysql.orderstransaction:steps:-freeze_balance-create_refund-update_order-notify_customercompensate:create_refund:cancel_refundfreeze_balance:unfreeze_balance它看起来很完整,甚至很“企业级”。
但换一个组织,问题马上出现:
- 审批人可能不来自
role,而来自部门、金额区间、项目归属、临时授权或 OA 流程; - 有的系统使用 JWT,有的使用 PHP Session、企微成员 ID、证书、服务账号或签名票据;
- 有的租户按数据库隔离,有的按 Schema、行级字段、组织树、商户关系或资源归属隔离;
refundable_amount是实时业务状态,不是能力契约中的常量;- 退款的补偿不一定等于调用一个反向接口,还可能涉及资金渠道、对账、人工介入和不可逆外部通知;
- 无 UI 的网关、队列消费者和命令行 Agent,根本没有同一种审批交互界面。
于是,这份看似统一的声明实际上只是把某个产品、某个组织和某套数据库的假设写成了公共字段。
其他实现即使能解析 YAML,也无法以相同语义兑现它。
这不是可移植标准,而是把私有业务系统藏进注解里。
二、一个字段进入 ACC Core,必须先通过六道门
ACC 的设计依据给出了一组核心字段准入测试。一个新字段至少需要同时满足:
- 它会改变可移植的 Agent 治理语义;
- 相互独立的 Parser 或 Runtime 能实现相同含义;
- 不依赖某个产品的私有数据库、UI、身份目录或工作流,就能测试行为;
- 目标 Binding 的原生 Schema 和标准机制尚不能清晰表达它;
- 它不要求 Agent Runtime 成为最终业务授权方;
- 旧运行时忽略它时不会静默降低安全性,否则必须进入新的 Major 兼容边界。
即使全部通过,也只代表这个字段有资格进入治理评审,还要继续回答:
- 是否在多个独立实现中出现了同一种需求;
- 默认值、优先级和失败语义是否完整;
- 是否具备机器可读测试向量;
- 是否存在更合适的 Binding、扩展或相邻协议;
- 加入核心后,生态实现成本是否值得。
这组测试背后有一个朴素原则:
字段不是越多越安全。无法被不同实现一致执行的字段,只会制造“已经治理”的错觉。
三、为什么 ACC 声明“需要审批”,却不声明“谁来审批”
ACC 可以声明一项调用需要形成审批意图:
approval:required:true也可以针对本次调用参数声明条件审批:
approval:when:-param:amountop:">"value:1000label:退款金额超过 1000 元这两种语义都具有可移植性。
任何合规 Runtime 都可以观察到一次具体调用的amount,按严格 JSON 类型比较,并确定:
本次调用是否应该先产生审批意图但下面的问题没有统一答案:
谁有资格批准? 在哪里批准? 需要几个人? 是否允许委托? 审批多久过期? 主管休假时如何升级? 法务和财务是串行还是并行? 批准记录由谁签名并长期保存?这些答案依赖组织结构、权限目录、业务类型、审批系统、合规要求和实时状态。
如果 ACC 增加一个看似方便的字段:
approval:approver:finance_manager它会立即引入一串无法由 Core 回答的问题:
finance_manager是角色名、用户组、岗位编码还是外部目录 Claim?- 谁证明当前审批人属于这个角色?
- 角色刚刚被撤销怎么办?
- 跨公司、跨租户时,这个角色属于哪个组织?
- 代理审批、会签和条件升级怎样表达?
- 一个无 UI Runtime 应该把审批送到哪里?
因此,ACC 只声明审批意图,不定义审批流程所有权。
BailingHub 怎样承接这条边界
BailingHub 会在高风险或条件命中的调用上冻结具体调用快照,形成ApprovalIntent,其中包含:
job_id request_id subject tool scope risk args args_hash审批可以被投递到业务侧 Webhook、IM/OA 卡片,或由中枢控制台在轻量场景中兜底。业务侧返回ApprovalDecision时,中枢会复核任务身份和args_hash,只放行与批准快照完全一致的那次调用。
但 BailingHub 仍不维护企业的组织关系、审批人权限和多级审批规则。生产环境中,谁能审、在哪里审、是否会签、审批证据如何归档,仍由业务审批所有者决定。
这说明:
ACC:声明需要审批 BailingHub:冻结调用、投递意图、验证决策、精确放行 业务审批系统:决定谁能批准以及流程如何完成三层缺一不可,但不能互相冒充。
四、为什么subject.required不等于标准化租户隔离
ACC 可以声明:这项能力必须存在可信行动主体。
subject:required:true它的可观察结果很明确:当 Runtime 没有可信主体时,该工具不应该暴露给 Agent,也不应该在调用层被放行。
但 ACC 不规定主体必须长什么样。
真实系统可能使用:
tenant_1:user_1001这样的结构化主体;- JWT 中经过验证的租户和用户 Claim;
- 后台 Session 对应的员工 ID;
- 企微、飞书或钉钉成员标识;
- mTLS 证书绑定的服务主体;
- 业务系统签发的短期票据;
- 服务账号加单独的委托证明。
这些机制的发行者、有效期、信任域和撤销方式完全不同,不能因为都包含一个字符串,就被视为同一种身份保证。
租户隔离为什么必须留在业务系统
假设工具入参中有:
{"tenant_id":"tenant_2","order_id":"SO-1001"}如果tenant_id来自模型输出,用户只要要求模型换一个值,就可能尝试访问其他租户。
即使tenant_id由网关注入,也仍然不能代替业务系统自己的对象级检查。因为攻击者或内部服务可能绕开 Agent Runtime,直接访问原 API。
真正的租户隔离应该满足:
无论请求来自人类后台、Agent Runtime、内部任务还是直接 API 调用 -> 业务系统都从可信身份上下文恢复租户 -> 查询和写入都限定在授权数据边界内 -> 目标对象不属于该租户时 fail closedACC 的subject.required可以阻止匿名 Agent 获得这项工具,scope可以限制某条 Agent 路由最多触达哪些能力,但它们都不是最终租户权限。
BailingHub 的实现方式
BailingHub 把可信业务主体作为不透明值传给业务工具,并将其钉入请求签名。接入方可以使用tenant_1:user_1001之类的结构化主体,也可以映射成自己的身份形式。
中枢不解释这个主体究竟对应哪张用户表、哪个商户层级或哪种行级策略。业务 API 验证调用来源后,仍要:
- 解析可信主体;
- 恢复真实租户上下文;
- 查询原业务权限;
- 核对目标订单、员工或资源归属;
- 在不满足条件时拒绝。
这不是 BailingHub “少做了一层”,而是避免把每个 SaaS 完全不同的租户模型硬编码进通用中枢。
五、为什么单次条件审批不能变成通用策略引擎
ACCapproval.when有意只处理有限、类型安全的单次调用条件。
例如:
approval:when:-param:amountop:">"value:1000Runtime 可以确定性地判断当前 JSON 参数是否命中。
但它不表达累计、滚动窗口、跨调用或序列约束。
例如,下面三次调用都没有单次超过 1000 元:
第 1 次退款:400 元 第 2 次退款:400 元 第 3 次退款:400 元如果业务规则是“同一订单当日累计退款超过 1000 元必须财务复核”,只看每次调用的amount就会漏掉累计 1200 元的事实。
要正确计算它,系统必须知道:
- 哪些历史调用已经成功;
- 哪些仍在审批或结果不确定;
- 统计窗口使用哪个时区;
- 退款撤销后是否释放额度;
- 多实例并发时如何原子更新;
- 当前业务数据是否仍然新鲜。
这些已经不是一个参数条件,而是一套有状态策略系统。
同理,下面的规则也不适合直接塞进 ACC Core:
退款金额 <= 当前订单可退余额 员工只能导出自己管理部门的数据 过去 24 小时累计发券不超过 5000 张 先完成库存预占,才能创建订单 合同金额变更后必须重新执行法务检查它们依赖权威业务数据、读取权限、时效、并发和失败处理。ACC 如果只提供一个通用expression字符串,却不定义数据来源和一致性,就会制造半成品策略语言。
一个真正的通用策略引擎需要:
- 类型系统;
- 表达式语法;
- 权威数据源;
- 数据读取授权;
- 时效和缓存规则;
- 确定性失败语义;
- 执行沙箱;
- 冲突与优先级规则;
- 可解释和可审计的决策结果。
这已经是 OPA、Cedar 或企业策略服务所在的责任层,而不是一个能力声明字段应该假装解决的问题。
六、为什么工具顺序、事务和补偿不属于能力声明
考虑一条“创建订单”的业务流程:
校验客户状态 -> 锁定库存 -> 创建订单 -> 扣减余额 -> 发送通知如果第三步成功、第四步超时,系统应该怎样处理?
- 回滚订单还是等待余额结果确认?
- 库存锁定是否可逆?
- 通知已经发送还能撤回吗?
- 重试会不会重复扣款?
- 补偿动作本身需要什么权限和审批?
- 外部支付渠道返回不确定结果时,是否应该自动重试?
这不是五个工具描述的简单相加,而是一个 Saga、事务或工作流问题。
ACC 声明的是单项业务能力面向 Agent 的治理边界。它可以告诉 Runtime:
- 某个工具是否启用;
- 属于哪个
scope; - 自动发起的最坏后果等级;
- 是否需要可信主体;
- 本次参数是否命中审批;
- 是否只读、幂等、需要超时或限流。
但“工具 A 成功后才能调用工具 B”“工具 B 失败后必须补偿工具 A”属于跨操作的状态机。
把这类顺序塞进能力声明,会让一个描述“能力是什么”的契约变成描述“业务流程怎么跑”的工作流语言。不同组织的重试、补偿、人工介入和一致性要求不可能靠几个通用字段自动统一。
BailingHub、n8n 和业务工作流怎样分工
BailingHub 可以承载持久任务、审批暂停、结果续查、工具调用幂等和 Trace,也可以由 Agent 在一个任务中选择多个工具。
n8n、LangGraph、业务流程引擎或自研服务则可以负责确定性的步骤编排。
但无论由谁编排,真正的业务事务语义仍需要业务所有者明确设计:
哪些步骤可以重试 哪些结果属于不确定 哪些动作需要补偿 补偿是否还要授权 什么时候必须转人工 最终状态由哪个系统记录产品可以提供这些能力,ACC Core 不需要因此变成另一个工作流引擎。
七、“不进入 Core”不等于“无法表达”
业务私有信息仍然可以放在正确的位置。
1. 优先复用 Binding 原生机制
业务参数继续使用 OpenAPI Schema,响应结构使用标准responses,认证方案使用协议已有机制。ACC 不复制一套请求和响应 Schema。
2. 使用带命名空间的扩展
OpenAPI Operation 可以保留业务扩展:
x-business-owner:trade-teamx-business-policy:approval_scene:order_over_limitworkflow_key:refund_v3ACC 兼容 Runtime 可以把它们放进扩展袋,交给明确支持这些字段的产品或二开模块消费。
关键边界是:
未经标准化的扩展不能静默改变
scope、风险、审批、限流、审计或签名等安全行为。
否则,同一份声明在支持和不支持该扩展的 Runtime 中可能得到相反安全结果。
3. 把权威规则放回业务 API
例如:
退款金额不得超过当前可退余额应由退款预检或执行 API 使用最新订单状态判断。即使调用绕过 Agent Runtime,这条规则也必须成立。
4. 把组织流程交给审批和工作流所有者
接口只声明“这项动作需要审批”,部署配置决定审批意图发往哪里;业务审批系统决定谁能审以及流程怎样完成。
这样既保留开放契约的可移植性,也不会阻止企业使用复杂流程。
八、ACC、BailingHub 和业务系统的完整责任图
可以把整条链路画成:
OpenAPI / MCP / 其他 Binding 负责接口事实与原生 Schema | v ACC 声明 enabled / scope / risk / subject / approval / audit / execution / guidance 负责可移植的 Agent 触达与治理意图 | v BailingHub 等 Agent Runtime 工具装配、路由白名单、可信主体闸、审批意图、参数快照、限流、幂等、审计、Trace | +------> 企业身份与委托系统 +------> OA / IM / 审批平台 +------> n8n / Saga / 业务工作流 | v 业务系统 Authority 租户隔离、对象权限、实时状态、业务不变量、最终写入与结果其中:
- ACC 负责让不同 Runtime 对最小治理语义形成共同理解;
- BailingHub 负责消费声明并执行具体控制面机制;
- 企业现有身份、审批和工作流系统继续承担各自权威责任;
- 业务系统在调用时做最终授权。
“薄标准”并不是缺少这些组件,而是拒绝对不属于自己的组件作虚假保证。
九、一条真实接入应该怎样落地
第一步:先把业务动作拆成原子能力
不要把“完成整套退款流程”暴露成一个含糊工具。优先拆成:
refund.preview refund.request.create refund.status.get refund.execute查询、预检、创建申请和真实执行具有不同后果,也应有不同风险与审批策略。
第二步:用 ACC 声明可移植治理意图
x-agent-capability:version:1enabled:truescope:refund.request.createrisk:level:mediumsubject:required:trueapproval:when:-param:amountop:">"value:1000audit:sensitive:trueexecution:readonly:falseidempotent:false这份声明让 Runtime 知道 Agent 是否可触达、是否需要主体、何时先形成审批意图,以及调用应怎样被审计和约束。
第三步:由业务侧建立可信主体
用户登录、租户归属和身份票据应来自可信业务代码,不经过模型生成。Runtime 只接受经过验证的主体上下文。
第四步:接入真实审批所有者
生产环境优先让审批意图进入现有 OA、IM 或业务审批页。审批回调需要绑定任务、工具和精确参数快照,不能只返回一个裸approved=true。
第五步:业务 API 继续执行最终授权
验签成功只证明请求来自可信中枢,不证明该主体有权操作目标对象。业务系统仍要校验租户、角色、资源归属、订单状态和业务不变量。
第六步:多步骤流程交给确定性编排
需要顺序、等待、重试、补偿或人工介入时,使用业务工作流、Saga、n8n 或其他显式状态机。不要把跨步骤事务寄托在模型临场规划上。
第七步:用负向测试验收边界
至少验证:
- 无可信主体时,受保护工具不可见且调用被拒;
- 错误
args_hash的审批决策无法放行; - 审批人权限被撤销后,审批系统拒绝决策;
- 伪造其他租户资源时,业务 API fail closed;
- 多次小额调用命中业务累计上限时仍被拦截;
- 工作流中途失败时按已定义状态进入重试、补偿或人工对账;
- 绕过 Agent Runtime 直接调用业务 API 时,租户和业务规则仍然成立。
十、怎样判断一个新字段应不应该进入 Core
以后再遇到“能不能给 ACC 加一个字段”的问题,可以先用下面的清单过滤。
可移植性
- 两个没有共享私有代码的 Runtime,能否给出相同解释?
- 字段是否依赖某个组织的角色名、数据库或 UI?
可测试性
- 能否用有限输入和预期输出形成机器可读测试向量?
- 失败时应该拒绝、降级还是产生更多审批,是否足够明确?
层级归属
- Binding 的原生字段是否已经能表达?
- 它是否实际上属于身份、授权、审批证据、策略或工作流协议?
- 是否要求 Runtime 读取业务私有数据并成为最终授权方?
安全兼容
- 旧 Runtime 忽略它时会不会更宽松?
- 如果会,是否需要新的 Major 家族或能力协商,而不是悄悄增加一个可选字段?
实现证据
- 是否已经在多个独立实现中出现同一问题?
- 是否先通过带命名空间的扩展完成过真实验证?
如果这些问题还没有答案,先作为实现扩展存在,通常比急着进入 Core 更安全。
十一、标准与产品必须允许不同速度演进
ACC 是实现中立的能力契约。
BailingHub 是消费 ACC 的一个开源实现。它可以提供:
- 审批意图账本和回调;
- 主体传递与签名;
- 持久任务和有界等待;
- 幂等、限流、审计和 Trace;
- 控制台、渠道适配与运行状态。
这些产品能力可以比标准更快演进,也可以保留部署特有的选择。
但不能反过来说:因为 BailingHub 实现了一个功能,所以 ACC Core 必须增加同名字段。真正进入标准的语义,需要证明它能够跨实现成立,并具备明确的 Conformance 行为。
同样,其他 Runtime 可以用完全不同的数据库、审批系统和身份桥实现 ACC,只要它对规范字段给出一致的可观察结果。
标准与产品分离,带来的不是割裂,而是两个重要自由:
标准可以保持中立、稳定和可移植 产品可以针对真实部署快速完善运行能力十二、写在最后:边界清楚,才是真正的完整
企业 Agent 当然需要审批人、租户隔离、业务策略和事务编排。
但“需要”不等于“都应该由一个契约定义”。
一份可靠的能力契约,应该准确声明自己能够兑现的最小语义,并明确把其他责任交还给真正拥有权威数据和组织流程的系统。
所以 ACC 的完整性不体现在字段覆盖了多少企业名词,而体现在责任链有没有断裂:
ACC 声明 Agent 触达与治理意图 -> Runtime 确定性执行暴露、主体、审批和审计闸门 -> 身份、审批与工作流系统完成自己的权威职责 -> 业务系统在最新状态下做最终授权如果把审批人、租户模型和事务编排全部塞进 Core,契约可能更厚,却会更依赖单一产品、更难一致实现,也更容易对外作出无法兑现的安全承诺。
真正成熟的标准,不是看到所有问题都增加字段。
而是知道哪些问题必须标准化,哪些问题必须留在标准之外,并让两边通过清晰接口协作。
项目、规范与真实 API 评估入口
- ACC
v1.0.5规范:https://github.com/agent-capability/agent-capability-contract/blob/v1.0.5/SPEC.md - ACC 中文设计依据与边界:https://github.com/agent-capability/agent-capability-contract/blob/v1.0.5/DESIGN_RATIONALE.zh-CN.md
- ACC 治理与演进规则:https://github.com/agent-capability/agent-capability-contract/blob/v1.0.5/GOVERNANCE.md
- ACC 官网:https://agentcapability.org/
- BailingHub
v0.3.4:https://github.com/bailinghub/bailinghub/releases/tag/v0.3.4 - BailingHub 工具模型:https://github.com/bailinghub/bailinghub/blob/v0.3.4/docs/TOOLS_MODEL.md
- BailingHub 架构说明:https://github.com/bailinghub/bailinghub/blob/v0.3.4/docs/ARCHITECTURE.md
- BailingHub 开源仓库:https://github.com/bailinghub/bailinghub
- 真实 API 接入评估:https://github.com/bailinghub/bailinghub/issues/new?template=integration_evaluation.yml
如果你正在评估现有商城、CRM、ERP、OA 或内部管理系统,可以只准备“一条脱敏业务 API + 当前身份与租户方式 + 审批由谁承接 + 一个允许的测试对象”,先验证第一条受治理动作,不需要公开生产凭据或整套后台。
提交公开 Issue 时,请勿附带 Token、模型密钥、个人信息或生产业务数据。