30 秒快速回答
Agent Control Standard(ACS,智能体控制标准)是 OWASP GenAI Security Project 在 2026 年 9 月 1 日发布的开放规范:它规定 Agent 平台必须在哪些位置暴露中间件钩子,以及安全策略如何通过这些钩子被执行。
核心价值一句话:过去你只能看 Agent「做过什么」的日志,ACS 让你在 Agent「即将做」的那一刻把它拦下来——allow、deny,或者直接改掉这次工具调用的参数。
规范用 JSON-RPC 2.0 描述「被治理的 Agent」与「守护 Agent」之间的契约,代码 Apache-2.0、文档 CC BY-SA 4.0;当前是 v0.1 公开预览版,仓库在 2026 年 10 月 16 日更新到 0.1.4,v0.2.0 计划 2027 年 3 月发布。
一、为什么「事后看日志」救不了 Agent
传统可观测性解决的是归因:Agent 转了 7.5 万美元,日志能告诉你哪一步出的错。但钱已经转出去了。
Agent 的风险已经从「说错话」变成「做错事」:删文件、改权限、发邮件、调支付接口。这类动作一旦过了工具边界就不可撤销,所以控制点必须放在工具真正执行之前。ACS 把这一层叫 Instrument,它也是三个支柱里唯一能真正「阻断」的一层。
| 层次 | 作用时机 | 能否阻止副作用 | 典型产物 |
|---|---|---|---|
| 提示词护栏 | 每次推理前 | 弱,靠模型自觉 | System Prompt |
| ACS Instrument | 动作执行前 | 能,裁决不通过则不调用 | 钩子 + Guardian |
| OpenTelemetry Trace | 动作发生中/后 | 不能 | Span、OCSF 事件 |
| 事后日志审计 | 动作结束后 | 不能 | 审计报告 |
一句话结论:System prompt 不是控制手段,模型变强也不覆盖边界情况,真正的控制是「代码里有个地方在等一个裁决」。
二、三根支柱:Instrument / Trace / Inspect
ACS 刻意不去发明新标准,而是扩展已有生态,这是它能进企业的主要理由。
- Instrument(控制):19 个生命周期钩子,覆盖
sessionStart、agentTrigger、userMessage、toolCallRequest、toolCallResult、memoryStore、knowledgeRetrieval、subagentStart、skillLoad等节点。MCP 流量挂在protocols/MCP/*下,Agent 间委派走 A2A。 - Trace(追踪):每个钩子与裁决序列化成 OpenTelemetry Span,安全事件映射成 OCSF 事件,直接进你现有的 SIEM,不用再搭第二套管道。
- Inspect(清点):AgBOM(Agent 物料清单)基于 CycloneDX、SPDX、SWID 扩展,动态记录这个 Agent 装了哪些模型、工具、知识源,以及它们什么时候被换掉。
三支柱之外还有一致性问题:多数企业同时跑 Copilot、Claude Code、Bedrock 自建 Agent 和几个没人登记的内部脚本,每家护栏各写一套。ACS 的价值就是把策略写一次,在所有实现钩子的平台上通用。
三、一次工具调用是怎么被拦下的
最小闭环只有两个角色:Observed Agent 在关键节点暂停、发请求;Guardian Agent 收请求、按策略给裁决。裁决有三种:allow、deny、modify——modify 是它区别于日志标准的关键,Guardian 可以在工具跑之前改写参数。
{
"jsonrpc": "2.0",
"method": "steps/toolCallRequest",
"id": "c-7f3a",
"params": {
"session_id": "sess-20261111-01",
"tool": "payments.transfer",
"arguments": { "payee": "ACME-8842", "amount": 75000, "currency": "USD" },
"provenance": { "source": "email:invoice-9931", "trusted": false }
}
}
Guardian 侧的确定性策略(规则先跑,LLM 只处理规则交出去的模糊案例):
POLICY = [
# 付款金额超阈值 → 必须人工审批,先 deny 再走 ask 流程
{"when": lambda c: c.tool == "payments.transfer" and c.args["amount"] > 10000,
"verdict": "deny", "reason": "exceeds_auto_approval_limit"},
# 供应商账户变更且来源不可信 → 阻断并要求二次核验
{"when": lambda c: c.tool == "payments.transfer" and not c.provenance.trusted,
"verdict": "deny", "reason": "untrusted_payee_change"},
# 只读查询做参数收敛,防止全表拉取
{"when": lambda c: c.tool == "db.query" and "limit" not in c.args,
"verdict": "modify", "patch": {"limit": 200}},
]
def guardians_decide(call):
for rule in POLICY:
if rule["when"](call):
return {"disposition": rule["verdict"],
"reason": rule["reason"],
"patch": rule.get("patch")}
return {"disposition": "allow"}
被治理的一端只需要「等」并「听话」:
def call_tool(name, args, session):
resp = acs.send("steps/toolCallRequest",
{"session_id": session.id, "tool": name, "arguments": args})
d = resp["result"]
if d["disposition"] == "deny":
raise ToolDenied(d["reason"]) # 目标服务此刻什么都没收到
if d["disposition"] == "modify":
args.update(d["patch"])
return registry[name](**args)
关键不在钩子叫什么,而在时序:toolCallRequest 在工具执行前触发,能被兜住;toolCallResult 在工具跑完、结果回灌模型前触发,它能挡住被污染的输出进入下一轮,但钱已经转走了。
| 钩子 | 触发时机 | 能阻止外部副作用 | 要验证什么 |
|---|---|---|---|
steps/toolCallRequest |
工具执行前 | 能(前提是运行时等待并执行裁决) | 直连 SDK / 原始 HTTP 是否绕过 |
steps/toolCallResult |
执行后、回灌前 | 不能,副作用已发生 | 污染结果是否进记忆或下一轮 |
| Trace 导出 | 裁决后 | 不能 | 关联是否完整、参数是否脱敏 |
四、上线前必须确认的四个细节
- 默认是 fail-open。Guardian 超时或不可达时,Agent 默认继续执行,除非把
on_decision_failure显式设为deny。不设这一项,你的「控制」在故障时刻等于零。 - 握手会暴露执行缺口。
methods_evaluated里没有列出的方法默认放行——一个看起来成功的手握,可能对应大片没有策略覆盖的钩子。两侧的方法清单都要逐个核对。 - 绕过路径比钩子更重要。子 Agent、定时任务、裸 HTTP 客户端都可能不走框架适配层。要拿到调用图,并指出每一路在哪一行被拦住;架构图上画个「Guardian」方块几乎证明不了任何事。
- 合规是自我声明。v0.1 没有测试套件、注册表或监管机构,「ACS-Core conformant」目前只是厂商说法,必须自己压测。强制项是 ACS-Core(握手、信封、最小钩子集、哈希链会话上下文、重放保护、HMAC-SHA256 基线签名、裁决执行),Trace / Inspect / Provenance / Crypto / Audit 是可选档案。
总结
ACS 不是产品,是一份线协议契约:策略引擎、加密套件、传输方式都由你自己选,标准只管「在哪里停、该问谁、怎么听」。它适合三类人——做 Agent 平台的(实现钩子)、管安全的(写策略、消费审计流)、做采购的(用 conformance 档案当验收词汇)。
最后提醒一句:ACS 解决的是「能不能被治理」,不解决「策略写得对不对」。 把确定性的授权规则留在规则层,别让概率模型去判断一笔付款是否超限。
延伸阅读建议:先读 OWASP 的 Agentic Top 10(它讲会出什么事,ACS 讲怎么拦),再对照 MCP 与 A2A 的授权章节,最后看 OpenTelemetry 的 GenAI 语义约定,把 Trace 接到现有可观测性栈上。