当Agent调用企业系统时,仅掌握参数结构和请求地址并不足以保证安全执行。运行时必须了解操作是否改变状态、能否安全重试、等待边界和调用频率。本文将只读、幂等、超时和限流四项执行提示纳入能力声明的必要性,阐明它们如何为多平台提供统一的行为预期,并明确声明、运行时与业务系统的责任边界。
输入Schema与执行语义的维度差异
假设企业系统向Agent提供四项能力:
order.read 查询订单
refund.request.create 创建退款申请
notification.batch.send 批量发送通知
report.generate 生成经营报表这些操作的参数可由OpenAPI或输入Schema描述,运行时也知道请求目标地址。但仅知道“怎么调用”仍不足以安全执行。运行时还需明确:
- 查询订单是否真的不改变业务状态?
- 创建退款申请失败后能否用同一组参数重试?
- 报表生成等待多久应停止?
- 批量通知在一分钟内最多允许调用多少次?
这些问题看似运行时配置,实则首先描述的是operation本身的稳定执行属性。若各Agent平台仅凭HTTP方法、接口名称或私有经验推断,将导致行为分裂:
运行时A看到GET,默认无限重试
运行时B看到POST,永不重试
运行时C等待模型接口默认30秒
运行时D无限流,短时间发起数百次批量通知因此,能力声明除参数结构和治理意图外,还需一组最小、可移植的执行提示。执行提示不负责替运行时编排整个工作流,但应让不同运行时对“该操作能否安全读取、重复、等待和限速”形成统一基本理解。
readonly描述业务状态而非HTTP方法
ACC v1可通过以下声明表达只读属性:
execution:
readonly: true该声明表示operation不应改变业务状态,强调的是业务效果而非网络协议表面。GET请求通常只读,但并非天然安全:
GET /export-and-mark-as-downloaded
GET /track-email-open
GET /legacy/trigger-sync这些历史接口可能产生状态变化或外部副作用。反之,部分只读查询因参数复杂可能使用POST:
POST /reports/query
POST /search/advanced若运行时仅根据HTTP方法推断,可能将带副作用的GET误判为只读,或将纯查询的POST误判为写操作。显式readonly为不同绑定和运行时提供稳定信号。但声明并非最终保证:
- API作者必须如实标注;
- 实现本身必须真的不改变业务状态;
- 读取敏感数据仍需主体和业务授权;
- “只读”不等于“无风险”,大规模导出即使不改状态也可能具有高后果。
因此,readonly回答执行效果,不替代risk、subject和最终Authority。
idempotent解决安全重复调用问题
分布式系统中,客户端超时时可能面临三种情况:请求未到达服务端、请求到达但事务失败、请求已成功但响应未返回。若在第三种情况下直接重试,可能导致两笔退款、两次库存扣减、两条通知或两个重复工单。
ACC v1的幂等声明如下:
execution:
idempotent: true该声明表示:使用相同参数重复调用该operation,可被安全视为同一逻辑动作。它让运行时知晓重试是否可能成立,但不会凭空赋予接口幂等性。真正的幂等通常需要业务实现提供:
- 稳定的幂等键;
- 唯一约束;
- 请求指纹;
- 重复结果回放;
- 明确的幂等窗口;
- 对并发请求的原子处理。
若API无这些保证却声明idempotent: true,运行时会基于错误信号采取危险行动。幂等声明描述已存在的能力属性,而非要求运行时替业务系统发明幂等。同时,idempotent: true不等于“应无限自动重试”。是否重试还需考虑错误类型、最大次数、退避策略、剩余任务期限、服务端是否返回明确终态、当前调用是否仍绑定原始主体和参数,以及外部系统是否具有同等幂等保证。
timeout_ms是等待边界而非取消保证
长时间无结果时,Agent运行时需决定继续等待、转为异步、返回待处理或终止尝试。ACC v1支持:
execution:
timeout_ms: 10000该字段表示invocation的超时提示(毫秒)。其现实价值包括:避免运行时无限等待、帮助工作流设置有界阻塞时间、让UI区分同步操作与长任务、为模型提供明确的“暂未完成”状态,以及统一不同运行时的默认值。
但“客户端停止等待”与“服务端停止执行”并非同一概念。以下链路完全可能发生:
运行时等待10秒后超时
-> 客户端关闭连接
-> 业务服务继续处理
-> 第15秒完成退款
-> Agent因超时又发起一次调用因此,timeout_ms不能被解释为服务端已取消、业务事务已回滚、可立即安全重试、超时后一定无业务后果,或运行时必须采用特定UI/错误文本。若需真正取消语义,还需定义取消令牌、服务端确认、可取消阶段、补偿动作和最终状态查询,这已超出简单执行提示的范畴。
rate_limit控制调用节奏不替代配额系统
单次调用合法不代表高频调用安全。例如:批量通知连续调用会骚扰客户,报表高频导出会拖垮数据库,库存循环查询会放大成本,外部供应商API超限会触发封禁或额外费用。
ACC v1可声明:
execution:
rate_limit:
count: 30
window: 1m该声明表达运行时限流提示:在给定窗口内,调用次数不应超过上限。兼容运行时可在请求到达业务系统前建立基本节奏控制。但通用声明无法替企业回答所有配额问题:
- 按route、Agent、用户、主体还是租户计数?
- 多运行时如何共享计数?
- 窗口是固定、滑动还是令牌桶?
- 超限后拒绝、排队还是降级?
- 不同客户套餐是否有不同额度?
- 内部重试是否计入配额?
- 审批等待后的恢复是否重新计数?
这些问题依赖部署策略、计费体系和组织边界。通用契约提供可移植的调用频率提示,部署方负责决定计数键、算法、共享状态和失败行为。
四项提示正交且不可相互推导
一项能力可同时具有不同组合:
| 能力 | readonly | idempotent | timeout | rate limit |
|---|---|---|---|---|
| 查询单个订单 | true | true | 短 | 较宽松 |
| 生成复杂报表 | true | true | 长 | 较严格 |
| 创建退款申请 | false | 可能为true | 中 | 严格 |
| 批量发送通知 | false | 取决于业务键 | 长 | 很严格 |
| 触发一次性部署 | false | 通常为false | 长 | 严格 |
从readonly: true不能自动推出数据不敏感、风险一定是low、可无限并发、可无限重试或不需要可信主体。从idempotent: true也不能推出操作是只读、无业务副作用、任何错误都值得重试,或相同参数在任何时间都代表同一业务意图。执行提示的价值在于分别描述不同事实,而非压缩成模糊的“safe”字段。
为何不能仅依赖运行时私有配置
同一业务能力可能同时被Dify、n8n、MCP Client、自研Agent、API Gateway、本地执行器和云端工作流调用。若执行语义仅存于某一运行时,其他调用方需重新猜测和配置。随平台增加,配置将分叉:
平台A认为可重试
平台B认为不可重试
平台C等待30秒
平台D无限流将operation的稳定属性放入能力声明,可建立共同事实源。部署方仍可采用更保守的本地策略:
声明上限30/min,当前组织限制为10/min
声明超时10s,当前路由只允许等待5s
声明可幂等重试,当前高风险场景仍选择不自动重试但本地策略不应悄悄放宽声明中的安全边界。
ACC为何不定义重试、并发与事务
看到idempotent和timeout_ms后,自然疑问是:为何不将重试次数、退避算法、并发数、回滚和事务写入ACC?因为这些概念已从稳定operation属性进入具体执行策略和工作流语义。
重试策略
需区分网络错误、业务拒绝、超时、限流和未知终态,并定义退避、抖动、预算和截止时间。
并发控制
需确定计数维度、共享状态、一致性模型和租户边界。
事务与回滚
跨多个业务API的原子性不能由atomic: true字段创造,需事务协调、Saga、补偿授权、状态恢复和失败语义。
取消
需服务端协议确认,而非客户端停止等待。
这些是真实需求,但属于运行时、工作流、业务系统或独立协议。无边界地塞入能力声明,会使薄契约逐渐变成无法跨实现兑现的编排语言。
声明、运行时与业务系统的分工
一条可靠链路可这样理解:
能力声明:
告诉调用方operation是否只读、是否幂等、建议等待多久、调用频率上限
运行时:
结合本地策略决定等待、限流、是否尝试重试以及怎样报告状态
业务系统:
真正保证状态变化、幂等唯一性、最终授权和业务结果更完整的分工如下:
| 层次 | 责任 |
|---|---|
| ACC Core | 定义可移植执行提示及其稳定含义 |
| 协议Binding | 映射承载协议的原生信号、优先级和保守回退 |
| Agent运行时 | 执行超时与限流,结合错误和本地政策作保守决策 |
| 工作流系统 | 管理重试预算、异步等待、并发、编排和补偿 |
| 业务系统 | 保证真实只读效果、幂等实现、最终权限和状态一致性 |
这遵循Reach与Authority的分离。执行提示告诉运行时“怎样更安全地尝试调用”,并不授予主体操作业务对象的最终权限。
完整声明示例与常见误区
完整声明示例:
x-agent-capability:
version: 1
enabled: true
scope: order.read
risk:
level: low
subject:
required: true
audit:
sensitive: true
execution:
readonly: true
idempotent: true
timeout_ms: 5000
rate_limit:
count: 60
window: 1m该声明表达:operation显式允许进入Agent-facing候选范围;当前场景可通过稳定scope引用;最坏合理后果为low;每次调用需可信行动主体;参数或响应可能含敏感数据,需保守日志处理;操作不应改变业务状态;相同参数可安全重复;运行时建议在5秒处建立等待边界;运行时应尊重每分钟60次的调用频率提示。
它未表达:当前用户一定可读取该订单;所有low风险能力都不需治理;运行时可无限重试;5秒后服务端一定取消;任何租户拥有相同配额;审计和限流已由某产品自动完成。
八个常见误区
- GET一定只读:历史接口和不规范实现可能在GET中产生副作用,显式声明与实现审查仍必要。
- POST一定不可幂等:带稳定业务键、唯一约束和结果回放的POST可具备幂等语义。
- 幂等就应自动重试:重试仍要看错误类型、次数、截止时间和未知终态。
- 客户端超时代表业务未执行:停止等待不等于服务端取消,更不等于事务回滚。
- 限流只为保护服务器性能:限流也控制外部通知、资金动作、第三方成本和业务影响范围。
- 只读操作天然低风险:大规模导出、敏感查询和跨租户读取即使不改状态,也可能具严重后果。
- 声明幂等,运行时就能保证幂等:幂等必须由真正持有业务状态的一侧实现。
- 执行提示等于最终授权:调用方式正确,不代表当前主体有权操作当前资源。
最小执行语义检查表
在将operation暴露给Agent前,建议逐项确认:
- [ ] readonly是否按真实业务效果标注,而非仅看HTTP方法?
- [ ] 只读能力是否仍按数据敏感性和影响范围评估风险?
- [ ] idempotent: true是否有服务端唯一约束或等价机制支撑?
- [ ] 相同参数在幂等窗口内是否真的代表同一逻辑动作?
- [ ] 超时后是否可查询最终状态,而非立即盲目重试?
- [ ] 客户端停止等待与服务端取消是否被明确区分?
- [ ] 限流的本地计数维度和失败行为是否已定义?
- [ ] 部署策略是否只会收紧,而不会悄悄放宽声明边界?
- [ ] 重试、并发、补偿和事务是否留在正确的运行时或工作流层?
- [ ] 业务系统是否仍执行最终主体权限与业务状态校验?
- [ ] 日志和审计能否区分首次调用、重试、重复消费和幂等命中?
若这些事实仅藏于某SDK或开发者脑中,多Agent、多运行时接入后迟早产生不一致。执行语义是能力的一部分,但不是整个执行系统。好的契约不试图执行一切,只在系统行动前明确不能依赖猜测的事实,从而避免大量昂贵且可预防的错误。

