Agent-to-Agent Receipts
When one AI agent hands work to another, each side needs evidence of the hand-off. With Asqav the sending agent signs the action and passes its receipt along with the request. The receiving agent checks that receipt, then signs an acknowledgment whose counterparty_binding commits to the sender's exact bytes. Either side, or an auditor, can later prove what was sent and what was accepted.
Requires the Python SDK asqav 0.10.12 or later, or the TypeScript SDK @asqav/sdk 0.10.12 or later.
How it works
- Agent A signs.
agent.sign(...)returns a receipt.response.receipt()(TypeScript:receiptOf(response)) gives the signed envelope,{signature_id, payload, signature}, to hand on. - A sends it.
encode_receipt_headerputs it in thex-asqav-receiptheader as one base64 token. - Agent B admits it.
check_peer_receiptorAcceptorMiddlewareverifies the receipt and refuses expired ones, a dropped sequence counter, or an unanswered challenge. The middleware fails closed: a request with no receipt is refused. - B acknowledges.
agent.acknowledge(peer_receipt)signs aprotectmcp:acknowledgmentreceipt bound to A's payload and signature bytes.
Agent A: sign and send
import asqav
from asqav import encode_receipt_header
asqav.init()
agent_a = asqav.Agent.get("agt_sender")
sent = agent_a.sign("a2a:task", {"to": "agent-b", "task": "summarise"})
headers = {"x-asqav-receipt": encode_receipt_header(sent.receipt())}
# send the request to agent B with these headers
Agent B: admit and acknowledge
from asqav import AcceptorMiddleware, check_peer_receipt
# check one receipt yourself...
decision = check_peer_receipt(peer_receipt)
if decision.accepted:
ack = agent_b.acknowledge(peer_receipt)
print(ack.verification_url)
# ...or refuse unreceipted calls at the edge of an ASGI app
app = AcceptorMiddleware(app)
TypeScript
import { checkPeerReceipt, encodeReceiptHeader, receiptOf } from "@asqav/sdk";
// agent A
const sent = await agentA.sign({ actionType: "a2a:task", context: { to: "agent-b" } });
const headers = { "x-asqav-receipt": encodeReceiptHeader(receiptOf(sent)!) };
// agent B
if (checkPeerReceipt(peerReceipt).accepted) {
const ack = await agentB.acknowledge(peerReceipt);
}
What the acknowledgment carries
The acknowledgment's signed payload holds a counterparty_binding:
{
"type": "protectmcp:acknowledgment",
"counterparty_binding": {
"envelope_hash": "base64 SHA-256 of the sender's payload and signature",
"receipt_ref": "sig_...",
"expect_ack_from": "optional key id of the expected acknowledger",
"scope": "envelope_minus_anchors"
}
}
envelope_hash covers exactly the sender's payload and signature, so a changed byte in either breaks it. verify_counterparty_binding(ack, original) (TypeScript: verifyCounterpartyBinding) re-checks it offline and reports matches, mismatch, or kid_mismatch when expect_ack_from names a different acknowledger.
Where the cloud admits it
The cloud signs an acknowledgment only after it resolves the sender's receipt and recomputes the hash. The sender's receipt must be visible to the acknowledging organization: signed in the same organization, or published. A binding the cloud cannot match is refused with counterparty_binding_mismatch or counterparty_origin_unavailable, and nothing is signed.
What this does not claim
The receipts prove which bytes each agent signed and when. They do not prove that agent B did the task well, and they do not make either agent's claims true. Asqav does not implement the A2A protocol; any transport that carries a header works. To reference a receipt from an A2A Agent Card, see A2A Receipt Interop.