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.
Metadata
Release files for wai-agent-audit 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| wai_agent_audit-0.1.0.tar.gz | 62.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| wai_agent_audit-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 119.7 kB
Release files / wai_agent_audit-0.1.0.tar.gz
| Download URL | wai_agent_audit-0.1.0.tar.gz |
|---|---|
| Size | 62.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bd58804054ed491a5b46e9ecec784f0b18d84aa4f1a2c1f05ddb8524533dc276
|
|
BLAKE2b-256 checksum How to use checksums |
de8bc20b36faa83e29a7bb84d085f79c9ea8add3be04502a78f825929598e56e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.13
|
Release files / wai_agent_audit-0.1.0-py3-none-any.whl
| Download URL | wai_agent_audit-0.1.0-py3-none-any.whl |
|---|---|
| Size | 56.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
37d71d373eb2746e4b0014dd6cf356d0823304d829147cdafa82da1d00dad29b
|
|
BLAKE2b-256 checksum How to use checksums |
60d2c6aae9f939e9e9f02e535d2cf11801b675e8d5e163488bd54a19dc54bf9e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.13
|