Skip to main content

Agent Receipts

Two AI agents negotiate a real foreign-exchange trade. Every message they exchange — and every tool call they make — passes through a receipt middleware that they don't know exists. It hashes the payload, signs a small receipt with each party's own Ed25519 key, and writes it to three copies of a ledger. Edit any past row in any copy and a standalone verifier catches it. That's the whole project: tamper-evident receipts for agent interactions (triple-entry accounting, backing an academic paper).

Use it on your own agents

pip install agent-audit-sdk

Declare your agents, wrap a boundary, and every interaction across it is receipted. Two ways to run the same front:

from agent_audit import Agent, Receipts

receipts = Receipts(
    agents=[Agent("planner", planner_seed), Agent("researcher", researcher_seed)],
    ledger_path="receipts.db",
)
front = receipts.a2a(
    upstream="http://researcher:9000", caller="planner", callee="researcher"
)

# (a) as an endpoint — point the planner's researcher URL at :9102 and
#     change nothing inside either agent
await receipts.serve((front, 9102))

# (b) in your own app — mount it, and drive the ledger worker from your
#     lifespan with `async with receipts.running():`
app.mount("/researcher", front.build_app())

receipts.mcp(...) does the same for an agent-to-tool boundary. Seeds are 32-byte hex — python -m scripts.gen_signing_seeds prints a set.

Two agents, no LLM, no framework, end to end in one command:

python -m examples.two_agents
planner  -> researcher : research_request(topic='container throughput')
researcher -> planner  : container throughput looks stable this quarter

3 receipts recorded - neither agent did anything to make this happen:

  seq 1  a2a_message      planner -> researcher  research_request  proof OK
  seq 2  a2a_message   researcher -> planner     findings          proof OK
  seq 3  task_summary  researcher -> planner     completed         proof OK

  the question itself is not in the ledger: True
  every receipt signed by both parties: True
  three copies, all agreeing: [3, 3, 3] True

  VERDICT: MATCH

Recording is never something an agent opts into. It happens at the transport boundary, which is why there is no record_receipt tool: an agent that could choose to call it could choose not to, and self-reported bookkeeping is the thing triple-entry exists to replace.

Reading back is a separate, read-only surface — in process (TransparencyService), over HTTP (transparency_service.main, plus a console at /ui), as a CLI (python -m verifier), or as MCP tools an agent can audit with:

python -m agent_audit.mcp_server   # get_receipts · get_tasks · verify_ledger · get_anchors

The scenario

  • Buyer (Claude Agent SDK) wants to buy €10,000 with USD, and will tolerate at most 80 bps of spread.
  • Seller (CrewAI) is an FX broker with a 20 bps floor. It prices quotes off the real ECB reference rate, fetched live from the Frankfurter API through a custom MCP server — no mocked data.
  • They negotiate over the A2A protocol, up to 3 rounds. Deal or no-deal, every turn is receipted.

Architecture

buyer ──A2A──▶ ┌────────────────────┐ ──A2A──▶ seller
               │ receipt middleware │
   seller ──MCP──▶ │  :9102 A2A front   │ ──MCP──▶ frankfurter (ECB rates)
               │  :9201 MCP front   │
               └─────────┬──────────┘
                         │ per interaction: hash payload → sign twice → append
                         ▼
               ledger.db (SQLite): sender_copy | receiver_copy | neutral_copy
                         ▲                 each row: prev_hash + entry_hash chain
                         │
                  verifier (standalone, read-only)

The middleware is a transparent proxy. Agents are pointed at it purely by env vars (SELLER_URL, FX_MCP_URL) — zero agent code changes, which is the point: it must work on agents whose internals you don't control. A receipt stores only the payload's SHA-256, never the payload, so the schema works for any future interaction type.

Run it

docker compose up -d --build                                  # 5 services
docker compose exec buyer python -m scripts.run_negotiation   # one negotiation
docker compose exec middleware python -m scripts.export_public_keys  # once
docker compose exec middleware python -m verifier             # check the ledger

(Bare-metal, one terminal per service: see docs/running.md.)

What you should see

The negotiation prints a deal (with the agreed quote) or no_deal. The ledger (ledger-data/ledger.db, browsable with any SQLite tool) then holds one receipt per interaction, all tied to one task id:

seq 1: a2a_message    request_quote  buyer->seller
seq 2: mcp_tool_call  get_ecb_rate   seller->frankfurter-mcp
seq 3: a2a_message    quote          seller->buyer            task=35cc9c59
seq 4: a2a_message    counter        buyer->seller            task=35cc9c59
seq 5: a2a_message    accept         seller->buyer            task=35cc9c59
seq 6: task_summary   deal           seller->buyer            task=35cc9c59

And the verifier reports:

  sender_copy      6 rows   chain OK   signatures OK
  receiver_copy    6 rows   chain OK   signatures OK
  neutral_copy     6 rows   chain OK   signatures OK
  cross-copy     counts OK, contents identical, stored hashes identical

VERDICT: MATCH

Prove the tamper-evidence

Flip one field in one copy with any SQLite tool, rerun the verifier:

  receiver_copy    6 rows   chain BROKEN at seq 3   2 signature failure(s)
VERDICT: MISMATCH

SQLite being editable is fine — the guarantee is that tampering is detectable, not impossible. Detection is the product.

Where things live

Path What
agent_audit/ The published SDKAgent, Receipts, and the read-only MCP server
receipt_middleware/ Proxy fronts, receipt schema, signing, spool, ledger, anchoring
transparency_service/ Query + verify over HTTP
frontend/ The console served at /ui
verifier/ Standalone verifier (imports only the receipt schema)
agents/buyer, agents/seller The two negotiating agents (the demo)
custom_mcps/frankfurter/ MCP server wrapping the ECB rates API
config/ All env vars and fixed demo constants
docs/technical-spec.md Every design decision, with the why

The demo runs on the SDK rather than beside it — receipt_middleware/main.py builds the same Receipts object the snippet above does, so the published surface is the one exercised every day.

Honest caveat (stated in the spec too): this POC is single-operator — one middleware holds all keys and writes all three copies. Real multi-party non-repudiation needs independently operated middlewares; that and public-chain anchoring of the neutral copy's root hash are the next steps.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wai_agent_audit-0.1.0.tar.gz (62.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wai_agent_audit-0.1.0-py3-none-any.whl (56.9 kB view details)

Uploaded Python 3

File details

Details for the file wai_agent_audit-0.1.0.tar.gz.

File metadata

  • Download URL: wai_agent_audit-0.1.0.tar.gz
  • Upload date:
  • Size: 62.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.13

File hashes

Hashes for wai_agent_audit-0.1.0.tar.gz
Algorithm Hash digest
SHA256 bd58804054ed491a5b46e9ecec784f0b18d84aa4f1a2c1f05ddb8524533dc276
MD5 97b3e6e4c886cd00b92083b0664002c5
BLAKE2b-256 de8bc20b36faa83e29a7bb84d085f79c9ea8add3be04502a78f825929598e56e

See more details on using hashes here.

File details

Details for the file wai_agent_audit-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for wai_agent_audit-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 37d71d373eb2746e4b0014dd6cf356d0823304d829147cdafa82da1d00dad29b
MD5 4ed6fea5f4fffd89938732929bc9afe8
BLAKE2b-256 60d2c6aae9f939e9e9f02e535d2cf11801b675e8d5e163488bd54a19dc54bf9e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page