financial governancefor AI agents.keep the receipts.

one line wraps your LLM client. every governed call becomes a ledger transaction — with a receipt anyone can verify.

verify a ledger →

Apache 2.0 · 0 runtime deps in the verifier · 2 commands to first receiptruns on TigerBeetle 0.17.9

the docket

the facts, itemized.

exhibit c
9
transfer codes
exhibit f
12
policy operators
exhibit e
0
runtime deps in the verifier
exhibit b
23+
models priced
exhibit d
SHA-256 · RFC 6962
hash chain, merkle proofs
exhibit d
Ed25519
anchoring signatures
open ledger
2
commands to first receipt
open ledger
Apache 2.0
license

exhibit a

RECORD

every governed call returns evidence.

three frontier models, three real receipts, one ledger. every field below is the object the SDK handed back — 50,000 usertokens = $5.00.

import { trust } from "usertrust";

const client = await trust(new Anthropic());

const { response, receipt } =
  await client.messages.create({
  model: "claude-fable-5",
  max_tokens: 256,
  messages: [...],
});
receipt · Anthropic
{  "transferId": "tx_msm19wxa_a107246b",  "cost": 104,  "budgetRemaining": 49896,  "auditHash": "f4a15bc63fc64bcdab2dcca67718d6a89469311e01cb16fa9dd7bb0c31286654",  "chainPath": ".usertrust/audit",  "receiptUrl": null,  "settled": true,  "model": "claude-fable-5",  "provider": "anthropic",  "timestamp": "2026-08-09T16:45:13.634Z",  "usageSource": "provider",  "usage": {    "inputTokens": 317,    "outputTokens": 131,    "cacheReadTokens": 87,    "cacheWriteTokens": 43  },  "endpoint": {    "class": "cloud",    "runtime": "unknown"  },  "meter": {    "costBasis": "usd-proxy",    "rateSource": "table"  },  "pricing": {    "appliedRates": {      "inputPer1k": 100,      "outputPer1k": 500,      "cacheReadPer1k": 10,      "cacheWritePer1k": 125    },    "tableVersion": "2026-08-09"  }}
captured v3.2.0 · TigerBeetle 0.17.9 · ledger · 699fcef · 2026-08-09

exhibit b

your SDK. one line.

import Anthropic from "@anthropic-ai/sdk";import { trust } from "usertrust"; const client = await trust(new Anthropic()); const { response, receipt } =  await client.messages.create({    model: "claude-fable-5",    max_tokens: 1024,    messages: [...],  });
the usertrust governance dieutututanthropicopenaigooglereceipts

your keys. your billing. your evidence.

works with

  • vLLM
  • LM Studio
governed surfaces

governed

anthropic

  • messages.create
  • messages.stream
  • messages.parse
  • beta.messages.create
  • beta.messages.stream
  • beta.messages.parse

openai

  • chat.completions.create
  • responses.create — incl. stream: true

google

  • models.generateContent

passthrough — not governed

anthropic

  • messages.batches
  • beta.messages.batches
  • beta.models
  • beta.files

openai

  • responses.stream() helper
  • responses.parse()
  • responses.retrieve / cancel / delete
  • legacy completions.create
  • beta.* (assistants / threads / realtime)

google

  • models.generateContentStream
  • models.countTokens

passthrough surfaces bypass governance, audit, and budget enforcement — route spend through the governed entry points. 23+ models priced across anthropic, openai, and google.

exhibit c

ENFORCE

hold. settle. or void.

the banking pattern: held, then settled or voided. never lost.

every governed call opens a two-phase hold against the budget before a token moves — available = budget − Σ(holds). without holds, concurrent agents each see the full budget and settle past it. with holds, the first hold that would exceed what is actually available throws. the budget here is 50,000 ut = $5.00 — the starter default. run the race yourself:

the gate is per-governor: one process serialises its own budget read and hold behind a mutex. across processes the guarantee is the ledger’s — TigerBeetle atomically rejects a hold that would overshoot, whoever asked for it.

4
12,000 ut
enforcement mode
available2,000 utbudget50,000 utΣ(holds)48,000 ut

budget bar — holds stack. the cap is the wall.

  • agent 01hold 12,000 utheld
  • agent 02hold 12,000 utheld
  • agent 03hold 12,000 utheld
  • agent 04hold 12,000 utheld
  • agent 01 · retry PolicyDeniedError: hold would exceed available budgethold 12,000 ut > available 2,000 ut — nothing moved, nothing owed

held 48,000 ut of 50,000 ut — the bar never passes the cap.

two-phase holds: 48,000 ut held of 50,000 ut, 2,000 ut available, agent 01 · retry blocked

arithmetic, not a screenshot: the same gate the block-budget-overshoot default rule enforces before every governed call. the error it throws is captured below, verbatim.

case file 001incident log

subject: autonomous coding agent · production API key

overnight, the agent entered a retry loop nothing was watching.

47 identical completion calls. no variation. no ceiling.

the spend surfaced as a $500 line item on the invoice — not in any log.

no budget hold. no audit chain. nothing threw.

finding: the run left nothing behind to verify, dispute, or replay.

no receipts existed.

file closed — unstamped
BLOCKED

the denial, on the chain.

denials don’t get receipts. they get chain events.

what the caller gets
PolicyDeniedError: Policy denied: [block-budget-overshoot] Deny pre-spend when estimated cost would drive remaining budget below zero; [WARN] [warn-high-cost] Emit a warning when estimated cost exceeds 1000 tokens

  Hint: A budget rule denied this call: increase the budget in trust() options or reduce the call's max_tokens, and review your budget_remaining / budget_remaining_after tiers.
  Docs: https://usertrust.ai/docs/errors/policy-denied
what the chain gets · policy_denied
kind
policy_denied
decision
deny
denialClass
budget_gate
rule
block-budget-overshoot
model
claude-fable-5
estimatedCost
2,000,003 ut
budgetRemaining
49,344 ut
promptHash
bbef7150e9b81a13378e3f48d66e2c29b22e14d35cae8e89007250bf93c819b4
transferId
tx_msm19z83_ddc7e446
hash
627256b4498f26af6c5ec8ff55d9ea21cdab3b021028f96021600a5afb1cdbc8
previousHash
dbcbefad935613c7d02c806c039d2bea8fc3f73e2a3b022c7533010adf140bbf
the prompt is hashed, never stored · captured on usertrust 3.2.0 @ 699fcef · one governed call with max_tokens 4,000,000 against a 50,000 usertoken budget

exhibit d

RECORDANCHOR

tamper with one byte. break the whole chain.

f4a15b0f103191df5d80bad9919d1052f85adbcbef627256root

the chain verifies itself

sha-256 · rfc 6962 leaf and node prefixes

and can be anchored beyond your infra

  • S3 Object Lock
  • git
  • SIEM
  • RekorEXPERIMENTAL

entries captured in ledger mode — TigerBeetle enforcing; dry-run writes the same audit chain without the ledger

#1 llm_callsha256 f4a15bc6…prev 00000000…#2 llm_callsha256 0f1031df…prev f4a15bc6…#3 llm_callsha256 91df5d9e…prev 0f1031df…#4 llm_callsha256 80bad924…prev 91df5d9e…
  1. entry 1

    llm_call

    llm_call · claude-fable-5 · 104 usertokens

    sha256 f4a15bc63fc6

    prev   000000000000

  2. entry 2

    llm_call

    llm_call · gpt-5.6-sol · 59 usertokens

    sha256 0f1031df7133

    prev   f4a15bc63fc6

  3. entry 3

    llm_call

    llm_call · kimi-k3 · 31 usertokens

    sha256 91df5d9e8bc1

    prev   0f1031df7133

  4. entry 4

    llm_call

    llm_call · claude-fable-5 · 104 usertokens

    sha256 80bad924078d

    prev   91df5d9e8bc1

  5. entry 5

    llm_call

    llm_call · claude-fable-5 · 112 usertokens

    sha256 919d10dca884

    prev   80bad924078d

  6. entry 6

    llm_call

    llm_call · claude-fable-5 · 119 usertokens

    sha256 52f85adb310a

    prev   919d10dca884

  7. entry 7

    llm_call

    llm_call · claude-fable-5 · 127 usertokens

    sha256 dbcbefad9356

    prev   52f85adb310a

  8. entry 8

    policy_denied

    policy_denied · claude-fable-5 · denied · budget_gate

    sha256 627256b4498f

    prev   dbcbefad9356

chain intact — 8 entries, every prevHash verified

live demo recomputes sha-256 over each card's visible fields — printed stubs are the vault's captured hashes over the full event record

view raw JSONL →

built for the record-keeping era — EU AI Act Art. 12 traceability

tamper-evident, not tamper-proof — detection, not recovery.

exhibit e

VERIFY

don’t take our word for it.

0

runtime dependencies in the verifier.

the verifier owes us nothing.

packages/verify/package.json — dependencies key count

usertrust-verify
Vault integrity: VERIFIED (UNANCHORED — internal consistency only)
Chain length: 8 events
Merkle root: 4f7ad6693448abe0f713b5656cf13021d61c01ec36b94df55463b2482f4514c8
Hash algorithm: SHA-256
First event: 2026-08-09T16:45:13.602Z
Last event: 2026-08-09T16:45:16.565Z
All hashes: valid (8/8)

exhibit f

BLOCKED is a feature.

the gate runs before the provider is ever called. a denial throws, the provider is never reached, and no receipt is returned — but the refusal is not silent. denials don’t get receipts. they get chain events.

12 policy operators · packages/core/src/shared/types.ts — FieldOperator union members

policy.yaml
rules:
- name: frontier-cost-cap
effect: deny
enforcement: hard
severity: critical
conditions:
- { field: model, operator: , value: [gpt-x, gpt-x-mini] }
- { field: estimated_cost, operator: , value: 2000 }
- name: research-scope-guard
effect: deny
enforcement: hard
conditions:
- { field: cost_center, operator: , value: research }
- { field: model, operator: , value: "^gpt-x" }
scopePatterns: ["agents/research/**"]
timeWindows: [{ daysOfWeek: [1, 2, 3, 4, 5], startHour: 9, endHour: 18 }]
print spool
  1. chit-01
    modelclaude-fable-5
    est_cost840
  2. chit-02
    modelgemini-2.5-flash
    est_cost120
  3. chit-03
    modelgpt-x-mini
    card_number
    est_cost300
  4. chit-04
    modelclaude-haiku-4-5
    cost_centersupport
  5. chit-06
    modelgpt-x-mini
    est_cost990

thrown AND chained

BLOCKED

thrown: PolicyDeniedError · the provider was never called

chained: policy_denied · denialClass budget_gate · prompt hashed

frontier-cost-cap · estimated_cost 2400 gt 2000 · model gpt-x in [gpt-x, gpt-x-mini]

exhibit g

VERIFY

every way we know to forge a ledger.

every forgery fails. every legitimate operation verifies.

29 scenarios · every verdict below is the string the verifier really returns

emerald rows are control cases — legitimate operations that must verify. red rows are the non-verified states: forgeries the verifier refuses (ANCHOR_MISMATCH, ANCHOR_INVALID), and the can’t-attest states (ANCHOR_STALE, ANCHOR_UNVERIFIABLE, UNANCHORED) — no accusation in those, and the default verifier still exits clean; --require-anchor is what fails them.

packages/core/tests/harden/anchoring/anchor-corpus.test.ts29 rows
  1. F1 KILL: mutate anchored event + full re-chain + .meta rewrite → ANCHOR_MISMATCH/root-mismatch; --tx never prints INCLUSION VERIFIEDANCHOR_MISMATCH
  2. F2 KILL: delete the ENTIRE audit tree; external anchor supplied → ANCHOR_MISMATCH/deletion, exit 1ANCHOR_MISMATCH
  3. tail truncation below latest treeSize + .meta rewrite → ANCHOR_MISMATCH/rollbackANCHOR_MISMATCH
  4. rollback to an older internally-valid snapshot → ANCHOR_MISMATCH/rollback (AC-1.3)ANCHOR_MISMATCH
  5. fork: rewrite events ≤ treeSize, regrow chain → root-mismatch + consistency-failure (AC-1.4, row 17)ANCHOR_MISMATCH
  6. attacker keypair re-signs full history; auditor pins the real root → ANCHOR_INVALID (AC-6.3)ANCHOR_INVALID
  7. key swap inside the vault: doctored identity.json + attacker-signed mirror → ANCHOR_INVALID (pinned root governs)ANCHOR_INVALID
  8. gap: middle record deleted from the supplied set (mirror wiped) → anchor-chain-gap (AC-1.5)ANCHOR_MISMATCH
  9. monotonicity: decreasing treeSize across records → ANCHOR_MISMATCHANCHOR_MISMATCH
  10. fork evidence: same prevAnchorHash, same key, DIVERGENT content → ANCHOR_MISMATCH/forkANCHOR_MISMATCH
  11. benign duplicate: identical modulo timestamp/sig → ANCHORED_VERIFIED + duplicate-anchor warningANCHORED_VERIFIED
  12. emitter race with the lock: exactly one record minted, loser skipsANCHORED_VERIFIED
  13. mirror↔external disagreement at one anchorSeq → ANCHOR_MISMATCH/mirror-disagreement; external governs (AC-1.2)ANCHOR_MISMATCH
  14. cross-vault replay: valid record from another vaultId → ANCHOR_MISMATCH/vault-id-mismatchANCHOR_MISMATCH
  15. corrupt JSON and unknown extra fields → ANCHOR_INVALID (fail-closed)ANCHOR_INVALID
  16. degenerate numerics (treeSize 0 / negative / float / unsafe) → ANCHOR_INVALID/range-invalid, NO throwANCHOR_INVALID
  17. stale beyond thresholds → ANCHOR_STALE; exit 0 default, 1 strictANCHOR_STALE
  18. future-dated timestamp on the newest anchor → future-timestamp warning; event threshold still firesANCHORED_VERIFIED
  19. anchors present, no trust material → ANCHOR_UNVERIFIABLE/no-trust-material; exit 0 default, 1 strictANCHOR_UNVERIFIABLE
  20. legacy vault, no anchors anywhere → UNANCHORED; exit 0 default, 1 strict (AC-4.1)UNANCHORED
  21. witness unreachable → ANCHOR_UNVERIFIABLE/witness-unreachable; NEVER ANCHORED_VERIFIED from the mirror (AC-2.4)ANCHOR_UNVERIFIABLE
  22. witness disagrees: fetched record contradicts the vault → ANCHOR_MISMATCH, always exit 1ANCHOR_MISMATCH
  23. records after a rotation still signed by the superseded key → ANCHOR_MISMATCH/rotation-continuityANCHOR_MISMATCH
  24. genesis under a successor pin instead of the root → ANCHOR_MISMATCH (valid sig, wrong position)ANCHOR_MISMATCH
  25. rotation hijack with pins supplied: rotation to an unpinned key → ANCHOR_MISMATCH/rotation-unpinnedANCHOR_MISMATCH
  26. rotation without pins: accepted + prominent rotation-unpinned warning; successor chain verifiesANCHORED_VERIFIED
  27. competing rotations extending the same predecessor → ANCHOR_MISMATCH/forkANCHOR_MISMATCH
  28. mirror-only + --require-external-anchor → exit 1 (state stays ANCHORED_VERIFIED)ANCHORED_VERIFIED
  29. happy paths: single anchor / full history / grown chain / post-rotation keyring → ANCHORED_VERIFIEDANCHORED_VERIFIED

indexed by row · source test titles linked verbatim; their original spec-row prefixes are omitted (row 17 was folded into scenario 5 upstream).

reproduce it yourself
$ git clone https://github.com/usertools-ai/usertrust.git
$ cd usertrust
$ npm ci
$ npm test -- anchor-corpus

don’t trust us — recompute us.

the discipline

the harden doctrine.

harden suites
53
test cases
3014
test assertions
7228
AGENTS.md invariants
35
shared verifier lines
0

counted at capture · 699fcef · v3.2.0

Core produces the hashes in a vault; verify recomputes them without importing core. If the two share code, the verifier verifies nothing.

Never "DRY up" this duplication. Mirror every change into both packages.

AGENTS.md · the packages/verify parity contract

a scrolling strip of entries from the published audit chain — the same entries exhibit d lists in full.

open your ledger.

the usertrust mascot, full length, on its white studio ground
governance wins.

your keys. your billing. your evidence.

start in dry-run — TigerBeetle skipped; audit chain and policy gates still run

setup2 min
runtime deps (verifier)0
licenseApache 2.0

TOTAL SURPRISES ··· 0

chain head 627256b4498f26af6c5ec8ff

captured v3.2.0 · 2026-08-09

run it where you need it.