关于 agent-custody
agent-custody 将 Agent 工具调用记录为签名收据,并可通过 MCP 网关执行授权和 Cedar 策略检查。它适用于需要验证 Agent 操作记录的团队;SDK 也可在受支持的 Agent 框架内记录调用。
主要功能
- 01网关执行授权与策略检查
- 02为允许和拒绝的调用签发收据
- 03Merkle 透明日志
- 04使用公钥离线验证
- 05面向 Agent 框架的 SDK 拦截器
- 06针对具体操作的人类审批签名
适用场景
- 01用签名授权约束重要的 Agent 调用
- 02保存可验证的工具调用拒绝记录
- 03审核收据并测试策略变更
MCP 相关
网关作为 Agent 与上游 MCP 服务之间的代理,在转发工具调用前检查授权和策略,并为允许或拒绝的调用生成签名收据。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
agent-custody
AI Agent 的监管链:每次工具调用都有一份签名回执,仅凭公钥即可验证。每项检查能证明什么,以及不能证明什么,见下表。
两个生成方,一种回执格式,一个验证器。
- 网关是 Agent 与其能够影响的系统之间的 MCP 代理:包括 MCP 服务、作为工具描述的 REST API,以及它通过 A2A 委托的其他 Agent。对于每次工具调用,无论允许还是拒绝,它都会检查由人类主体签名的委托授权,以及对确切参数的任何必要人工审批;通过自行调用上游来收集策略所需的事实;评估一项默认拒绝的 Cedar 策略;仅在允许时转发调用;并生成签名回执,将其追加到 Merkle 透明日志中。
- SDK是 Agent 自身进程中的拦截器,挂接到框架的工具调用回调:Claude Code、Claude Agent SDK、OpenAI Agents SDK、Vercel AI SDK、OpenClaw、DeepSeek Harness、Hermes、Google ADK、LangChain,或任何你封装的函数。它能接触网关无法看到的一切,并生成相同的回执,标记为自报告。
任何持有公钥的人都可以离线验证回执。Agent 不受信任。它周围的层是可信的,而回执会准确说明这种信任的范围,从签发者是谁开始。回执文件如何载明一次调用的信息,由每项工具自行决定:字段可以作为承诺予以隐藏,也可以使用你的密钥加密;验证器会在获知这些值后重新计算摘要。
- 参考文档:每个函数、端点、MCP 工具和命令及其请求与响应
- 教程:34 个可运行示例,每个示例对应代码的一个方面,并由测试套件全部执行
- 金融操作:持久化操作 ID、提供方幂等键、原子每日预算预留,以及用于处理结果不确定情况的对账回执
- 网关控制台:供网关运营者使用的管理服务,包含配置、工具、每份回执及其判定、可在保存前重放已记录调用的策略编辑器、授权撤销和审计跟踪
- 配置网关:哪些人可以使用哪些工具,以及工具所在位置;包含一个涉及两个 Agent 的完整示例、Docker 镜像,以及用于在启动前验证配置的
agent-custody check - 使用指南:网关设置、接入 Claude Desktop、Claude Code 或自有 Agent 循环,以及将网关作为 A2A Agent,让相互委派任务的 Agent 之间进行交互;私有证据:回执文件记录的每次调用内容;以及使用状态包的审查控制台搜索回执并导出离线案例
- Codex 实时恢复演示:真实支持 Agent、错误的 CRM 记忆、由操作员审查的补偿操作,以及经验证的回执;可选择运行的实时演示,并由离线回归测试覆盖
- 人工审批:针对确切操作的短期签名、双人审批门槛、单次使用强制执行,以及离线验证
- 拦截器 SDK:Claude Code 钩子、Claude Agent SDK、适用于 OpenAI Agents SDK、Vercel AI SDK、LangChain、OpenClaw 和 DeepSeek Harness 的适配器、通过 Python 软件包提供的 Hermes Agent 插件和 Google ADK 回调,以及在其他任何工具中封装工具函数
- 编写策略:工具调用如何转化为 Cedar 请求,附带经过测试的示例;以及使用
policy simulate根据拟定策略重放历史记录 - 验证回执:每项检查的含义,以及已验证回执能证明和不能证明的内容
- 证据满足哪些要求:将回执、证据包和证书对应到 SOC 2、ISO 27001、《欧盟 AI 法案》和 UK GDPR,并说明它们都不作出哪些声明
- 将日志锚定到链上:从公开地址将日志检查点提交到 Ethereum 主网,或安装选择的其他 EVM 链;说明这能证明和不能证明什么、如何运行锚定,以及如何使用
log-check检查链上记录 - 支付授权和 Agent 身份:使授权和回执符合 Google 的 AP2 mandates、Visa 的 Trusted Agent Protocol 和 Mastercard Agent Pay 的设计;尚未实现
- 威胁模型:所有可能伪造回执的参与方、其手段、阻止方法,以及这属于证据本身还是部署特性;并说明未防御的内容
- 与 IETF SCITT 保持一致:将回执作为 SCITT 签名声明、将日志作为透明度服务、将我们的 RFC 9162 证明作为 COSE Receipts(RFC 9942);说明差异及各项选择、Agent 操作配置文件,以及迈向工作组的步骤
快速入门
npm install @agent-custody/receipts # or bun add, pnpm add
已发布到 npm,包名为 @agent-custody/receipts:编译后的 JavaScript 和类型声明,需要 Node 22 或更高版本,采用 Apache-2.0 许可证。
无需网关,即可在自己的 Agent 内记录回执。生成签名密钥,为其指定配置,然后包装 Agent 调用的函数:
npx agent-custody keygen --dir keys --name app
{ "agentId": "billing-bot", "identity": { "keyFile": "keys/app.key" }, "receiptsDir": "receipts", "logFile": "log.jsonl" }
import { createSdkIssuer, loadSdkConfig, loadPublicKey, verifyBundle } from "@agent-custody/receipts";
const issuer = createSdkIssuer(loadSdkConfig("./sdk.json"));
const refund = issuer.wrap("stripe.refund", async (args: { amount: number }) => stripe.refund(args));
await refund({ amount: 5000 }); // one signed receipt in receipts/, one leaf in log.jsonl
const bundle = JSON.parse(readFileSync("receipts/<id>.json", "utf8"));
verifyBundle(bundle, { issuerKeys: [loadPublicKey("keys/app.pub")], logFile: "log.jsonl" }).ok; // true
框架钩子和适配器包括 Claude Code、OpenAI Agents SDK、Vercel AI SDK、LangChain、OpenClaw、DeepSeek Harness、Hermes 和 Google ADK,详见 docs/sdk.md。如需强制执行而非仅记录,可在 Agent 与其工具之间部署网关:npx agent-custody gateway --config gateway.json,配置方法见 docs/usage.md;如需在 Agent 与其通过 A2A 委派的 Agent 之间部署网关,--a2a 可将网关作为 A2A Agent 提供服务。该网关已在 MCP Registry 中登记,名称为 io.github.svayatta/agent-custody;其条目见 server.json,每次 npm 发布后,可通过 Actions 选项卡运行 mcp-registry 工作流来发布(使用 GitHub OIDC,无需令牌)。
工作原理
架构指南补充介绍了客户侧与托管侧的划分、数据模型,以及入驻、撤销和验证的时序图。
flowchart LR
P["Principal<br/>(human or org, holds a signing key)"]
A["Agent host<br/>Claude Desktop, Claude Code,<br/>LangGraph, custom loop"]
S["SDK interceptor<br/>inside the agent process:<br/>hooks or wrapped tools"]
G["agent-custody gateway<br/>scope check → fact lookups → Cedar policy"]
U["Upstream MCP server<br/>Stripe, database, GitHub, ..."]
R[("receipt bundles<br/>receipts/*.json")]
L[("Merkle log<br/>local file, or a remote log<br/>run by someone else")]
V["Verifier<br/>auditor, counterparty, CI job"]
O["Observability<br/>OTel, Splunk, LangSmith, Arize"]
P -- "signed delegation grant" --> G
A -- "MCP tools/call" --> G
G -- "only on allow" --> U
U -- "result" --> G
G -- "result + receipt id, or denial + receipt id" --> A
G -- "signed receipt (issuer: gateway)" --> R
G -- "leaf hash" --> L
A -. "in-process tool calls" .-> S
S -- "signed receipt (issuer: sdk)" --> R
S -- "leaf hash" --> L
R --> V
L -. "copy of the log (optional)" .-> V
P -. "public key" .-> V
G -. "public key" .-> V
A -. "traces (unchanged)" .-> O
G -. "one span per receipt, OTLP" .-> O
S -. "one span per receipt, OTLP" .-> O
三方持有密钥。主体签署授权,说明哪个 Agent 可以使用哪些工具,以及授权何时到期。签发方(网关或 SDK)签署每条回执和每个树头。验证方只持有公钥,无需访问签发方、Agent 或上游系统。
两种生成方,一种回执
| 网关 | SDK | |
|---|---|---|
| 运行位置 | Agent 与工具之间的独立进程 | Agent 进程内部 |
| 可见内容 | MCP 工具调用 | 框架钩子所能提供的内容 |
| 强制执行 | 可以,已拒绝的调用不会到达上游 | 仅当钩子可以阻止调用时才行 |
| 字段来源 | attested 和 observed | 全部为 claimed |
| 验证方能获知什么 | Agent 无法跳过或伪造此记录 | Agent 进程报告了此记录,且此后未被更改 |
| 安装方式 | 在宿主的 MCP 配置中添加一行 | 添加钩子条目或包装函数 |
每条回执都会标明其签发方,验证方会先说明该签发方类型的可信程度。如果仪表板上满是 sdk 记录,就应该将关键调用路由至网关。
支持的宿主和框架
| 宿主或框架 | 提供方 | 强制执行 + 记录 | 仅记录 | 测试依据 |
|---|---|---|---|---|
| 任意 MCP 宿主:Claude Desktop、Claude Code、Cursor、自定义宿主 | 网关 | 是 | 真实的 MCP 客户端,以及通过 stdio 连接的上游 | |
| Codex CLI(无头模式) | 网关 | 是 | 手动运行 codex exec 和离线 MCP 恢复检查;指南 | |
| Claude Code | SDK | hook 命令,PreToolUse 拒绝 | PostToolUse | 通过 stdin 验证文档所述的钩子契约 |
| Claude Agent SDK | SDK | claudeAgentHooks | 同左 | 同一个处理器 |
| OpenAI Agents SDK (JS) | SDK | wrapTools | observeRunner | 使用脚本化模型运行真实的 Runner |
| Vercel AI SDK | SDK | wrapTools | 使用 SDK 的模拟模型运行真实的 generateText 循环 | |
| LangChain / LangGraph (JS) | SDK | tool(issuer.wrap(fn)) | ReceiptCallbackHandler | 调用真实的 StructuredTool |
| OpenClaw | SDK | registerOpenClaw / openclawHooks:before_tool_call 拒绝 | after_tool_call | 在插件进程中验证 OpenClaw 文档所述的钩子契约 |
| DeepSeek Harness | SDK | 此模块即插件:tools/pre-execute 拒绝 | tools/post-execute | 按照 harness 的调用方式驱动工具瀑布流类型 |
| Hermes Agent | sidecar + Python 包 | agent_custody.hermes:pre_tool_call 阻止 | post_tool_call | 针对运行中的 sidecar 验证文档所述的钩子契约 |
| Google ADK | sidecar + Python 包 | adk_callbacks:before_tool_callback 跳过 | after_tool_callback | 使用真实的包和 ADK 的 runner,配合脚本化模型 |
| 其他任何框架 | SDK | issuer.wrap(name, fn) | issuer.record | 普通函数 |
| Python:LangChain、OpenAI Agents SDK、CrewAI、Claude Agent SDK | sidecar + Python 包 | wrap_tools(OpenAI Agents、CrewAI)、claude_hook PreToolUse 拒绝、client.wrap | ReceiptCallbackHandler | 使用真实的 Python 包,收据由此验证器检查 |
| Go、Java、Rust,以及任何支持 HTTP 的语言 | sidecar | 决策后记录 | 记录 | 语言示例,每个示例都针对运行中的 sidecar 执行 |
| 任意语言中的任意 MCP 宿主:Claude Agent SDK Python、OpenAI Agents Python | 网关 | 是 | 网关本身是 MCP 服务;usage.md | |
| 任意 REST API,作为 Agent 通过网关访问的工具 | 网关,rest 上游 | 是 | 模拟 HTTP API;usage.md | |
通过 A2A 接入的任意 Agent(Google ADK RemoteA2aAgent,或任意 A2A 客户端) | 网关,a2a 上游和 gateway --a2a | 是 | 使用两种线格式的模拟 A2A Agent;Python 测试套件中的 ADK 自带 to_a2a 和 RemoteA2aAgent;usage.md |
框架包、pg 和 @node-saml/node-saml(用于登录门户的 SAML)都是可选的对等依赖项。每个适配器仅从其自身的包中导入内容。
一次工具调用,从头到尾
sequenceDiagram
participant Agent
participant Gateway
participant Upstream as Upstream MCP server
participant Log as Merkle log
Agent->>Gateway: tools/call stripe.refund {customer_id, amount}
Gateway->>Gateway: tool in signed grant's scopes?
Gateway->>Upstream: tools/call customer.lookup {customer_id}
Upstream-->>Gateway: {verified: true}
Note over Gateway: facts.customer = that result, labelled "observed"
Gateway->>Gateway: Cedar: permit if amount ≤ limit and facts.customer.verified
alt allow
Gateway->>Upstream: tools/call stripe.refund {customer_id, amount}
Upstream-->>Gateway: {refund_id, status}
else deny
Note over Gateway: no upstream call is made
end
Gateway->>Gateway: build in-toto statement, sign (DSSE, Ed25519)
Gateway->>Log: append canonical envelope
Log-->>Gateway: leaf index, inclusion proof, root
Gateway->>Gateway: sign tree head, write bundle to receipts/
Gateway-->>Agent: result or denial, _meta["agent-custody/receipt"] = id
被拒绝的调用也会生成收据。“Agent 曾尝试支付资金,但遭到拒绝”是值得留存的证据。
收据包的组成
flowchart TB
B["receipt bundle (one JSON file)"]
B --> E["envelope: DSSE, signed by gateway key"]
B --> T["treeHead: DSSE, signed by gateway key<br/>{treeSize, rootHash, timestamp}"]
B --> I["inclusion: {leafIndex, treeSize, hashes[]}"]
E --> S["in-toto Statement v1"]
S --> SU["subject: tool-call:<tool>:<id><br/>digest = sha256(args)"]
S --> PR["predicate"]
PR --> P0["issuer: gateway or sdk, keyid, framework"]
PR --> P1["principal, agent, delegation<br/><b>attested</b>: signed by principal key (gateway)"]
PR --> P2["tool, facts, policy decision, execution<br/><b>observed</b>: gateway obtained it (gateway)"]
PR --> P3["args, model id, session<br/><b>claimed</b>: agent-supplied, unchecked (both)<br/>every field, when issued by the sdk"]
每个字段都带有来源标签。这是最重要的设计决策,也是验证器读取的内容。
| 来源 | 含义 | 当前示例 |
|---|---|---|
attested | 由非签发方的密钥签名 | 主体 ID、Agent ID、委托授权(网关收据) |
observed | 签发方在 Agent 无法控制的情况下,以确定性方式获取 | 上游工具结果、事实查询、策略决策、执行状态(网关收据) |
claimed | 源自 Agent、模型或 Agent 自身进程,未经独立核验 | 工具参数、模型 ID、会话 ID,以及 SDK 收据中的所有字段 |
快速开始
bun install # from the repository root, once for the workspace
cd packages/receipts
node scripts/demo.ts # gateway: keys, grant, policy, four tool calls, verification, a tampering attempt; then the SDK wrapping the same tool
node examples/01-keys-and-signing.ts # first of thirty-four step-by-step examples, see docs/tutorials.md
bun run test # this package; `bun run test` at the root runs every package
所有内容都可直接在 Node 22 或更高版本上运行。无需构建步骤,也无需在命令行中使用 npx 或 tsx。
演示会将所有内容(包括两个生产方生成的收据)放在 demo-out/ 中。手动验证收据:
node src/cli.ts verify demo-out/receipts/<id>.json \
--issuer-key demo-out/keys/gateway.pub \
--principal-key demo-out/keys/principal.pub \
--log demo-out/log.jsonl
退出代码为 0 表示所有检查均已通过。报告含义请参阅 docs/verification.md。
无需网关,即可从自己的 Agent 代码签发收据:
import { loadSdkConfig } from "./src/config.ts";
import { createSdkIssuer } from "./src/sdk/index.ts";
const issuer = createSdkIssuer(loadSdkConfig("./sdk.json"));
const refund = issuer.wrap("stripe.refund", async (args: { amount: number }) => stripe.refund(args));
docs/sdk.md 包含配置文件和各框架的适配器。
收据能证明什么,不能证明什么
| 声明 | 验证者 | 对手 | 机制 | 状态 |
|---|---|---|---|---|
| 收据签发后未被篡改 | 任何人 | 后续编辑者 | 对规范化 JSON 进行 DSSE 签名 | 已完成 |
| 收据按顺序签发,且没有被删除 | 持有日志副本的审计员 | 篡改历史记录的操作员 | Merkle 包含性证明 + 重新计算根哈希 | 已完成 |
| 主体确实将此范围委托给了此 Agent | 持有主体密钥的任何人 | 操作员、Agent | 由主体密钥签名的授权;检查范围和有效时间窗口 | 已完成 |
| 策略决策依据的是这一确切策略 | 任何人 | 替换策略的操作员 | 收据中策略文本的 sha256 | 已完成 |
| 策略所依据的事实并非由 Agent 声明 | 阅读收据的任何人 | Agent | 由网关获取事实,记录各自的摘要,并标记为 observed | 已完成 |
| 副作用发生前已提交到日志 | 持有日志副本的任何人 | 操作员、日志中断 | 对于 precommit 中的工具,或 strict 下的所有工具,会先记录已签名的授权,再将其及其叶子位置嵌入收据,并检查其是否早于收据 | 已完成 |
| 上游系统确实执行了操作 | 持有上游密钥或提供商密钥的任何人 | 操作员 | 上游为收据签署其结果,或嵌入并验证提供商自己的交付签名(Stripe、GitHub) | 已完成,适用于会签名的上游 |
| 操作员本身无法伪造收据 | 监管机构、交易对手 | 操作员 | 需要采用经证明构建的 TEE 网关(#179),并依靠提供商自己的记录来发现网关之外的操作(#178) | 尚未完成 |
| 哪个模型生成了调用 | 任何人 | 操作员 | 没有托管式提供商会对模型身份签名 | 目前无法做到,标记为 claimed |
| SDK 收据是否反映了工具实际执行的操作 | 任何人 | Agent 自身进程 | 无;SDK 与 Agent 共享进程 | 有意不作此声明;签发者类型 sdk 表明了这一点 |
如果供应商告诉你,他们的收据能证明的内容超过前五行,请问他们是哪把密钥签署了收据。
目录结构
src/config.ts gateway and SDK config schemas, path resolution
src/crypto.ts canonical JSON, sha256, Ed25519 keys, DSSE sign/verify
src/log.ts Merkle log: append, root, inclusion and consistency proofs, verify, JSONL persistence
src/log-check.ts the outside monitor: verifies the head, checkpoints, witness, and anchor of a running log
src/portal.ts the tenant portal: register, first key, usage against plan, keys, Stripe billing, export, member roles, TOTP MFA, single sign-on, on the log's Postgres
src/sso.ts enterprise sign-in for the portal: OIDC (code and PKCE, id_token checked against the IdP's key set) and SAML 2.0 (signed assertions only, through @node-saml/node-saml)
src/log-export.ts a tenant's export of their own log, self-checked, as a log file the verifier reads
src/witness.ts the witness: countersigns the log's checkpoints from another operator's machine, or refuses with an alarm
src/anchor.ts the anchor: commits a Merkle root over new checkpoints to a chain, or refuses with an alarm; checks a record against JSON-RPC
src/anchor-evm.ts the anchor's transactions on Ethereum or another EVM chain, through the optional dependency viem
src/signer.ts the signer: the log's key in its own process, the key document verifiers fetch
src/checkpoints.ts signed heads published on a schedule, to files and to Postgres
src/log-store.ts the log server's backends: the file, and Postgres with tenants, hashed tokens, one writer per tenant, rate limits
src/log-sink.ts where leaves go: the local file, or a remote log over HTTP; plus the reference log server
src/policy.ts Cedar evaluation wrapper, fail-closed
src/simulate.ts replay recorded policy decisions against a proposed Cedar policy, grouped changes and run digest
src/delegation.ts signed delegation grants
src/approval.ts exact-action human approvals, distinct trusted signatures, persistent single-use consumption
src/receipt.ts receipt and authorization statement types and provenance labels
src/issue.ts sign, log, and write a receipt, or commit an authorization first; shared by both producers
src/gateway.ts the MCP proxy: a host (key, policy, upstreams, log) and a session per grant; scope check, facts, policy, forward, receipt
src/operations.ts SQLite operation claims and daily budget reservations, retained across retries and process crashes
src/gateway-http.ts the gateway over Streamable HTTP: one process, a session per connection, each under the grant it presents
src/sdk/index.ts the interceptor: policy decision, record, wrap(tool fn)
src/sdk/claude.ts Claude Code command hook and Claude Agent SDK in-process hooks
src/sdk/openai-agents.ts, vercel-ai.ts, langchain.ts framework adapters, tested against the real packages
src/sidecar.ts the SDK issuer behind a local HTTP API, for agents in other languages
src/otel.ts OpenTelemetry export: one OTLP/HTTP span per receipt, after the receipt, no SDK dependency
src/splunk.ts Splunk export: one HTTP Event Collector event per receipt, token from the environment, beside or instead of otel
src/rest.ts the REST connector: an HTTP API described as tools, standing where an MCP upstream stands
src/a2a.ts agents delegating to agents: a remote A2A agent as an upstream, and the gateway serving A2A itself in front of it
src/upstream.ts attested execution: an upstream signs its result for the receipt; the verifier checks it with the upstream key
src/capture.ts private evidence: per-tool capture rules applied by the issuer to the whole statement, withheld values as salted commitments or encrypted under the customer's key, restore and reveal
vectors/ conformance vectors: receipts, keys, logs, proofs, and expected verdicts; `bun run vectors` regenerates them
src/verify.ts offline verification, the human-readable report, and the audit that a later log extends an earlier one
src/cli.ts keygen, grant, gateway, hook, serve, log, prune, verify, reveal, audit, policy simulate
src/retention.ts pruning the log: leaves become their hashes, bundles are removed, proofs survive
src/index.ts the package's public surface; adapters are exported on ./sdk/<framework> subpaths
tsconfig.build.json emits dist/ (JavaScript plus declarations) for consumers; the repo itself runs the .ts directly
scripts/ fake Stripe upstream (signs its results with --key), a second fake upstream, fixture builders for gateway and SDK, the replay builder (`replay.ts`: example 27 and state example 07 as a folder of receipts and public keys anyone can verify with the published packages), demo
examples/ thirty-four runnable tutorials, plus examples/languages/: Python, Go, Java, and Rust clients of the sidecar, run by the test suite, one per aspect; each is run by the test suite
test/ unit tests per module, end-to-end gateway test, SDK and hook tests,
adapter tests against the real packages, and a test that runs every policy in docs/policies.md
docs/ tutorials, usage (gateway), codex-recovery (live demo), approvals, sdk, policies, verification
计划
设计由两个生产方为一个验证器提供数据。SDK 位于漏斗顶端:安装成本低、覆盖面广,并如实说明内容为自我报告。网关则是安全或合规负责人对重大操作的强制要求。两者都已存在;接下来的工作是分别扩大它们的应用范围。
已完成
- 严格模式:
"strict": true会拒绝本地日志、非仅哈希日志以及未固定密钥的日志;在观察模式下也会预先提交每个工具;并关闭发件箱。在授权记录写入运营方无法运行的日志之前,Agent 发出的调用不会被转发;每份回执都会注明issuer.strict,且verify --require-strict会要求启用严格模式(用法,教程 34)。Issue #177。 - 策略重新加载:网关会在
policy.cedar发生变化时重新读取,并从下一次调用起应用新策略,无需重启,也不会中断会话;无法解析的策略会被拒绝,当前生效的策略保持不变;每次变更或拒绝都会记录在receipts/policy-reloads.jsonl中(用法)。 - Codex 实时支持恢复演示:由模型选择 MCP 调用、CRM 记忆有误、经过审核的恢复计划、模拟信用补偿以及已验证的证据;参见指南,并提供离线回归测试覆盖。
- 与负载绑定的人工审批:对主体、Agent、工具、完整参数、金额、货币和受益方进行短时签名;可配置单密钥或双密钥门槛,即使在观察模式下也会强制执行;可配置有效期上限;转发前会持久化并以原子方式完成一次性消费;并支持独立验证审批密钥。指南,教程 30。
- 网关:MCP 代理、签名委派、由网关获取事实、Cedar 策略、拒绝回执、Merkle 日志、离线验证器。
- 回执架构包含签发方类型,因此验证器会先判断是网关还是 SDK 签发。
- SDK 核心:策略决策、记录,以及适用于任何以函数作为工具的框架的通用
wrap(tool, fn)。 - Claude Code 命令钩子,支持 PreToolUse、PostToolUse 和 PostToolUseFailure,并会在拒绝时阻止调用。
- Claude Agent SDK 进程内钩子,使用同一处理程序。
- 提供方原生交付:包装 Stripe 或 GitHub 的上游服务会将已签名的 webhook 或交付内容附加到调用中;持有共享密钥的验证器会检查 HMAC、时间戳以及与结果的绑定关系,并报告该执行已通过共享密钥证明。
- 对数级追加:Merkle 日志会缓存完整子树,因此在第百万个叶节点签发回执,与在第一个叶节点签发的成本相同;每份回执的实测耗时为 0.15 ms;包括策略、事实查询和上游签名在内,每次网关调用约耗时半毫秒。远程日志每次调用会额外增加一次网络往返和约 5 ms 的服务器处理时间;预先提交的调用则增加 2 ms;部署指南提供了基于实时日志的测量数据。
- 日志保留:
prune会将早于截止时间的叶节点替换为其哈希,并删除对应的包,因此证明仍可验证,而内容已被移除。 - 一个网关和一份授权下可接入多个上游;每个工具恰好由一个上游负责,回执会注明处理该调用的上游;已消费的事实会在这些上游之间流转。
- 经证明的执行:持有密钥的上游会为其结果签名,网关将签名嵌入回执;向验证器提供上游密钥后,验证器会将执行报告为已证明,而非仅观察到。记忆服务和演示上游都会签名。
- HTTP 上游:网关可通过 Streamable HTTP 使用环境变量中的 bearer token 连接已运行的 MCP 服务,也可通过 stdio 启动 MCP 服务。
- 可选事实查询:如果查询引用的调用参数并未包含在调用中,则会跳过该查询,而不是拒绝调用;这样,策略可以看到某次写入即将替代的事实,同时不会拒绝那些没有替代任何内容的写入。
- 已消费的事实:上游会在结果的
_meta中声明其提供的事实;会话中之后的每份网关回执都会以consumed标出这些 ID,并将其记录下来,因此 Agent 在每次调用前已看到的内容都有据可查。 - 转发的调用会在
_meta中携带回执 ID,以及已证明的 Agent 和主体,因此有状态的上游可以引用该回执;@agent-custody/state中的记忆服务便以这种方式运行。 - 一致性测试向量由测试套件生成并随规范发布;agent-custody.dev 上的浏览器验证器可通过所有测试向量。
- Sidecar:通过本地 HTTP API(
serve)提供 SDK 签发方,并配有已具备发布至 PyPI 条件的 Python 包,以及 Go、Java 和 Rust 客户端,因此任何语言的 Agent 都能通过同一套签名实现获得相同的回执。 - 树头之间的连续性证明(RFC 9162)由日志提供,并由
audit命令检查;因此,持有旧树头的审计人员可以证明此前没有内容被改写。 - 远程日志:签发方可通过 HTTP 向他人运行的日志追加记录,由该日志的密钥为树头签名,因此验证器可以确认回执位于运营方无法改写的日志中。包括参考日志服务、bearer token 身份验证以及供审计人员使用的根端点。
- 框架适配器均使用脚本化模型进行测试,针对真实软件包运行且不访问网络:OpenAI Agents SDK(
wrapTools强制执行,observeRunner根据生命周期事件记录)、Vercel AI SDK(wrapTools用于真实的generateText循环)、LangChain(ReceiptCallbackHandler负责记录,issuer.wrap负责强制执行)。 - 计划与租户门户:每个租户都属于某个计划(免费计划,每月 1 万次追加;团队计划,每月 100 万次;企业计划,不限量),追加时会强制执行,并清晰返回 429;运营方的
PORTAL_HOST上的门户允许团队注册、获取租户及首个密钥、查看相对于计划的用量、创建和撤销密钥、通过 Stripe 购买团队计划,以及复制导出命令;每项操作都会记录在审计追踪中。 - 管理操作审计追踪:每个租户的创建或停用,以及每个令牌的创建或撤销,都会记录执行者;无论操作来自管理页面还是命令行,记录都会显示在页面上,并随租户导出一同提供。
- 租户导出:
log-export使用租户自己的令牌获取每个叶子哈希、已签名的树头、已发布的密钥、检查点及其用量,验证这些数据彼此吻合,并写入一份供验证器离线读取的日志副本;这些证据永远不依赖运营方继续经营。 - 监控与计量:
log-check是一个外部探测程序,用于验证树头、检查点和见证方,遇到问题时以状态码 1 退出;它由monitor工作流每十分钟运行一次;另有GET /health,以及管理页面和 CSV 中按租户、按月显示的用量。 - 见证方:在日志运营方无法控制的另一台机器上运行第二个签名方;它先证明每个检查点都接续了自己上次签署的检查点,然后才进行联署;若历史记录被改写或分叉,则拒绝签署并发出警报;同时发布自己的密钥;
audit --witness-url要求提供见证方。参见 issue #6 的阶段 6。 - 签名方、密钥与检查点:让密钥运行在独立进程中(
signer、--signer-url);密钥文档位于/.well-known/agent-custody-log.json,由verify --log-url和audit --log-url获取并固定;每个日志的已签名检查点会发布到目录和表中,供未持续监看的验证器使用。参见 issue #6 的阶段 3。 - 基于 Postgres 的日志:使用
log --db-env;叶子哈希存储在按租户键控的一张表中;每个租户通过 advisory lock 确保只有一个写入方;租户和哈希后的令牌存储在由log-admin管理的表中;支持速率限制和正文大小上限、接收端重试,以及用于导入现有文件日志的import。参见 issue #6 的阶段 2。 - 为他人提供的日志:
hashOnly仅发送叶子哈希,因此日志永远不会持有收据;参考服务在/t/<tenant>/下运行多个租户日志,各自使用独立的令牌和 ID;树头会标明所属日志,验证器通过--log-id检查。参见托管日志阶段 1,issue #6。 - OpenTelemetry 导出:在任一配置中启用
otel后,每张收据也会作为一个 span 发送到团队已在使用的收集器;trace ID 等于收据 ID,并包含工具、Agent、主体、状态、决策和日志位置等属性;在收据生成后尽力发送,绝不进入证据路径。协调收据会被标记且不包含策略决策,因此仪表板不会将其计作第二次已允许的调用。 - 子 Agent 的委派链:一项授权会指定 Agent 的密钥,使其能够委派范围更窄的授权,并嵌入父级授权,最多三层;验证器和网关会沿链追溯至主体,拒绝任何扩大范围、放宽时间窗口、更改主体或由错误密钥签署的链接;收据会标明子 Agent 和主体,并包含委派链。由
gateway-chain-*测试向量固定,并在浏览器验证器中保持一致。 - 一个网关服务多个 Agent:
gateway --http通过 Streamable HTTP 提供网关服务;每个连接在其提交的授权范围内拥有一个会话;会话共享上游和策略,仅此而已;平台团队只需在工具前运行一个网关,而不必为每个 Agent 单独运行一个进程。 - Splunk 导出:在任一配置中启用
splunk后,每张收据也会作为一个事件发送到 HTTP Event Collector;收据 ID、工具、Agent、主体、状态、决策和日志位置会作为可搜索字段,并从环境中读取令牌;同样遵循尽力而为原则。 - REST 连接器:在网关配置中将普通 HTTP API 描述为工具,并从环境中读取凭据,使 Agent 对 API 的直接调用转变为通过网关、带有收据且经过策略检查的工具调用。
- 对重要工具进行预先提交授权:在
precommit中指定后,调用会先签名并写入日志,再转发;如果日志不接受,则不会转发;其收据包含已提交的授权及证明,表明授权先于执行。 - 每次调用都检查授权(issue #97):在所有模式下,通过 MCP、HTTP 和 A2A 接口进行的每次调用都会检查授权是否过期或已撤销,并在转发前再次检查;
agent-custody revoke会撤销一项授权及从其委派的所有授权。预先提交的调用转至上游前,会先根据固定的密钥验证远程日志的响应(issue #99)。门户只有在用户自己的账户会话中验证地址后,才允许提供方登录与该地址关联(issue #103)。验证器会重新计算执行结果摘要(issue #104)。Stripe webhook 会忽略重复或乱序事件(issue #105)。 - 在托管下进行 Agent 间委派:支持 A2A 上游和网关的 A2A 接口;每次委派都会生成一张收据,并将受托方的卡片作为事实记录。Google ADK 的
RemoteA2aAgent或任何 A2A 客户端都可以指向网关;若委派被拒绝,返回的任务会被标记为已拒绝,受托方不会看到该任务。受托方目前不会对其答复联署;收据证明网关发送和观察到的内容,而非受托方对其采取的行动。 - 私有证据:按工具设置捕获规则,由签发方对完整声明应用规则,确保私有规则下只有固定结构列表之外的内容不会以明文形式出现;被隐去的值会以加盐承诺表示,盐值绝不进入收据;内容使用客户密钥加密,并由验证器揭示和重新计算;这些承诺无法确认对被隐去值的猜测。人工审批信封包含操作字段和未加盐的参数摘要,因此私有规则会将其隐去并作为承诺;适用隐私限制。
- 托管门户的企业身份:成员角色(
owner、admin、reviewer、readonly)支持邀请;支持使用一次性代码的 TOTP MFA 和运营方重置(log-admin user mfa-reset);每个租户可通过自己的 IdP 使用单点登录,支持 OIDC 或 SAML 2.0,由运营方设置(log-admin sso set);启用强制模式后,成员将无法通过密码、GitHub 和 Google 路径登录。现已支持在会话期间撤销 Agent 的权限(agent-custody revoke,示例 27);示例 31 展示了企业登录和各类角色。 - 状态审查控制台:按交易、客户、Agent 或时间搜索收据,查看十项答复,并导出可移植的 HTML/JSON 案例;其中的签名、授权和包含证明均可离线验证。
- 普通调用后日志中断(issue #149):已签名的收据会暂存于
receipts/outbox/,日志恢复后由网关追加;Agent 会收到实际结果,并标记为log-pending;GET /health会统计待处理收据;requirePrecommit会拒绝没有precommit的远程日志。故障表列出了各种情况。 - 锚定(issue #176):
agent-custody anchor会证明每个新检查点都接续了上次锚定的检查点,然后在单笔交易中从自己的地址向自身提交一个覆盖这些检查点的 Merkle 根;默认使用 Ethereum,也可使用 Base、Arbitrum、Optimism、Polygon,或通过链 ID 指定任意 EVM 链(--chain),并按各链设置费用上限。它会发布每个日志的记录和包含证明;闲置日志不会产生交易;超过费用上限时会跳过该次运行;不会同时发起多笔交易。每笔交易都涵盖所有受监控的日志;交易只有在其区块最终确定后才算完成,并会处理链重组。log-check --anchor-url会通过一个或多个 JSON-RPC 端点检查记录,要求日志 ID 与验证器固定的 ID 相同,且地址的交易必须是最新交易;还会将日志树头与已锚定的检查点进行核对,因此仅凭链上数据就能发现被改写或回滚的树(锚定,示例 33)。 接下来,按投入产出排序
前三项旨在弥合两者之间的差距:日志无法由操作员改写,而记录无法由操作员塑造。
- 在主网上运行 log.agent-custody.dev 的锚定服务:由锚定服务独自在主机上持有一把已注资密钥,其记录与检查点一同提供,其地址和每个租户的日志 ID 发布在欢迎页和安全页面上,并由监控系统检查。代码已经完成。Issue #176。
- 从服务提供商一侧验证完整性:审计员的只读密钥列出 Stripe 的退款,然后列出 GitHub 的操作,并将每一项与收据匹配。这能发现任何网关都无法看到的情况:在网关之外执行的操作。Issue #178。
- TEE 网关:一个可复现且经过证明的机密容器;在 enclave 中生成并保管签名密钥;TLS 在容器内部终止;两个验证器都检查从收据密钥、经由证明、直到发布版本的完整路径。它将取代此前计划的托管在 TEE 中的签名器。Issue #179。
- 在不属于我们的机器上、使用不属于我们的账户运行 log.agent-custody.dev 的见证服务,并要求将其列入欢迎页。代码已经完成;现在需要的是第二位操作员。Issue #116。
- 后量子签名:在同一个 DSSE 信封中同时使用 ML-DSA 和 Ed25519;存在 PQ 密钥时默认采用混合模式,并用于每个签名工件和浏览器验证器。Issue #11。
- Agent 间调用的接收方证明:委托方为收据签署其答复,就像上游签名方现在所做的那样。
- SD-JWT 脱敏,然后是策略合规性的 ZK 证明。暂不推进。
TypeScript 适配器稳定后,将按照相同的模式推出 Python SDK。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。