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 SDK — Agent, 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd58804054ed491a5b46e9ecec784f0b18d84aa4f1a2c1f05ddb8524533dc276
|
|
| MD5 |
97b3e6e4c886cd00b92083b0664002c5
|
|
| BLAKE2b-256 |
de8bc20b36faa83e29a7bb84d085f79c9ea8add3be04502a78f825929598e56e
|
File details
Details for the file wai_agent_audit-0.1.0-py3-none-any.whl.
File metadata
- Download URL: wai_agent_audit-0.1.0-py3-none-any.whl
- Upload date:
- Size: 56.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
37d71d373eb2746e4b0014dd6cf356d0823304d829147cdafa82da1d00dad29b
|
|
| MD5 |
4ed6fea5f4fffd89938732929bc9afe8
|
|
| BLAKE2b-256 |
60d2c6aae9f939e9e9f02e535d2cf11801b675e8d5e163488bd54a19dc54bf9e
|