Proof · the evidence index
The records a limit leaves behind.
Your agent's limits belong outside the prompt. These are the records that belief leaves, each labelled with where it came from. First, a sub-agent launch that Burn's Claude Code hook refused on one of our machines.
01 · Local plugin recordBurn's Claude Code hookone of our machinesSep 20 2026
- decision
- STOP, blocked: true spawn_rate:STOP
- tool
- sub-agent launch action: spawn, host claude-code
- time
- 2026-09-20 20:20:49.753 UTC
- chain link
- previous e015b61fad863633…25dda5be0e SHA-256 of receipt 15
- limit
- 16 launches in 15 active minutes warn at 8, mode enforce
- measured
- 16 launches, depth 1
- policy digest
- 7fc75c3841bbe6ee…c9c5cf6e
- signer
- key ef7bfccb878b05e0 Ed25519
- signature
- 9/+XXXc7c1zCDCSRQZFCAh6x…5dlhErBg==
Raw receipt JSON
{
"payload": {
"schema": "agentguard.burn.decision.v1",
"decisionId": "e7e74842-5779-46e8-b9c2-3bf714e020f6",
"at": 1789935649753,
"host": "claude-code",
"action": "spawn",
"sessionDigest": "58143098bae620e9ae380915798f2ba88d9342f2de8d3908277461ff7cb0c60d",
"policy": {
"mode": "enforce",
"digest": "7fc75c3841bbe6ee6401bfb57db26e1ce117de1d1dd53095cbb1f7fbc9c5cf6e"
},
"measured": {
"sessionTokens": 397712009,
"sessionSpawns": 16,
"proposedDepth": 1,
"inFlight": 0,
"occupiedMs": 0
},
"coverage": {
"spawns": "authoritative",
"depth": "authoritative",
"usage": "authoritative"
},
"verdict": "STOP",
"blocked": true,
"reasons": [
"spawn_rate:STOP"
],
"previous": "e015b61fad863633c5ed01ad30623149058e5560ca66272c9940d025dda5be0e"
},
"keyId": "ef7bfccb878b05e0",
"algorithm": "Ed25519",
"publicKey": "MCowBQYDK2VwAyEAiFnnAXnRd4QU15O4n91QtsHNtuX9L1N6qOUMYlN9ovU=",
"signature": "9/+XXXc7c1zCDCSRQZFCAh6x/eaRubzupQ4u9zXVfkI8xc6L6duSWYy/5Jx83w6jnziI/5MZ9K5wNb5dlhErBg=="
}What it proves: the limit ran in a hook on the machine, outside the model, and signed a chained record of the refusal.
Burn, which the free plugin runs on, signed 18 receipts in 20 seconds of one Claude Code session: 7 OK, 8 WARN, 3 STOP. A receipt carries counts, digests and the verdict, never a prompt, path or tool input. It was recorded before the plugin asked first: today, in Claude Code, that launch waits for your yes.
Check it yourself. Each signature covers the receipt's canonical payload under the key it carries, and each previous is the SHA-256 of the receipt before it.
Also on file: two plugin receipts in the SDK format the hosted verifier reads, signed Sep 19 2026 by @agentguard-run/codex-plugin 0.2.1 from a synthetic tool event (no service called; unlicensed, so the decision is shadow). Open it in /verify · JSON
02 · Recorded run · Claude Code, Sep 23 2026
15 launches in 36.6 seconds, 8 refused.
What it proves: the refusals happened in a live Claude Code session, and the clip was rendered from signed records and transcripts whose hashes the provenance file lists.
Recorded with Burn 0.3.12, 109 live frames. Claude Code started 15 copies; AgentGuard refused 3 attempts at the limit and 5 more when the copies tried to start their own. The copies read 29,997,622 tokens, $78.21 at API list price, not a bill. Like the record above, it predates the plugin asking first.
Open the clip
- recording
- burst-v2.jsonl sha256 2e04a58f…16f964c
- blocks
- blocks-v2.json sha256 797a3853…1e33bf71
- data
- fanout-data.json sha256 23418da5…cb64f78
- transcripts
- 15 child transcripts, each hashed in the file
03 · Hosted experiment · agentguard.run signer
Mint a receipt, verify it, then break it.
What it proves: a receipt signed by the hosted gate verifies under AgentGuard's published key, and one edited field makes the same verifier refuse it.
The receipt beside this was minted from the metadata fixture in its curl round trip on Oct 28 2026 at 20:30 UTC and verified the same second; the edited copy was refused. The fixture is metadata only: no prompt, customer record or provider key. Each button is a request to agentguard.run; open your network tab and watch it.
- algorithm
- EdDSA · Ed25519
- key id
- 89c94118e046da63
- public key, x
- VJgSxpuwsx3vgT9UBd5sE9nIQORpR9QpFVvS42Su3pg
- published at
- /.well-known/jwks.json
- live check
- compared with the key in the receipt on file when the page loads
- action
- refund $142.50 USD payments_processor
- policy
- refunds-tier-1 v1 hash b6021aa3…
- capability
- payment_execute required payment_execute claimed
- window
- per_day, cap $5,000.00 $0.00 spent before
- reserved
- $142.50
- signer
- 89c94118e046da63 sandbox: true
Raw receipt JSON: edit it, then press Verify
The same round trip in curl
# 1. mint a signed authorization receipt (metadata only) curl -s https://agentguard.run/api/gate/authorize \ -H "content-type: application/json" -d '{ "tenant_id": "demo", "agent_id": "support-agent", "capability_claim": "payment_execute", "window_spent_cents": 0, "action": { "type": "refund", "amount_cents": 14250, "currency": "USD", "target_system": "payments_processor" }, "policy": { "id": "refunds-tier-1", "version": "1", "window": "per_day", "window_cap_cents": 500000, "rules": { "refund": { "required_capability": "payment_execute", "review_above_cents": 40000, "hard_block_above_cents": 200000 } } } }' \ | tee receipt.json | jq .decision # 2. verify it (expect ok: true) jq '{receipt: .receipt}' receipt.json | curl -s https://agentguard.run/api/verify \ -H "content-type: application/json" -d @- | jq . # 3. tamper: change the amount, verify again (expect ok: false) jq '{receipt: (.receipt | .action.amount_cents = 1)}' receipt.json | curl -s https://agentguard.run/api/verify \ -H "content-type: application/json" -d @- | jq .
04 · Browser-signed simulation · throwaway key
Exercise a refund policy without moving money.
What it proves: the SDK's signing scheme end to end in your tab (canonical JSON, a SHA-256 entry hash, an Ed25519 signature, a previous-hash link) and that one edited field breaks it. A throwaway key signs here, so this is a simulation, not an AgentGuard receipt.
Drive it: allow a small request, push it past the $500/day cap, try a payment_execute action while declaring read_only, or engage the zero-cap stop. When the simulation exceeds the cap or declares insufficient capability, the page signs a BLOCK record with its throwaway key.
refund-agent → refund order #4471 $40.00 ✓ SIMULATED ALLOW · no payment sent · $40.00 of $500.00 demo spend used receipt seq 0 signed · ed25519 60422f897d··3b7900 · signer a82e181a03e2181b refund-agent → refund order #4472 $120.00 ✓ SIMULATED ALLOW · no payment sent · $160.00 of $500.00 demo spend used receipt seq 1 signed · ed25519 2a4d84ed96··101a02 · signer a82e181a03e2181b refund-agent → refund order #4480 $1,122.41 ✗ BLOCK · Cap 'per_day=50000c' exceeded: $160.00 spent + $1,122.41 requested > $500.00 receipt seq 2 signed · ed25519 24778a9bbe··ba430f · signer a82e181a03e2181b
Example chain, signed Oct 28 2026 with this page's scheme by a throwaway key since discarded. Press an action to sign your own.
- decisionId
- ag_4480_2
- action
- BLOCK
- projected
- $1,122.41
- window after
- $160.00
- previousHash
- 272bcfc9fc0c…c7d985
- entryHash
- e9e9bd195c01…bb9d6b
- signer
- a82e181a03e2181b
- signature
- 24778a9bbe45…72ba430f
The simulation and signing run in your browser. The page code does not transmit its action metadata or private key. It loads cryptographic modules from jsDelivr. If you open a record on /verify, the signed chain and public key are placed in the AgentGuard URL for a separate browser verification; the private key is not. A record shows integrity of simulated metadata under the displayed throwaway public key, not that a refund or human approval occurred. This session's signer public key: generated in your browser when the page loads
Also on file: the same brakes in a terminal. The SDK's recorded deterministic run blocks $96.00 of projected API spend, refuses a simulated $9,000.00 wire for insufficient capability, applies a zero-cap policy, verifies the signed chain and detects a tampered receipt.
05 · Hosted sandbox · signed metadata evaluation
Watch the gate decide: allow, hold, refuse.
What it proves: the hosted gate evaluates a policy against declared metadata and signs every result, refusals included, and each receipt verifies at /api/verify.
Each action sends a fixed demo policy, caller-declared capability, and action metadata to /api/gate/authorize. The endpoint evaluates those supplied fields and signs the result. No payment provider is called. The three results beside this came from this page's fixture on Oct 28 2026 at 20:30 UTC; press an action for a fresh one.
The gate signs an evaluation of browser-supplied metadata. It does not authenticate those fields or implement expiry, replay, or idempotency checks. Allow, escalate, and block results produce standalone Ed25519 receipts. The signature protects the recorded fields from later editing; it does not prove human authorization.
support-agent → refund order #4471 · $142.50 ✗ BLOCK · capability_insufficient · provider never called receipt rcpt_3884a4fa3de94ae755daa2f5 signed · ed25519:67b9e3dc··96b60705 · 20:30:22 UTC demo UI → caller-declared capability is now payment_execute this is browser state, not an authenticated or signed human authorization. retrying the pending action support-agent → refund order #4471 · $142.50 ✓ ALLOW · within_policy · $142.50 reserved · provider may be called receipt rcpt_824a22b0b408fa98926de0cc signed · ed25519:22f6b795··2749e304 · 20:30:22 UTC support-agent → vendor payout · $620.00 ⏸ ESCALATE · amount_above_review_threshold · held for human review · money does NOT move receipt rcpt_bb4d2d73051b78e4a7e8d40e signed · ed25519:c1a7ba7d··da48a206 · 20:30:23 UTC
- decision
- ESCALATE held for a person under the policy
- reason
- amount_above_review_threshold
- amount
- $620.00 payout
- capability
- payment_execute required payment_execute claimed
- policy_hash
- 3575f1f589…
- signer_key
- 89c94118e046da63
- signature
- c1a7ba7d7d··da48a206
// wrap the one function that moves money import { SpendGuard } from "@agentguard-run/spend"; const guard = new SpendGuard({ policy, signingKeys }); // the agent calls refund() instead of the raw Stripe call. // dispatchToolCall evaluates configured policy before it calls Stripe. // A block or escalation throws AgentGuardBlockedError, so this executor is not called. const refund = (orderId, amount) => guard.dispatchToolCall( { scope: "money:refund", toolName: "refund_customer", toolArgs: { orderId, amount } }, (args) => stripe.refunds.create(args) );
# wrap the one function that moves money from agentguard_spend import SpendGuard guard = SpendGuard(config) # dispatch_tool_call raises AgentGuardBlockedError when tool policy refuses. async def refund_customer(order_id, amount): return await guard.dispatch_tool_call( {"scope": "money:refund", "toolName": "refund_customer", "toolArgs": {"order_id": order_id, "amount": amount}}, lambda args: stripe.Refund.create(**args), )
The bypass condition
Keep the raw provider client outside the agent's tools. Expose the guarded function.
"If it's just an SDK, can't the agent route around it?" Fair question. dispatchToolCall invokes the executor only after the configured policy approves, so the protection depends on your application not exposing a bypass path: a production integration must keep raw side-effecting clients outside the agent tool registry. With signing configured, AgentGuard records the request metadata and policy result in a content-free receipt whose signed fields can be checked later. It does not custody funds or proxy provider traffic.
Your agent talks to customers or moves money? The same gate works for a voice, SMS or chat agent: request a pilot or hear a storage call.
06 · Attestation walkthrough · runnable example
A broken link denies the payment capability.
What it proves, in the runnable example: a three-agent receipt DAG gates the payer's ADMIN capability on an unbroken chain (composite trust at least 0.82, depth at least 2). Alter the planner's receipt and the gate returns HASH_INVALID. The browser shows two fixed states and signs nothing.
Run the real cryptographic path locally with npm run demo:attestation in @agentguard-run/spend: it generates a customer-runtime key, signs complete DAG nodes, verifies them and calls the capability gate. It does not call a payment provider.
The fixed browser fixture carries step, capability, trust and truncated hash metadata, but no signatures or public key.
07 · Integration verdicts · tested installs
Where it binds, and where it only advises.
What it proves: each verdict comes from a tested install with signed receipts, and the posts say plainly when a host cannot be hard-bounded.
Each post uses a fresh install, runs real decisions, keeps the receipts and names the limit. Read the "no" and "advisory" ones first; they are the point of the exercise.
- OpenRouteryes
A guarded client wrapper blocks over-cap and under-capability calls before network dispatch, signs receipts locally and verifies offline.
- LiteLLMyes
A client-side integration blocks spend and capability violations before the proxy, signs locally and verifies offline.
- Claude Codepartial
A mandatory local hook for spend caps, destructive-action blocks and signed receipts, with the capability limit stated plainly: no generic capability ceiling.
- Cursorno
MCP gives Cursor signed spend decisions, not a hard interposer or capability ceiling. Real decisions and receipts, no mandatory provider interposition today.
- Gooseadvisory
An MCP decision tool with local spend decisions and signed receipts. MCP alone does not force the host to ask before it acts.
- Buzz agentsadvisory
A decision-tool integration for the buzz-acp harness with signed receipts, and the important limit that MCP is not a mandatory interposer.
What a valid result shows
Scope of these checks
Signature integrity, not external facts. A valid result shows that the signed fields validate under the named public key and have not changed since signing, and it records how a gate evaluated the policy and metadata it was given. It does not establish who supplied those fields, whether an external action occurred, or whether that action was safe, compliant or correct. Hooks allow a call on an error or timeout.