Skip to content

Advanced ​

The front page covers one job: stop a coding agent such as Claude Code from running destructive commands, and keep a signed record that a reviewer can check. This page holds the rest of the product. Each section says what the part does and links to the page that explains it.

Contents ​

The two ways to record a call ​

The Claude Code hook runs in the agent's own process. It can deny a call that the agent makes through its tools, and it writes a receipt. The receipt is the agent's own report: every field is claimed.

The gateway is a separate process between the agent and its tools. It checks a grant that a person signed, evaluates a policy on facts that it fetches itself, forwards or denies the call, and issues a signed receipt either way. The agent cannot skip it for the tools behind it. A credential in the agent's own environment still goes around it. The threat model lists what each part defends.

Product overview film ​

A 93-second narrated film with captions.

timechapterwhat it shows
0:00The problemThe record of an agent's action is its own log.
0:14The gatewayBetween your agents and their tools, a signed receipt for every call.
0:24How it worksA signed grant, a policy check before the tool runs, a Merkle log.
0:40The refund story£500 approved. Then a prompt injection asks for £5,000.
1:12The auditMonths later, an auditor checks it with public keys and a copy of the log.

The problem ​

The record of an agent's actions is the log that its own process wrote. Nobody outside the team can check it.

  • Made-up authority. The agent refunds £50,000. Its log says the customer asked. Nobody signed anything that allowed it.
  • A log that can be edited. The team that ran the agent holds the log. After the incident, the entry that matters is gone or different.
  • Evidence nobody else can check. Traces sit in a vendor account. The auditor gets a screenshot and a promise.

The refund demo ​

bun run demo runs a support agent against a test Stripe server. Each call writes a signed receipt to your disk. Then the demo verifies the receipts and edits one.

bash
git clone https://github.com/svayatta/agent-custody && cd agent-custody && bun install && bun run demo

It needs Node 22 and Bun. It runs four calls through the gateway, three refunds and a payout, and verifies each receipt.

> refund £500 to a verified customer
  EXECUTED {"refund_id":"re_b5db9a9e","customer_id":"cust_123","amount":50000,"status":"succeeded"}
  receipt a8094805-e7d0-48da-bf05-9dcfdfe7a1dd

--- a8094805-e7d0-48da-bf05-9dcfdfe7a1dd
PASS  receipt signature (issuer key)  (keyid 13221dd037cb)
PASS  delegation signature (principal key)  (signed by 0b715318f782)
PASS  policy decision consistent with execution  (allow -> executed)
PASS  log inclusion proof  (leaf 0 of 1, root 4901cb4cdc68)
… 15 more checks

RESULT: VERIFIED

ISSUER: gateway, enforced outside the agent's process; the agent could neither skip nor forge this receipt

field           provenance  value
principal       attested    user_456
agent           attested    support-agent
model           claimed     claude-fable-5-1
tool            observed    stripe.refund
args            claimed     {"amount":50000,"customer_id":"cust_123"}
fact.customer   observed    {"email":"alex@example.com","id":"cust_123","verified":true}
policy          observed    allow [policy1] policy ba4e4461ffc4
consumed        observed    (no facts shown before this call)
execution       observed    executed (carries an upstream signature; pass --upstream-key to check it)

Source: packages/receipts/scripts/demo.ts. Receipt ids change on every run.

How it works ​

Every call through the gateway goes through the same five steps.

  1. Authorize. A person signs a grant: which agent, which tools, for how long.
  2. Gate. The gateway fetches the facts itself, evaluates the policy, and forwards or denies.
  3. Record. One signed receipt per call: who authorized it, what was asked, what was checked, what happened.
  4. Log. The receipt's hash goes to a Merkle log with signed heads. A rewrite is detectable by anyone who kept a checkpoint.
  5. Verify. Anyone with the public keys checks a receipt, in the shell or the browser, offline.
Principala person, with a keyAgentany frameworkGatewaychecks the grantevaluates the policyfetches facts itselfToolMCP, REST, anythingsigns a granttrusted keytool callonly if permittedone per call, allowed or deniedSigned receiptwho, what, saw, did, dependedhashMerkle logsigned headsCheckpointspublishedVerifierpublic keys, nothing elsereads
The gateway sits between the agent and its tools. Every call becomes a signed receipt whose hash lands in a tamper-evident log. Anyone with the public keys can verify a receipt, offline.

Policies ​

Policies are written in Cedar and evaluated on facts that the gateway fetches itself. Each receipt records the policy digest. Nine more policies, each run by the test suite, are in Writing policies.

Limits. A ceiling per call, in minor units. No floats, no rounding.

cedar
permit(principal, action == Action::"stripe.refund", resource)
when { context.args.amount <= 100000 };

Facts, not claims. The gateway looks the customer up itself before it decides. The agent's own arguments are never enough.

cedar
permit(principal, action == Action::"stripe.refund", resource)
when { context.facts.customer.verified
    && context.args.customer_id == context.facts.customer.id };

Allowed targets. A short list of accounts, hosts, and paths. Anything else is refused before it leaves.

cedar
permit(principal, action == Action::"payout.send", resource)
when { ["acct_treasury", "acct_payroll"].contains(context.args.to) };

Human approval. A larger change needs a person that the gateway can see approved it, in the system of record.

cedar
permit(principal, action == Action::"pr.merge", resource)
when { context.facts.checks.all_green
    && context.facts.reviews.human_count >= 1 };

Boundaries. Where an agent may write, and what it must say when it does.

cedar
permit(principal, action == Action::"kb.write", resource)
when { context.args.path like "notes/*"
    && context.args has source };

Evidence before effect. Name a tool as consequential, and the authorization is logged before the call is forwarded. If the log refuses, nothing happens.

json
"precommit": ["stripe.refund", "pr.merge", "deploy.promote"]

Receipts compared with logs and traces ​

Receipts export to OpenTelemetry and Splunk, so you keep your current tools. The exports are in the SDK reference and the gateway reference. Tutorials 18 and 19 run them.

Application logTraceGateway receipt
Written bythe agent's processthe agent's processa gateway, with its own key
Allowed or denied before the tool rannonoyes, and a denial is a receipt
Who authorized itnot recordednot recordedthe grant a person signed
A rewrite is detectablenonoyes, by anyone who kept a checkpoint
Who can check itlog access holdersthe tracing accountanyone with the public keys

Measured on a laptop (lower is better):

whatmedian
a policy decision0.10 ms
a full gateway call0.52 ms
verifying a receipt0.14 ms
a receipt (the log gets 32 bytes)5.7 KB

Medians, Apple silicon, Node 22, from scripts/bench.ts. You can run it.

Use cases ​

Each use case has a runnable example, or says that it has none.

More, by kind of team, each with its policy: Use cases.

Parts, one by one ​

partwhat it doespage
The table of piecesFor each part, the failure it prevents and when you need itWhat each piece is for
TutorialsOne runnable example for each aspect of the code, run by the test suiteTutorials
The state packageA fact ledger: every belief of an agent cites the receipt that caused it, and a wrong belief is retracted, not deletedThe fact ledger
Blast radiusFrom a bad belief to every call and later belief that came from itThe fact ledger
Quarantine, certified forget, retention, legal holdGoverned memory for personal dataThe fact ledger
Extra memory adaptersWrite-through to Mem0 and Zep, so the ledger sits under a store that you already runThe fact ledger
Single sign-on and rolesAn enterprise tenant signs in through its own identity provider, with roles (owner, admin, reviewer, readonly)Hosted log and portal, tutorial 31
The consolesThe gateway console (config, tools, receipts with a verdict each, policy replay, grant revocation) and the state review consoleThe gateway console, Explain, review, packs
Payload-bound approvalsOne or two approvers' keys sign the exact action before it runsApprovals
Financial operationsDurable operation ids, provider idempotency keys, budget reservations, and reconciliationFinancial operations
CompletenessMatch the provider's own records against verified receipts, to find actions taken outside the gatewayCompleteness
The chain anchorCommits the log's checkpoints to Ethereum from a published address. It has not run on mainnet for the hosted log yetAnchoring
The witnessA second signer that countersigns the log's checkpoints. The code exists. No independent witness runs yetVerifying a receipt, the hosted log
Splunk and OpenTelemetry exportsOne event or span for each receipt, and the collector is never on the evidence pathTutorial 18 and 19, the SDK reference
What the evidence satisfiesWhich artifact answers which SOC 2, ISO 27001, EU AI Act, and UK GDPR requirementCompliance

Other frameworks and languages ​

The Claude Code hook has the same input and output as the Claude Agent SDK, so one handler serves both. These frameworks have an adapter:

frameworkhowwhere
Any MCP hostthe gateway as an MCP serverThe gateway
OpenAI Agents SDKwrapToolsGetting started
LangChain, LangGraphissuer.wrap and a callback handlerGetting started
Vercel AI SDKwrapToolsGetting started
Google ADKtool callbacksGetting started
OpenClawthe before_tool_call hookThe SDK page
DeepSeek Harness, Hermes, CrewAIa plugin or an adapterThe SDK page, the Python client
Pythonthe Python client of the sidecarThe Python client
Go, Java, Rustthe HTTP sidecarThe sidecar, the client examples

Hosted log, trust, and the specification ​