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

STOP · blockedreceipt 16 of 18
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==
Ed25519 signature verified over the canonical payload
Chain link verified: previous is the SHA-256 of receipt 15
Verified with node:crypto when this page was built. Your browser repeats both checks when the page loads.
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.

Final frame of the recorded run: Claude Code launched 15 copies in 36.6 seconds, 3 attempts were refused at the limit and 5 more when the copies tried to start their own; 29,997,622 tokens, $78.21 at API list price, not a bill. Final frame of the recorded run, phone layout: 15 copies launched in 36.6 seconds, 8 attempts refused; 29,997,622 tokens, $78.21 at API list price, not a bill. Open the clip
Final frame of the recorded run, replayed at 16x in the clip: 3 refused at the limit, 5 from sub-agents.
provenanceagentguard-fanout-sep23.provenance.json
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
ALLOW · within_policyrcpt_f7a6137682c8fd39e4387dfb2026-10-28 20:30:21 UTC
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
/api/verify, as minted: ok: true
/api/verify with action.amount_cents changed from 14250 to 1: receipt_signature_invalid
The signature covers the recorded metadata and decision. It shows the fields have not changed since signing, not who supplied them or whether an action occurred.
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.

policy
refund-agent-v1 enforce
capability claim
read_only
daily cap · spent
$160.00 / $500.00
zero-cap stop
READY · simulation active
routine refunds
push the policy
custom refund
$
change local demo state
browser-signed simulation · decision logexample chain · seq 3
refund-agent → refund order #4471 $40.00
  evaluate · capability=read_only · refund $40.00 · cap $500.00/day
  ✓ 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
  evaluate · capability=read_only · refund $120.00 · cap $500.00/day
  ✓ 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
  evaluate · capability=read_only · refund $1,122.41 · cap $500.00/day
  ✗ 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.

BLOCKEDrefund order #4480 $1,122.41 · seq 2example
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
Cap 'per_day=50000c' exceeded: $160.00 spent + $1,122.41 requested > $500.00
↗ Verify chain on /verify
  1. ALLOWEDseq 1 · refund order #4472 $120.00entryHash 272bcfc9fc…c7d985↗ verify
  2. ALLOWEDseq 0 · refund order #4471 $40.00entryHash 98a56666e6…e720ef↗ verify

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.

asciinema cast · GIF, 0.9 MB · verify a receipt of your own

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.

choose demo metadata
change the caller-declared claim
the architecture boundary

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.

hosted sandbox · refund policycapability: read_only · 3 results on file
support-agent → refund order #4471 · $142.50
  POST /api/gate/authorize · capability_claim=read_only · refund $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
  POST /api/gate/authorize · capability_claim=payment_execute · refund $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
  POST /api/gate/authorize · capability_claim=payment_execute · 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
ESCALATErcpt_bb4d2d73051b78e4a7e8d40e2026-10-28 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
Ed25519 signature valid at /api/verify
signer key pinned to AgentGuard (89c94118e046da63)
fixed demo fields contain no customer or payment detail
signed decision field: ESCALATE · held for human · money did NOT move
the signature establishes record integrity, not human authorization or payment execution
Payload-minimized by construction: no customer name, card, prompt, or model output. Only metadata and the decision. This exact object came from /api/gate/authorize.
the guarded executor
// 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.

grantedIllustrated SDK result: the chain verifies (trust 0.88, depth 3). A simulated payment handler would be eligible to run. No payment is sent.
✓gather_invoiceREAD_ONLY
researcher · trust 0.96 · 8cf81f4b…2c36 · signer 0b2e62b029
✓validate_vendorTRANSACT
planner · trust 0.9 · b428cb95…c085 · signer 0b2e62b029
✓payment_executeADMIN
payer · trust 0.88 · 74b1af0a…433b · signer 0b2e62b029

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.

  1. A guarded client wrapper blocks over-cap and under-capability calls before network dispatch, signs receipts locally and verifies offline.

  2. A client-side integration blocks spend and capability violations before the proxy, signs locally and verifies offline.

  3. A mandatory local hook for spend caps, destructive-action blocks and signed receipts, with the capability limit stated plainly: no generic capability ceiling.

  4. MCP gives Cursor signed spend decisions, not a hard interposer or capability ceiling. Real decisions and receipts, no mandatory provider interposition today.

  5. Gooseadvisory

    An MCP decision tool with local spend decisions and signed receipts. MCP alone does not force the host to ask before it acts.

  6. 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.