Skip to content

Getting started

Node 22 or later. Both packages ship compiled JavaScript with type declarations.

bash
npm install @agent-custody/receipts @agent-custody/state

1. Give the agent's process a signing key and a config. The key signs every receipt. The config says where receipts and the log go.

bash
npx agent-custody keygen --dir keys --name app
json
{ "agentId": "support-bot", "identity": { "keyFile": "keys/app.key" }, "receiptsDir": "receipts", "logFile": "log.jsonl" }

2. Record a tool call as a receipt. Wrap the functions the agent calls, or record a call explicitly. Either way a signed receipt lands in receipts/ and a leaf in the Merkle log.

ts
import { createSdkIssuer, loadSdkConfig } from "@agent-custody/receipts";

const issuer = createSdkIssuer(loadSdkConfig("./sdk.json"));

const lookup = issuer.wrap("crm.lookup", async (args: { id: string }) => crm.lookup(args));   // records every call
const customer = await lookup({ id: "acct:42" });

const bundle = issuer.record({ tool: "crm.lookup", args: { id: "acct:42" } }, { status: "executed", result: customer });   // or record one by hand and keep the bundle

3. Verify it, offline. Anyone with the public key and a copy of the log can check the signature, the statement, and the inclusion proof. No access to the agent or the issuer needed.

ts
import { loadPublicKey, verifyBundle } from "@agent-custody/receipts";

verifyBundle(bundle, { issuerKeys: [loadPublicKey("keys/app.pub")], principalKeys: [], logFile: "log.jsonl" }).ok;   // true
bash
npx agent-custody verify receipts/<id>.json --issuer-key keys/app.pub --log log.jsonl    # the same check from the shell

4. Record what the agent now believes, citing the receipt. The ledger is bitemporal: it knows when a fact was true and when the agent learned it. A belief points back at the receipt that produced it.

ts
import { receiptIdOf } from "@agent-custody/receipts";
import { Ledger } from "@agent-custody/state";

const ledger = new Ledger("./ledger.jsonl"); // or "./ledger.sqlite", or "postgres://…"
const belief = await ledger.assert({ subject: "acct:42", predicate: "plan", value: customer.plan, space: "org", actor: "support-bot", source: { receiptId: receiptIdOf(bundle) } });

5. When a belief is wrong, undo it without losing the record. Retract removes it from the present, keeps it visible to questions about the past, and restores whatever it had superseded. The receipt id says exactly which call produced the bad belief.

ts
await ledger.retract({ factId: belief.fact.factId, actor: "user:admin", reason: "CRM lookup returned a stale plan" });
await ledger.asOf({ subject: "acct:42" });                                                  // [] now
await ledger.asOf({ subject: "acct:42", validAt: earlier, txAt: earlier });                  // still shows what was believed then
await ledger.history(belief.fact.factId);                                                    // assert, retract, with actors and reasons

This whole loop is one runnable file, packages/state/examples/02-receipt-to-belief.ts, executed by the test suite.

Where to go next

  • Enforce instead of record: put the gateway between the agent and its MCP tools. The gateway
  • Hook an existing framework, or another language. The interceptor SDK
  • Write policies, for tools and for memory, with tested examples. Policies, and what a verified receipt proves: Verification
  • Put beliefs in the ledger. State