agent-custody

io.github.svayattaPublished by GitHub account svayattav0.16.6Updated Nov 11, 2026

MCP gateway that checks each agent tool call against a signed grant and policy, and signs a receipt

Overview

AI-generated overview

An MCP gateway that checks each agent tool call against a signed grant and policy, forwards only allowed calls, and signs a verifiable receipt for every…

What you can do
  • Gate and proxy tool calls: check scope against a principal-signed delegation grant, gather facts from upstream, evaluate a fail-closed Cedar or Rego policy, and forward only allowed calls.
  • Issue and verify receipts: sign a receipt for every allowed or denied call, append it to a Merkle transparency log, and verify bundles offline with public keys.
  • Record from inside the agent: wrap tool functions or use framework hooks to issue self-reported receipts without a gateway.
  • Cover traffic around the gateway: a receipted reverse proxy for your own site or API, and a forward proxy that receipts connections under egress lockdown.
  • Audit past sessions: read Claude Code transcripts against a starter…
When to use it
Use it when agents hold real power over consequential systems and you need provable, tamper-evident records of what they attempted and what was allowed. It suits security or compliance owners who want enforcement plus an audit trail, and teams that first want to observe what a policy would have blocked.
Requirements
Runs locally as a Node.js 22 or later process, installed from npm and started with npx. You generate signing keys and supply a config file naming the agent, key file, receipts directory and log file, plus a Cedar or Rego policy and upstream servers. Verification needs only public keys.
Before you install
It handles signing keys and delegation grants, so protect the key files and config. The gateway forwards allowed calls to upstream systems, which can change or delete data, and denied calls are still recorded. Receipts are only as strong as the issuer: SDK receipts are self-reported, the ACS Guardian only decides, and the egress witness records destinations and byte counts, not payloads. Receipt fields can be withheld or encrypted per tool.

Installation

In SourceWeft

  1. Open agent-custody in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

agent-custody

Proof of what your AI agents did. Give your agents real power, without losing control of them.

Chain of custody for AI agents: a signed receipt for every tool call, checkable with public keys alone. What each check proves, and against whom, is a table below.

Five producers, one receipt format, one verifier.

  • The gateway is an MCP proxy between an agent and the systems it can affect: MCP servers, REST APIs described as tools, and other agents it delegates to over A2A. For every tool call, allowed or denied, it checks a delegation grant signed by the human principal and any required human approval of the exact arguments, gathers the facts the policy needs by calling upstream itself, evaluates a Cedar or Rego policy that fails closed (Rego from a file or from your own OPA server), forwards the call only on allow, and emits a signed receipt appended to a Merkle transparency log.
  • The door is the gateway turned around: a reverse proxy in front of your own website, API, or forms. Every arrival, a browser, an API client, a crawler, or an agent, is classified by what it presented (a grant signed by a principal you trust makes it verified; an assertion the door cannot check is claimed; nothing is unknown), decided by a Cedar policy for its route, relayed or refused, and receipted either way with the same receipt. It is the first piece of an agent front door; it does not yet detect agents in web traffic, and says so (the door).
  • The egress witness covers the calls that go around the gateway. Under egress lockdown, a locked-down agent's network reaches only the gateway and the witness. The witness is a forward proxy: it identifies the agent by the proxy credential it checked, lets a Cedar or Rego policy decide per destination, relays or refuses the connection, and receipts it either way (issuer kind egress). It records the destination, the times, and the byte counts. It never records a payload, because there is no TLS interception. It sees nothing when lockdown is off, and it relays no protocol other than HTTP. A receipt is an observed bypass, not a record of what was done.
  • The ACS Guardian, a spike, answers an agent that asks before it acts over the OWASP Agent Control Standard: it decides steps/toolCallRequest and steps/toolCallResult by a Cedar policy and receipts each decision (issuer kind guardian). It carries out nothing, and the agent chooses to ask and to obey, so a receipt proves the decision, not that the call ran or did not (the ACS Guardian).
  • The SDK is an interceptor inside the agent's own process, hooked into the framework's tool-call callbacks: Claude Code, the Claude Agent SDK, the OpenAI Agents SDK, the Vercel AI SDK, OpenClaw, DeepSeek Harness, Hermes, Google ADK, LangChain, or any function you wrap. It reaches everything the gateway cannot see and issues the same receipts, labelled as self-reported.

Anyone holding the public keys can verify a receipt offline. The agent is not trusted. The layer around it is, and the receipt says exactly how far that trust extends, starting with who issued it. What the receipt file carries of a call is a choice per tool: a field can be withheld as a commitment or encrypted under your key, and the verifier recomputes the digests when it is shown the values.

  • Reference: every function, endpoint, MCP tool, and command with its request and response
  • Tutorials: runnable examples, one per aspect of the code, all executed by the test suite
  • Put the door in front of a site: a fifteen-minute quick start for a site owner, from keys to verified receipts.
  • The door: a receipted reverse proxy in front of your own site or API; what it knows about an arrival, how an arrival becomes a Cedar request, what the arrival gets back, and what it does not do yet
  • The egress witness: a receipted forward proxy for calls that go around the gateway; lockdown, how the witness knows the agent, the Cedar request (or Rego input) per destination, the receipt, and what it cannot see: payloads, traffic when lockdown is off, and non-HTTP protocols
  • Writing egress policies: per-agent allow lists, wildcard hosts and the *.example.com trap, ports, IP literals, why a provider that needs a credential stays in the gateway, forbid, and rolling out from observe mode with egress draft-policy; every example is tested
  • The ACS Guardian: agent-custody as an OWASP Agent Control Standard Guardian for steps/toolCallRequest and steps/toolCallResult; each decision a receipt a third party can check, the ACS sections it relies on, and what it does not implement
  • Door signals: every field of the arrival fact, labelled checked, observed, or a claim, with what it does and does not mean and how a policy should read it
  • Financial operations: durable operation ids, provider idempotency keys, atomic daily budget reservations, and reconciliation receipts for uncertain outcomes
  • The gateway console: an admin service for the person who runs a gateway: config, tools, receipts with a verdict each, a policy editor that replays recorded calls before it saves, grant revocation, and an audit trail
  • Configuring the gateway: who may use which tools and where the tools live, a worked example with two agents, the Docker image, and agent-custody check to validate a config before it starts
  • Usage guide: gateway setup, wiring into Claude Desktop, Claude Code, or your own agent loop, and the gateway as an A2A agent between agents that delegate to each other, private evidence: what the receipt file carries of each call, and searching receipts and exporting offline cases with the state package's review console
  • Live Codex recovery demo: a real support agent, incorrect CRM memory, operator-reviewed compensation, and verified receipts; an opt-in live run with offline regression coverage
  • Human approvals: short-lived signatures over the exact action, two-person thresholds, single-use enforcement, and offline verification
  • The interceptor SDK: Claude Code hooks, the Claude Agent SDK, adapters for the OpenAI Agents SDK, Vercel AI SDK, LangChain, OpenClaw and DeepSeek Harness, a Hermes Agent plugin and Google ADK callbacks through the Python package, and wrapping tool functions in anything else
  • Writing policies: how a tool call becomes a Cedar request or Rego input, with tested examples, a Rego file against an OPA server, and replaying history against a proposed policy with policy simulate
  • Verifying a receipt: what each check means and what a verified receipt does and does not prove
  • What the evidence satisfies: the receipts, packs, and certificates mapped to SOC 2, ISO 27001, UK GDPR, the EU AI Act, FINRA, MAS, Colorado, DORA, the CSA AI Controls Matrix, the OWASP agentic guidance, and ISO/IEC 42001. Each regulation has a primary source, an "as of" date, and a list of what a receipt does not supply
  • Anchoring the log on a chain: the log's checkpoints committed to Ethereum mainnet, or another EVM chain the installation picks, from a published address, what that proves and what it does not, running the anchor, and log-check against the chain
  • Receipts on a pull request: the GitHub Action that verifies a directory of receipt bundles and posts one table on the pull request, the workflow file to copy, the export command that copies the receipts into the branch, and why a committed receipt publishes the call unless a capture rule withholds it
  • Completeness: an auditor's read-only key lists the provider's objects and matches each to a verified receipt, so an action taken outside the gateway shows up
  • Payment mandates and agent identity: the design for fitting grants and receipts to Google's AP2 mandates, Visa's Trusted Agent Protocol and Mastercard Agent Pay; not yet built
  • Threat model: every party who could make a receipt false, the move, what stops it, and whether that is a property of the evidence or of the deployment; and what is not defended
  • What a receipt costs: each step of the issuer's hot path in microseconds, measured with bun run bench, what is and is not fsynced, and where the time goes
  • Aligning with IETF SCITT: the receipt as a SCITT signed statement, the log as a transparency service, our RFC 9162 proofs as COSE Receipts (RFC 9942), what differs and the options for each, an agent-action profile, and the steps toward the working group
  • Reading a Perathos log: verify-perathos checks another project's agent records, a Perathos session log, against its spec; what a valid log proves and does not, and how its fields map onto an agent-custody receipt

Getting started

bash
npm install @agent-custody/receipts        # or bun add, pnpm add

Published on npm as @agent-custody/receipts: compiled JavaScript with type declarations, Node 22 or later, Apache-2.0.

Record receipts from inside your own agent, no gateway needed. Generate a signing key, point a config at it, wrap the functions the agent calls:

bash
npx @agent-custody/receipts keygen --dir keys --name app
json
{ "agentId": "billing-bot", "identity": { "keyFile": "keys/app.key" }, "receiptsDir": "receipts", "logFile": "log.jsonl" }
ts
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

To see first what it would have caught, npx -y @agent-custody/receipts audit-sessions --since 7d reads your past Claude Code transcripts against the starter policy and writes nothing (docs/sdk.md). For your own Claude Code sessions, one command does the whole setup (key, config in observe mode, starter deny policy, hooks), and agent-custody report lists the calls the policy would have denied: npm install -g @agent-custody/receipts, then agent-custody init claude-code, then see docs/sdk.md.

Framework hooks and adapters, including Claude Code, the OpenAI Agents SDK, the Vercel AI SDK, LangChain, OpenClaw, DeepSeek Harness, Hermes and Google ADK, are in docs/sdk.md. To enforce rather than record, put the gateway between the agent and its tools: npx @agent-custody/receipts gateway --config gateway.json, set up in docs/usage.md; between an agent and the agents it delegates to over A2A, --a2a serves the gateway as an A2A agent itself. The gateway is listed in the MCP Registry as io.github.svayatta/agent-custody; its entry is server.json, published after each npm release by running the mcp-registry workflow from the Actions tab (GitHub OIDC, no token).

How it fits together

The architecture guide adds the customer-side and hosted split, the data model, and sequence diagrams for onboarding, revocation, and verification.

mermaid
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

Three parties hold keys. The principal signs a grant saying which agent may use which tools until when. The issuer, gateway or SDK, signs every receipt and every tree head. The verifier holds only public keys and needs no access to the issuer, the agent, or the upstream system.

Five producers, one receipt

gatewaydooregress witnessACS GuardianSDK
where it runsseparate process between agent and toolsseparate process between the world and your own site or APIforward proxy in a locked-down agent's network pathseparate process the agent asks, one JSON-RPC document at a timeinside the agent's process
what it seesMCP tool callsevery HTTP arrival, by routeeach connection that does not go to the gateway: destination, times, byte counts, never the payloadthe tool-call requests and results the agent chooses to sendwhatever the framework's hooks expose
enforcementyes, denied calls never reach upstreamyes, refused arrivals never reach the originyes, refused connections are never opened; only under lockdown, since an agent can ignore the proxyno: it decides, and the agent honours the answer or notonly where a hook can block
provenance of its fieldsattested and observedobserved; attested only when the arrival presented a grant, else claimedobserved; the agent is observed only through a proxy credential the witness checked, else claimedthe decision and the policy observed; the agent, the tool, and the arguments claimedclaimed, all of them
what a verifier learnsthe agent could not skip or forge thisthe arrival could not skip or forge this; who it was is proven only with a grantthe agent connected to this destination around the gateway; not what it sentthe Guardian made this decision on this request under this policy; not that the call ranthe agent's process reported this and it has not changed since
installone line in the host's MCP configa reverse proxy in front of the origina forward proxy plus egress lockdown (deploy/gateway/lockdown)agent-custody acs --config acs.json, or createAcsGuardian in codea hook entry or a wrapped function

Every receipt names its issuer, and the verifier prints what that issuer kind is worth before anything else. A dashboard full of sdk rows is the reason to route the consequential calls through the gateway. The door is the gateway turned around (the door). The egress witness receipts the connections that go around the gateway (the egress witness). The ACS Guardian receipts the decisions it gives an agent that asks (the ACS Guardian).

Supported hosts and frameworks

host or frameworkproducerenforce + recordrecord onlytested against
any MCP host: Claude Desktop, Claude Code, Cursor, customgatewayyesa real MCP client and upstream over stdio
Codex CLI (headless)gatewayyesa manual codex exec run and offline MCP recovery checks; guide
Claude CodeSDKhook command, PreToolUse deny; init claude-code sets it upPostToolUsethe documented hook contract, via stdin; init and report against a temporary home
Claude Agent SDKSDKclaudeAgentHookssamethe same handler
OpenAI Agents SDK (JS)SDKwrapToolsobserveRunnera real Runner with a scripted model
Vercel AI SDKSDKwrapToolsa real generateText loop over the SDK's mock model
LangChain / LangGraph (JS)SDKtool(issuer.wrap(fn))ReceiptCallbackHandlerreal StructuredTool invocations
OpenClawSDKregisterOpenClaw / openclawHooks: before_tool_call denyafter_tool_callOpenClaw's documented hook contract, in the plugin's process
DeepSeek HarnessSDKthe module is the plugin: tools/pre-execute denytools/post-executethe harness's tool waterfall types, driven as the harness drives them
Hermes Agentsidecar + Python packageagent_custody.hermes: pre_tool_call blockpost_tool_callthe documented hook contract, against a live sidecar
Google ADKsidecar + Python packageadk_callbacks: before_tool_callback skipsafter_tool_callbackthe real package, ADK's runner with a scripted model
anything elseSDKissuer.wrap(name, fn)issuer.recordplain functions
Python: LangChain, OpenAI Agents SDK, CrewAI, Claude Agent SDKsidecar + Python packagewrap_tools (OpenAI Agents, CrewAI), claude_hook PreToolUse deny, client.wrapReceiptCallbackHandlerthe real Python packages, receipts checked by this verifier
Go, Java, Rust, any language with HTTPsidecardecide then recordrecordexamples/languages, each run against a live sidecar
any MCP host in any language: Claude Agent SDK Python, OpenAI Agents Pythongatewayyesthe gateway is an MCP server; usage.md
any REST API, as tools the agent reaches through the gatewaygateway, rest upstreamyesa stand-in HTTP API; usage.md
any agent reached over A2A (Google ADK RemoteA2aAgent, or any A2A client)gateway, a2a upstream and gateway --a2ayesstand-in A2A agents in both wire formats; ADK's own to_a2a and RemoteA2aAgent in the Python suite; usage.md

The framework packages, pg, and @node-saml/node-saml (for SAML sign-in to the portal) are optional peer dependencies. Each adapter imports only from its own package.

One tool call, end to end

mermaid
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

Denied calls get receipts too. "The agent tried to pay out funds and was refused" is evidence worth keeping.

Anatomy of a receipt bundle

mermaid
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:&lt;tool&gt;:&lt;id&gt;<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"]

Every field carries a provenance label. This is the design decision that matters most, and it is what a verifier reads back.

provenancemeaningtoday's examples
attestedsigned by a key other than the issuer'sprincipal id, agent id, the delegation grant (gateway receipts)
observedthe issuer obtained it deterministically, outside the agent's controlupstream tool results, fact lookups, the policy decision, execution status (gateway receipts)
claimedoriginated from the agent, the model, or the agent's own process, no independent checktool arguments, the model id, session ids, and every field of an SDK receipt

Quick start

bash
bun install                              # from the repository root, once for the workspacecd packages/receiptsnode scripts/demo.ts                     # gateway: keys, grant, policy, four tool calls, verification, a tampering attempt; then the SDK wrapping the same toolnode examples/01-keys-and-signing.ts     # first of the step-by-step examples, see docs/tutorials.mdbun run test                             # this package; `bun run test` at the root runs every package

Everything runs on plain Node 22 or later. No build step, no npx, no tsx needed on the command line.

The demo leaves everything in demo-out/, including receipts from both producers. Verify a receipt by hand:

bash
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

Exit code 0 means every check passed. See docs/verification.md for what the report means.

To issue receipts from your own agent code, without a gateway:

ts
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 has the config file and the per-framework adapters.

What a receipt proves, and what it does not

claimverifieradversarymechanismstatus
receipt was not altered after issueanyonelater editorDSSE signature over canonical JSONdone
receipt was issued in sequence, none removedauditor with a log copyoperator rewriting historyMerkle inclusion proof + recomputed rootdone
the principal really delegated this scope to this agentanyone with the principal's keyoperator, agentgrant signed by principal key, checked for scope and validity windowdone
the policy decision was made against this exact policyanyoneoperator swapping policiessha256 of policy text in the receiptdone
facts the policy relied on were not asserted by the agentanyone reading the receiptagentfacts fetched by the gateway, recorded with their own digests, labelled observeddone
the side effect was committed to the log before it happenedanyone with a log copyoperator, log outagefor tools in precommit, or every tool under strict, a signed authorization logged first, embedded in the receipt with its leaf position, checked to precede the receiptdone
the upstream system actually executed the actionanyone with the upstream's key or the provider's secretoperatorthe upstream signs its result for the receipt, or the provider's own delivery signature (Stripe, GitHub) is embedded and checkeddone, for upstreams that sign
the operator itself cannot mint a false receiptregulator, counterpartyoperatorneeds a TEE gateway with an attested build (#179), and the provider's own records to catch actions outside the gateway (#178)not done
which model produced the callanyoneoperatorno hosted provider signs model identitynot possible today, labelled claimed
an SDK receipt reflects what the tool really didanyoneagent's own processnone; the SDK shares a process with the agentby design not claimed; issuer kind sdk says so

If a vendor tells you their receipts prove more than the first five rows, ask them which key signed it.

Layout

src/config.ts      gateway and SDK config schemas, path resolutionsrc/crypto.ts      canonical JSON, sha256, Ed25519 keys, DSSE sign/verifysrc/log.ts         Merkle log: append, root, inclusion and consistency proofs, verify, JSONL persistence; a lock file makes an append safe when several processes share one log file, and a partial last line from a killed writer is moved to `<file>.torn`src/lock.ts        the one lock file design (owner token, liveness check, rename to break a stale lock) that the local log, prune, and the gateway outbox sharesrc/log-check.ts   the outside monitor: verifies the head, checkpoints, witness, and anchor of a running logsrc/design.ts      the design tokens and shared components (button, select, card, dialog, headings) that the site, the portal, and the consoles share; the app pages inline DESIGN_CSSsrc/portal.ts      the tenant portal: register, first key, usage against plan, activity, keys with last use, Stripe billing, export, member roles, password reset and change, TOTP MFA with a QR code and recovery codes, single sign-on, on the log's Postgressrc/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 readssrc/witness.ts     the witness: countersigns the log's checkpoints from another operator's machine, or refuses with an alarmsrc/anchor.ts      the anchor: commits a Merkle root over new checkpoints to a chain, or refuses with an alarm; checks a record against JSON-RPCsrc/anchor-evm.ts  the anchor's transactions on Ethereum or another EVM chain, through the optional dependency viemsrc/completeness.ts  completeness from the provider's side: the provider's own list, matched against verified receiptssrc/pr-summary.ts  `verify --markdown`: a directory of bundles verified into one Markdown table, for the GitHub Actionsrc/export.ts      `export`: verified receipt bundles that keep their values private, copied byte for byte into a directory for a pull requestsrc/signer.ts      the signer: the log's key in its own process, the key document verifiers fetchsrc/checkpoints.ts signed heads published on a schedule, to files and to Postgressrc/log-store.ts   the log server's backends: the file, and Postgres with tenants, hashed tokens, one writer per tenant, rate limitssrc/log-sink.ts    where leaves go: the local file, or a remote log over HTTP; plus the reference log serversrc/policy.ts      Cedar evaluation wrapper, fail-closedsrc/policy-rego.ts Rego: compiled with opa build, evaluated in-process with the OPA WebAssembly SDK, fail-closedsrc/opa-server.ts  an OPA server's Data API as the policy, with bundle revisions as the digestsrc/simulate.ts    replay recorded policy decisions against a proposed Cedar or Rego policy, grouped changes and run digestsrc/delegation.ts  signed delegation grantssrc/approval.ts    exact-action human approvals, distinct trusted signatures, persistent single-use consumptionsrc/receipt.ts     receipt and authorization statement types and provenance labelssrc/issue.ts       sign, log, and write a receipt, or commit an authorization first; shared by every producersrc/gateway.ts     the MCP proxy: a host (key, policy, upstreams, log) and a session per grant; scope check, facts, policy, forward, receiptsrc/operations.ts  SQLite operation claims and daily budget reservations, retained across retries and process crashessrc/gateway-http.ts the gateway over Streamable HTTP: one process, a session per connection, each under the grant it presentssrc/door.ts        the door: a reverse proxy in front of your own HTTP surface; every arrival classified, decided per route, relayed or refused, receipted as issuer kind doorsrc/egress.ts      the egress witness: a forward proxy (CONNECT and plain HTTP, no TLS interception) for calls around the gateway; agent by proxy credential, decided per destination, receipted as issuer kind egresssrc/egress-draft.ts `egress draft-policy`: a first egress policy from verified observe-mode receipts, one permit per agent and scheme over the destinations it used, for reviewsrc/acs.ts         an OWASP ACS Guardian: steps/toolCallRequest and steps/toolCallResult over JSON-RPC, decided by Cedar, each decision receipted as issuer kind guardiansrc/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 hookssrc/sdk/openai-agents.ts, vercel-ai.ts, langchain.ts   framework adapters, tested against the real packagessrc/sidecar.ts     the SDK issuer behind a local HTTP API, for agents in other languagessrc/otel.ts        OpenTelemetry export: one OTLP/HTTP span per receipt, after the receipt, no SDK dependencysrc/splunk.ts      Splunk export: one HTTP Event Collector event per receipt, token from the environment, beside or instead of otelsrc/rest.ts        the REST connector: an HTTP API described as tools, standing where an MCP upstream standssrc/a2a.ts         agents delegating to agents: a remote A2A agent as an upstream, and the gateway serving A2A itself in front of itsrc/upstream.ts    attested execution: an upstream signs its result for the receipt; the verifier checks it with the upstream keysrc/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 revealvectors/           conformance vectors: receipts, keys, logs, proofs, and expected verdicts; `bun run vectors` regenerates them; `interop/` holds other projects' records, copied as they wrote them and never regeneratedsrc/verify.ts      offline verification, the human-readable report, and the audit that a later log extends an earlier onesrc/claude-init.ts `init claude-code`: the agent key, the observe-mode config, the starter policy, and the three hooks merged into the settings file with absolute pathssrc/deny-report.ts `report`: verifies the receipts of an SDK config and lists the calls the policy would have deniedtemplates/         the starter Cedar policy that `init claude-code` copies (shipped in the package)src/cli.ts         keygen, grant, gateway, door, egress, egress draft-policy, acs, init, report, hook, serve, log, prune, verify, reveal, audit, policy simulatesrc/retention.ts   pruning the log: leaves become their hashes, bundles are removed, proofs survivesrc/index.ts       the package's public surface; adapters are exported on ./sdk/<framework> subpathstsconfig.build.json  emits dist/ (JavaScript plus declarations) for consumers; the repo itself runs the .ts directlyscripts/           fake Stripe upstream (signs its results with --key), a second fake upstream, fixture builders for gateway, SDK, door (with a stand-in origin), and the egress witness (with a stand-in destination), 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, the site's numbers (`bench.ts`), and the per-step hot-path benchmark (`hot-path-bench.ts`, `bun run bench`)examples/          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 suitetest/              unit tests per module, end-to-end gateway test, SDK and hook tests,                   adapter tests against the real packages, and tests that run every policy in docs/policies.md and site/guide/egress-policies.mddocs/              tutorials, usage (gateway), codex-recovery (live demo), approvals, sdk, policies, verification, performance

Plan

The design is several producers feeding one verifier. The SDK is the top of the funnel: cheap to install, wide reach, honest about being self-reported. The gateway is what a security or compliance owner mandates for consequential actions. The door turns the gateway around, in front of the business's own surface. The egress witness receipts what goes around the gateway. The ACS Guardian, a spike, receipts the decisions it gives an agent that asks before it acts. All five exist; the work is widening each, and the door's next steps are detection signals with published error rates and standards-based passports (RFC 9421, AP2).

Done

  • A cheaper receipt hot path: a Cedar policy is parsed once per text instead of on every decision (a few slots, evicted oldest first), and the local log keeps one descriptor open for its appends, reopened when a prune replaces the file. The log lock reads its owner's start time once per process instead of running ps on every append on macOS. The receipt format, the decisions, and what is fsynced are unchanged. bun run bench times each step, Cedar and Rego both; docs/performance.md has the numbers.

  • The egress witness decides by Rego as well as Cedar: a policyFile ending in .rego is compiled with opa build as the gateway compiles one, the input is the Cedar request with plain strings, and a file that does not compile is not loaded. An OPA server is not supported for egress yet (the egress witness, tutorial 44).

  • Drafting an egress policy: agent-custody egress draft-policy --receipts <dir> --issuer-key <witness.pub> verifies each egress receipt, keeps the connections the witness relayed for a verified agent, and prints one permit per agent and scheme over the host:port destinations it used, sorted, with a count and the first and last time seen for each. The output starts with a warning that it permits whatever the agents did, bad destinations included. Writing egress policies is the guide, and its examples are tested; tutorial 42-egress-draft.ts.

  • An OWASP Agent Control Standard Guardian, as a spike: agent-custody acs answers steps/toolCallRequest and steps/toolCallResult with the Cedar policy's allow or deny, and every decision is a receipt with the new issuer kind guardian, signed with the Guardian's key and logged, which ACS's HMAC baseline cannot attribute (ACS §8.6). It publishes the ACS chain head, and its receipts in a log someone else runs answer two of the three directions in ACS issue #18 (a signed checkpoint of chain_hash, and signed heads in an external append-only log; Guardians exchanging heads is not done). Handshake, ACS signatures, the other hooks, modify/ask/defer, and provenance are not implemented; the list. Example 43.

  • The egress witness: agent-custody egress is an HTTP forward proxy for a locked-down agent's connections that do not go to the gateway. It relays CONNECT and plain HTTP with no TLS interception. It identifies the agent only by a per-agent proxy credential it checks, lets the Cedar policy decide per host:port, commits each connection to the log before any byte moves, never dials an internal address for a host name, and receipts each connection, relayed or refused, as issuer kind egress, with the destination observed and the byte counts each way. deploy/gateway/lockdown gives the agent default-deny egress (a Kubernetes NetworkPolicy, an internal docker network). All three verifiers know the new kind, and ten conformance vectors pin it. It cannot see payloads, traffic when lockdown is off, or non-HTTP protocols (the egress witness, credential custody and lockdown, tutorial 41).

  • An incident replay: node examples/38-incident-replay.ts plays a Claude Code session against the starter deny set in enforce mode. terraform destroy and rm -rf ~ are denied before they run, and the denial receipts verify offline with the public key only. The site page An incident replay shows the output and the limit: a credential in the agent's environment goes around the hook (tutorial 38).

  • One command for Claude Code (issue #204): agent-custody init claude-code makes the agent key, writes sdk.json in observe mode with no log, writes a starter deny policy, and merges the three hooks into .claude/settings.local.json or ~/.claude/settings.json with absolute paths, safe to run two times. agent-custody report verifies the receipts and prints the calls the policy would have denied, with the reason, the rule, and the receipt id; removing the mode line enforces. The default local log takes a lock for each append, so hooks that run at the same time keep one log; --log-url needs --log-key; a second run keeps the pinned log key and warns about log flags it does not apply; init warns when git does not ignore the project settings file (sdk.md, tutorial 36).

  • agent-custody export: copies the receipt bundles of a session into a directory for a pull request, byte for byte. It verifies each receipt against the issuer key, refuses one that carries plain arguments, results, or facts (unless --allow-clear), reads only <id>.json at the top level (never salts/, outbox/, or *.authorization.json), and keeps only the receipts issued at or after --since; guide, tutorial 39.

  • A GitHub Action (action.yml at the repository root) that runs verify --markdown on a directory of receipt bundles, posts one table as a pull request comment that it edits on each push, writes the same table to the job summary, and fails when a receipt does not verify or none exists; guide. Issue #205.

  • The door, step one of the agent front door: agent-custody door is a reverse proxy in front of a business's own site or API. Every arrival is classified by what it presented (verified by a grant from a trusted principal in scope for its route, claimed, or unknown), decided by the Cedar policy for its route with the door's own arrival fact in context (the Cedar principal is a verified agent or unknown, never a declared name), committed to the log before any request that can change something is relayed, relayed to the origin or refused with 403, and receipted either way as issuer kind door; both verifiers, the Python verifier, and policy simulate accept the receipts, and five conformance vectors pin them. A Turnstile or hCaptcha token is verified with the provider and recorded as the challenge signal, so a policy can admit a person on a route that refuses unknown. It does not yet detect agents in web traffic or check HTTP message signatures (the door, door signals, tutorial 40).

  • Strict mode: "strict": true refuses a local log, a log that is not hash-only, and a log without pinned keys; precommits every tool, in observe mode too; and turns the outbox off. No call the agent makes is forwarded before its authorization is in a log the operator does not run, every receipt says issuer.strict, and verify --require-strict requires it (usage, tutorial 34). Issue #177.

  • Policy reload: the gateway re-reads policy.cedar whenever it changes and applies it from the next call, with no restart and no dropped session; a policy that does not parse is refused and the one in force stays, and every change or refusal is recorded in receipts/policy-reloads.jsonl (usage).

  • Live Codex support recovery demo: model-selected MCP calls, incorrect CRM memory, a reviewed recovery plan, simulated credit compensation, and verified evidence; guide, with offline regression coverage.

  • Payload-bound human approvals: short-lived signatures over the principal, agent, tool, full arguments, amount, currency, and beneficiary; configurable one- or two-key thresholds, enforced even in observe mode, a configurable lifetime cap, persistent atomic single-use consumption before forwarding, and independent approval-key verification. Guide, tutorial 30.

  • Gateway: MCP proxy, signed delegation, gateway-fetched facts, Cedar policy, denial receipts, Merkle log, offline verifier.

  • Receipt schema carries the issuer kind, so a verifier reads gateway versus SDK before anything else.

  • SDK core: policy decision, record, and a generic wrap(tool, fn) for any framework whose tools are functions.

  • Claude Code command hook for PreToolUse, PostToolUse, and PostToolUseFailure, with blocking on deny.

  • Claude Agent SDK in-process hooks over the same handler.

  • Provider-native deliveries: an upstream wrapping Stripe or GitHub attaches the signed webhook or delivery for the call; a verifier with the shared secret checks the HMAC, the timestamp, and the binding to the result, and reports the execution as attested by shared secret.

  • Logarithmic appends: the Merkle log caches complete subtrees, so issuing a receipt costs the same at the millionth leaf as at the first; measured at 0.15 ms per receipt and about half a millisecond per gateway call including policy, a fact lookup, and the upstream signature. A remote log adds one network round trip plus about five milliseconds of server work per call, two for a pre-committed call; the deployment guide has the measurements against the live log.

  • Retention on the log: prune replaces leaves older than a cutoff with their hashes and removes their bundles, so proofs still verify and the content is gone.

  • Several upstreams under one gateway and one grant, each tool owned by exactly one, with the receipt naming which served the call; consumed facts flow across them.

  • Attested execution: an upstream that holds a key signs its result for the receipt, the gateway embeds it, and a verifier given the upstream key reports the execution as attested rather than observed. The memory server and the demo upstream sign.

  • HTTP upstreams: the gateway reaches an already-running MCP server over Streamable HTTP with a bearer token from the environment, as well as spawning one over stdio.

  • Optional fact lookups: a lookup that references a call argument the call does not carry is skipped rather than denying, so policy can see the fact a write is about to supersede without refusing writes that supersede nothing.

  • Consumed facts: an upstream declares the facts it served in its result _meta, and every later gateway receipt in the session carries those ids as consumed, observed, so what the agent had been shown before each call is on the record.

  • The forwarded call carries the receipt id and the attested agent and principal in _meta, so a stateful upstream can cite the receipt; the memory server in @agent-custody/state runs this way.

  • Conformance vectors, generated by the test suite and published with the spec, and a browser verifier on agent-custody.dev that passes all of them.

  • Sidecar: the SDK issuer behind a local HTTP API (serve), with a Python package on PyPI-ready footing and Go, Java, and Rust clients, so agents in any language get the same receipts from one signing implementation.

  • Consistency proofs between tree heads (RFC 9162), served by the log and checked by the audit command, so an auditor holding an old tree head can prove nothing before it was rewritten.

  • Remote log: the issuer can append to a log run by someone else over HTTP, whose key then signs the tree heads, so a verifier learns the receipt was in a log the operator could not rewrite. Includes the reference log server, bearer-token auth, and a root endpoint for auditors.

  • Framework adapters, each tested against the real package with a scripted model and no network: OpenAI Agents SDK (wrapTools enforces, observeRunner records from lifecycle events), Vercel AI SDK (wrapTools over a real generateText loop), LangChain (ReceiptCallbackHandler records, issuer.wrap enforces).

  • Plans and the tenant portal: every tenant is on a plan (free, ten thousand appends a month; team, a million; enterprise, no allowance) enforced at append with a clear 429; the portal at the operator's PORTAL_HOST lets a team register, get its tenant and first key, watch usage against the plan, mint and revoke keys, buy the team plan through Stripe, and copy the export command, with every action in the audit trail.

  • An audit trail of administrative actions: every tenant created or disabled and every token minted or revoked is recorded with who did it, from the admin page or the command line, shown on the page and carried in the tenant's export.

  • A tenant's export: log-export takes, with the tenant's own token, every leaf hash, the signed head, the published keys, the checkpoints, and their usage, checks that they add up, and writes a log copy the verifier reads offline; the evidence never depends on the operator staying in business.

  • Monitoring and metering: log-check, the outside probe that verifies the head, the checkpoints, and the witness and exits 1 on trouble, run every ten minutes by the monitor workflow; GET /health; and usage per tenant per month on the admin page and as CSV.

  • The witness: a second signer on a machine the log's operator does not control countersigns each checkpoint after proving it extends the last one it signed, refuses a rewritten or forked history with an alarm, and publishes its key; audit --witness-url requires it. Phase 6 of issue #6.

  • The signer, keys, and checkpoints: the key in its own process (signer, --signer-url), the key document at /.well-known/agent-custody-log.json fetched and pinned by verify --log-url and audit --log-url, and signed checkpoints per log published to a directory and a table for a verifier who was not watching. Phase 3 of issue #6.

  • The log over Postgres: log --db-env, leaves as hashes in one table keyed by tenant, one writer per tenant by advisory lock, tenants and hashed tokens in tables managed by log-admin, rate limits and a body cap, retries in the sink, and import for an existing file log. Phase 2 of issue #6.

  • A log for someone else: hashOnly sends leaf hashes so the log never holds a receipt; the reference server runs several tenant logs at /t/<tenant>/ with their own tokens and ids; tree heads name their log and the verifier checks it with --log-id. Phase 1 of the hosted log, issue #6.

  • OpenTelemetry export: with otel in either config, every receipt is also one span at the collector the team already runs, trace id equal to the receipt id, attributes for tool, agent, principal, status, decision, and log position; after the receipt, best effort, never on the evidence path. A door receipt is a SERVER span, since it records an inbound request, and every other receipt is a CLIENT span. A reconciliation receipt is marked and carries no policy decision, so a dashboard never counts it as a second allowed call.

  • Delegation chains for sub-agents: a grant that names the agent's key lets it delegate a narrower grant, with the parent embedded, up to three deep; the verifier and the gateway walk the chain to the principal, refusing any link that escalates scope, widens the window, changes the principal, or is signed by the wrong key; the receipt names the sub-agent and the principal and carries the chain. Pinned by the gateway-chain-* vectors and mirrored in the browser verifier.

  • One gateway for many agents: gateway --http serves the gateway over Streamable HTTP, one session per connection under the grant that connection presents, sessions sharing the upstreams and the policy and nothing else; a platform team runs one gateway in front of the tools instead of one process per agent.

  • Splunk export: with splunk in either config, every receipt is also one event at the HTTP Event Collector, with the receipt id, tool, agent, principal, status, decision, and log position as searchable fields and the token from the environment; the same best-effort rule.

  • The REST connector: a plain HTTP API described as tools in the gateway config, credentials from the environment, so an agent's direct API calls become receipted, policy-checked tool calls through the gateway.

  • Pre-commit authorization for consequential tools: named in precommit, a call is signed and logged before it is forwarded, withheld if the log will not take it, and its receipt carries the committed authorization with proof that it precedes the execution.

  • The grant on every call (issue #97): expiry and revocation are checked on every call and again before the forward, in every mode, on the MCP, HTTP, and A2A fronts; agent-custody revoke revokes a grant and every grant delegated from it. The remote log's answer is verified against pinned keys before a pre-committed call goes upstream (issue #99). The portal verifies an address only from the account's own session before a provider sign-in can link to it (issue #103). The verifier recomputes the execution result digest (issue #104). The Stripe webhook ignores a repeated or out-of-order event (issue #105).

  • Agent-to-agent delegation under custody: the A2A upstream and the gateway's A2A front; receipts per delegation with the delegate's card as a fact. A Google ADK RemoteA2aAgent, or any A2A client, is pointed at the gateway and a refused delegation comes back as a rejected task the delegate never saw. The delegate does not yet countersign its answer; the receipt attests what the gateway sent and observed, not what the delegate did with it.

  • Private evidence: per-tool capture rules, applied by the issuer to the whole statement so nothing outside a fixed structural list is in clear under a private rule; withheld values as salted commitments whose salts never enter the receipt, encrypted under the customer's key, revealed and recomputed by the verifier; the commitments do not confirm a guess of a withheld value. A human approval envelope carries the action fields and an unsalted argument digest, so a private rule withholds it as a commitment; the privacy limits apply.

  • Enterprise identity for the hosted portal: member roles (owner, admin, reviewer, readonly) with invites, TOTP MFA with single-use codes and an operator reset (log-admin user mfa-reset), and single sign-on per tenant through the tenant's own IdP, OIDC or SAML 2.0, set by the operator (log-admin sso set), with an enforce mode that closes the password, GitHub, and Google paths to members. Revoking an agent's authority mid-session already shipped (agent-custody revoke, example 27); example 31 shows the enterprise sign-in and the roles.

  • The state review console: search receipts by transaction, customer, agent or time, read the ten answers, and hand over a portable HTML/JSON case whose signatures, authorizations and inclusion proofs can be verified offline.

  • A log outage after an ordinary call (issue #149): the signed receipt waits in receipts/outbox/ and the gateway appends it when the log is back, the agent gets the real outcome marked log-pending, GET /health counts the pending receipts, and requirePrecommit refuses a remote log with an empty precommit. The failure table gives each case.

  • The anchor (issue #176): agent-custody anchor proves each new checkpoint extends the last one it anchored, then commits a Merkle root over them in one transaction from its address to itself, on Ethereum by default or on Base, Arbitrum, Optimism, Polygon, or any EVM chain by id (--chain), with a fee cap sized per chain. It publishes each log's record and inclusion proof, sends nothing for an idle log, skips a run above a fee cap, and never has two transactions in flight. Every transaction covers every watched log, and a transaction counts only once its block is finalized, following reorgs. log-check --anchor-url checks the record against one or more JSON-RPC endpoints, requires the log id the verifier pinned and the address's newest transaction, and checks the log's head against the anchored checkpoint, so a rewritten or rolled-back tree is caught from the chain alone (anchoring, example 33).

  • Completeness from the provider's side (issue #178): agent-custody completeness lists Stripe's refunds, payouts, or transfers over a window with the auditor's own read-only key, and matches each to a verified receipt by the provider's object id. It reports actions with no receipt, receipts the provider does not have, amount, currency, and beneficiary mismatches, calls forwarded without an outcome receipt, and receipts it cannot match (completeness, example 35).

Next, in the order it pays off

The first three close the gap between a log the operator cannot rewrite and a record the operator cannot shape.

  1. Run the anchor for log.agent-custody.dev on mainnet: a funded key held only by the anchor service on the host, its records served beside the checkpoints, its address and each tenant's log id published in the welcome sheet and on the security page, and the monitor checking it. The code is done. Issue #176.
  2. Completeness beyond Stripe: the same check over GitHub's actions, and over on-chain transfers out of an agent's wallet, where the chain itself lists every transfer. Issue #178.
  3. A TEE gateway: a confidential container with a reproducible, attested build, the signing key generated and kept in the enclave, TLS ending inside it, and both verifiers checking the path from the receipt key, through the attestation, to the release. It replaces the TEE-hosted signer planned before. Issue #179.
  4. Run the witness for log.agent-custody.dev on a machine and under an account that is not ours, and require it in the welcome sheet. The code is done; what it needs is a second operator. Issue #116.
  5. Post-quantum signatures: ML-DSA beside Ed25519 in the same DSSE envelope, hybrid by default when a PQ key is present, in every signed artefact and in the browser verifier. Issue #11.
  6. Receiver attestation for agent-to-agent calls: the delegate signs its answer for the receipt, as a signing upstream does today.
  7. SD-JWT redaction, then ZK proofs of policy compliance. Not before.

Source: packages/receipts/README.md at commit 5fbe249

Tools

0
SourceWeft started this package once in an isolated sandbox, without credentials, to list its tools. That failed: the server did not start.

Version history

12
  1. v0.16.6LatestNov 11, 2026
  2. v0.16.5Nov 11, 2026
  3. v0.14.0Nov 3, 2026
  4. v0.13.0Oct 29, 2026
  5. v0.12.0Oct 26, 2026
  6. v0.11.0Oct 25, 2026
  7. v0.10.0Oct 23, 2026
  8. v0.9.0Oct 21, 2026
  9. v0.8.0Oct 19, 2026
  10. v0.7.0Oct 18, 2026
  11. v0.6.19Oct 11, 2026
  12. v0.6.15Sep 28, 2026

Score

50% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts

    Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.

  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listed15 pts

    Answer tools/list without a credential, or ship a package that starts in a sandbox without one.

  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts