Skip to main content

Python SDK for the Agent Receipts protocol

Project description

agent-receipts

Python SDK for the Agent Receipts protocol

PyPI License: Apache 2.0 Python CI


Create, sign, hash-chain, store, and verify cryptographically signed audit trails for AI agent actions.

SpecTypeScript SDKReference Implementation


Why receipts?

If you're building with AI agents, you're probably already logging what they do. Receipts go further: they're cryptographically signed, hash-chained records that can't be quietly altered after the fact — and they follow a standard format that works across languages, agents, and systems.

Here's where that matters in practice:

  • Post-incident review — An agent ran overnight and something broke. The receipt chain shows exactly which actions it took, in what order, and whether each succeeded or failed — with cryptographic proof the log hasn't been tampered with after the fact.

  • Compliance and audit — Regulated environments require evidence of what systems did and why. Receipts are W3C Verifiable Credentials with Ed25519 signatures, giving auditors a tamper-evident trail they can independently verify.

  • Safer autonomous agents — Agents can query their own audit trail mid-session. Before taking a high-risk action, an agent can check what it has already done and whether previous steps succeeded, enabling self-correcting workflows.

  • Multi-agent trust — When agents collaborate, receipts serve as proof of prior actions. Agent B can verify that Agent A actually completed step 1 before proceeding to step 2, without trusting a shared log.

  • Usage tracking — Every action is classified by type and risk level, giving you a structured breakdown of what agents spent their time on.

Beyond local storage

The protocol is designed for receipts to travel — publishing to a shared ledger, forwarding to a compliance system, or exchanging between agents as proof of prior actions. Receipts are portable W3C Verifiable Credentials, but where they go is always under the user's control.


Install

The Python SDK is on PyPI:

pip install agent-receipts

Receipts are signed by a separate agent-receipts-daemon process, which also ships the agent-receipts CLI (including the verify command). Install it on Linux or macOS (Windows is not supported yet) — see the Daemon Setup guide.

Quick start

Agent Receipts are signed by a separate agent-receipts-daemon process, not by your application. Your code sends tool-call events over a local Unix socket; the daemon holds the Ed25519 signing key, builds the receipt, signs it, and appends it to the hash-chained store. The signing key never enters your process — so the audit trail holds up even if the agent is compromised. This is the canonical deployment shape (ADR-0022) and the first thing you should reach for. To learn the receipt API without a daemon, see the in-process appendix.

1. Start the daemon

Generate the signing key once, then run the daemon. It listens on the per-OS default socket — see the Daemon Setup guide for socket paths and running it as a service.

agent-receipts-daemon --init   # one-time: creates the signing key
agent-receipts-daemon          # start the daemon (leave it running)

2. Emit a receipt

DaemonEmitter forwards the tool-call event to the daemon, which constructs, signs, and chains the receipt. By default emit() surfaces transport failure (ADR-0025): an unreachable daemon is logged at DEBUG and raised as EmitTransportError rather than dropped silently, so start the daemon before your app. The call stays non-blocking (bounded by the dial + write timeout); construct the emitter as DaemonEmitter(best_effort=True) to opt into loss-tolerant emission (emit() swallows the error and returns None instead of raising). best_effort is a constructor argument, not an emit() argument.

from agent_receipts import DaemonEmitter

with DaemonEmitter() as e:  # uses AGENTRECEIPTS_SOCKET or the per-OS default
    e.emit(
        channel="my-app",
        tool_name="filesystem.file.read",
        decision="allowed",
    )

3. Verify

agent-receipts verify reads the database directly and confirms hash linkage and signatures — the daemon does not need to be running.

AGENTRECEIPTS_DB=~/.local/share/agent-receipts/receipts.db \
  agent-receipts verify \
  --public-key ~/.local/share/agent-receipts/signing.key.pub

A successful run prints the chain length and confirms the signatures are intact. If you started the daemon with a non-default chain id (AGENTRECEIPTS_CHAIN_ID / --chain-id) or overrode AGENTRECEIPTS_DB or AGENTRECEIPTS_PUBLIC_KEY, pass those same values here — otherwise verify reads the default chain at the default paths.

Action taxonomy

The standardized action taxonomy (action types and risk levels) is defined in the protocol specification. The SDK ships classification helpers: classify_tool_call maps a tool name to an action type and risk level using your mappings, load_taxonomy_config loads those mappings from a JSON config ({ "mappings": [...] }), and resolve_action_type looks up a single action type's metadata.

from agent_receipts import classify_tool_call, load_taxonomy_config

mappings = load_taxonomy_config("taxonomy.json")
result = classify_tool_call("read_file", mappings)
print(result.action_type, result.risk_level)

Delivering receipts to a remote collector

When the agent host and the receipt-storage host differ, sign receipts client-side and POST them to an agent-receipts-collector over HTTPS. This is an enterprise / multi-host shape, not the first-run path — the daemon above is what most adopters want.

agent_receipts.emitters (re-exported at the package top level) provides the building blocks below, all delivering signed AgentReceipt values — the receipts you sign with sign_receipt, shown in the in-process appendix:

  • HttpEmitter — POSTs each receipt to the collector with retry + backoff. Default "sync" mode waits for the collector ack (201, or 409 for a duplicate id); "fire-and-forget" schedules the POST on a background thread and never raises to the caller (call drain() before shutdown for a best-effort flush).
  • WalEmitter — wraps a synchronous inner emitter (HttpEmitter in its default "sync" mode — "fire-and-forget" returns before the POST lands, so the WAL entry is cleared prematurely and the guarantee is lost) with a write-ahead log for at-least-once delivery. Each receipt is recorded before delivery and cleared only on ack, so a failed delivery is retained and re-sent — by replay() (call once at startup to drain a backlog a previous crash left behind) or flush(deadline_ms) (bounded best-effort drain on shutdown). Use FileWal for long-lived compute (survives restart) and MemoryWal for ephemeral compute (Lambda, Cloud Run).
  • CompositeEmitter (fan-out), BufferingEmitter (batching), and InMemoryEmitter (testing) round out the set.
from agent_receipts import (
    AgentReceipt,
    FileWal,
    HttpEmitter,
    HttpEmitterConfig,
    WalEmitter,
)

# Construct once at startup, then drain anything a previous crash left behind.
http = HttpEmitter(HttpEmitterConfig(endpoint="https://collector.example/receipts"))
emitter = WalEmitter(inner=http, wal=FileWal("/var/lib/my-app/wal"))
emitter.replay()


def deliver(receipt: AgentReceipt) -> None:
    emitter.emit(receipt)  # at-least-once: retained in the WAL until acked

Confirm receipts landed with agent-receipts verify (above) against the collector's store.

Signing with AWS KMS (production key custody)

Client-side signing needs a private key. Loading raw PEM bytes from an env var or secrets manager keeps an extractable key in process memory — it will not pass most security reviews. The optional aws extra provides a Signer whose key never leaves AWS KMS:

pip install "agent-receipts[aws]"
from agent_receipts.aws import KMSSigner

# keyId: a key ID, key ARN, alias name, or alias ARN. The key must be an
# ECC_NIST_EDWARDS25519 (Ed25519) key with SIGN_VERIFY usage. Credentials come
# from the ambient AWS provider chain (instance role, IRSA, env, profile).
signer = KMSSigner("arn:aws:kms:us-east-1:111122223333:key/abc…", timeout=5.0)

public_key = signer.get_public_key()  # raw 32 bytes (RFC 8032 §5.1.5)

# `sign` operates on the canonical (RFC 8785) bytes of a receipt.
canonical_receipt_bytes = b"...canonicalised AgentReceipt..."
signature = signer.sign(canonical_receipt_bytes)

sign delegates to kms:Sign with SigningAlgorithm=ED25519_SHA_512 and MessageType=RAW (pure Ed25519); the public key is fetched once via kms:GetPublicKey and cached. The key is not extractable, not present in process memory, and revocable via IAM — the production answer to the "Not for production" caveat below. (Wiring the Signer into sign_receipt so it signs canonical receipts end-to-end is tracked separately; this ships the key-custody half.)

Sequential receipt construction (parallel tool calls)

Hash chaining is inherently sequential: receipt N must be fully signed and its hash computed before receipt N+1 is constructed, or the previous_receipt_hash link cannot be formed. A single-threaded agent satisfies this for free, but an agent that fires parallel tool calls (across threads) would race on the shared chain head (sequence + previous_receipt_hash) and produce colliding sequence numbers or a forked chain.

ReceiptChain owns that head and serialises the whole build → sign → hash → link → deliver pipeline under a lock, so concurrent emit() calls are sequenced at the receipt layer even when the tool calls that triggered them ran in parallel. Concurrent emission is not supported as parallel chains in v1 (a future ADR may add forked sub-chains); overlapping calls from other threads block until the in-flight one completes, and the first overlap logs a one-shot warning so the misuse is visible.

from agent_receipts import (
    ChainEmitInput,
    InMemoryEmitter,
    ReceiptChain,
    generate_key_pair,
)
from agent_receipts.receipt.create import ActionInput
from agent_receipts.receipt.types import Issuer, Outcome, Principal

keys = generate_key_pair()

# One ReceiptChain per logical chain (e.g. per agent session). It owns the
# chain head; pass any Emitter — here the in-memory test double, in production
# an HttpEmitter or a WAL-backed emitter.
chain = ReceiptChain(
    chain_id="session-1",
    private_key=keys.private_key,
    verification_method="did:agent:my-agent#key-1",
    emitter=InMemoryEmitter(),
)

# Every emit() is sequenced: receipt N is signed and hashed before N+1 is
# built, even if your tool calls run on parallel threads.
receipt = chain.emit(
    ChainEmitInput(
        issuer=Issuer(id="did:agent:my-agent"),
        principal=Principal(id="did:user:alice"),
        action=ActionInput(type="filesystem.file.read", risk_level="low"),
        outcome=Outcome(status="success"),
    )
)

print(receipt.credentialSubject.chain.sequence)  # 1

The head advances as soon as a receipt is signed and hashed — before delivery — so a transient emitter failure does not fork or stall the chain; wrap a WalEmitter around your transport for at-least-once delivery.

Appendix: in-process signing (tutorial and testing only)

The SDK can also create and sign a receipt entirely in your process, with no daemon. This is useful for learning the receipt API and for unit tests that should not depend on a running daemon.

Not for production. This pattern keeps the signing key inside the agent process. Anyone with code execution in the agent can forge receipts. For real deployments, use the daemon-mediated path shown above (see also the Daemon Setup guide).

Create and sign a receipt

from agent_receipts import (
    ActionInput,
    Chain,
    CreateReceiptInput,
    Issuer,
    Outcome,
    Principal,
    create_receipt,
    generate_key_pair,
    hash_receipt,
    sign_receipt,
)

# Generate an Ed25519 key pair
keys = generate_key_pair()

# Create an unsigned receipt
unsigned = create_receipt(CreateReceiptInput(
    issuer=Issuer(id="did:agent:my-agent"),
    principal=Principal(id="did:user:alice"),
    action=ActionInput(
        type="filesystem.file.read",
        risk_level="low",
    ),
    outcome=Outcome(status="success"),
    chain=Chain(
        sequence=1,
        previous_receipt_hash=None,
        chain_id="chain_session-1",
    ),
))

# Sign and hash
receipt = sign_receipt(unsigned, keys.private_key, "did:agent:my-agent#key-1")
receipt_hash = hash_receipt(receipt)

Verify a receipt and chain

from agent_receipts import verify_chain, verify_receipt

valid = verify_receipt(receipt, keys.public_key)
print(f"Signature valid: {valid}")  # True

# Verify a list of receipts (e.g. [receipt] from the example above)
result = verify_chain([receipt], keys.public_key)
print(f"Chain valid: {result.valid}")
print(f"Receipts verified: {result.length}")
if not result.valid:
    print(f"Broken at index: {result.broken_at}")

What is an Agent Receipt?

A W3C Verifiable Credential signed with Ed25519, recording:

Field What it captures
Action What happened, classified by a standardized taxonomy
Principal Who authorized it (human or organization)
Issuer Which agent performed it
Outcome Success/failure, reversibility, undo method
Chain SHA-256 hash link to the previous receipt (tamper-evident)
Privacy Parameters are hashed, never stored in plaintext

API reference

Receipt creation and signing

from agent_receipts import (
    ActionInput,          # Action fields for CreateReceiptInput
    CreateReceiptInput,   # Input bundle for create_receipt
    create_receipt,       # Build an unsigned receipt from input fields
    generate_key_pair,    # Ed25519 key pair (PEM-encoded)
    sign_receipt,         # Sign with Ed25519Signature2020 proof
    verify_receipt,       # Verify a receipt's signature
)

Hashing and canonicalization

from agent_receipts import (
    canonicalize,         # RFC 8785 JSON canonicalization
    hash_receipt,         # Hash receipt (excluding proof) -> "sha256:<hex>"
    sha256,               # Hash arbitrary data -> "sha256:<hex>"
)

Chain verification

from agent_receipts import (
    verify_chain,         # Verify signatures, hash links, and sequence numbering
)

Types (Pydantic v2 models)

from agent_receipts import (
    AgentReceipt,         # Signed receipt with proof
    UnsignedAgentReceipt,  # Receipt before signing
    Action, ActionTarget, Authorization, Chain,
    CredentialSubject, Intent, Issuer, Operator,
    Outcome, Principal, Proof, StateChange,
)

ActionReceipt / UnsignedActionReceipt remain as deprecated aliases for backwards compatibility — prefer AgentReceipt / UnsignedAgentReceipt.

Subpackage imports

from agent_receipts.receipt import create_receipt, sign_receipt
from agent_receipts.receipt.hash import canonicalize
from agent_receipts.receipt.types import CONTEXT, CREDENTIAL_TYPE

TypeScript SDK compatibility

camelCase aliases are available for users coming from the TS SDK:

from agent_receipts import (
    createReceipt,    # = create_receipt
    generateKeyPair,  # = generate_key_pair
    signReceipt,      # = sign_receipt
    verifyReceipt,    # = verify_receipt
    hashReceipt,      # = hash_receipt
    verifyChain,      # = verify_chain
)

Cross-language compatibility

This SDK produces byte-identical output to @agnt-rcpt/sdk-ts:

  • RFC 8785 canonical JSON matches exactly
  • SHA-256 hashes are identical
  • Ed25519 signatures from either SDK verify in the other

Cross-language compatibility is verified by test vectors generated from the TypeScript SDK.

Project structure

src/agent_receipts/
  receipt/
    types.py       # Pydantic models for all receipt types
    create.py      # Receipt creation with auto-generated IDs
    signing.py     # Ed25519 signing and verification
    hash.py        # RFC 8785 canonicalization + SHA-256
    chain.py       # Chain verification

Development

uv sync --all-extras
uv run pytest              # run tests
uv run ruff check .        # lint
uv run ruff format .       # format
uv run pyright             # type check
Language Python 3.11+
Types Pydantic v2, pyright strict mode
Linting ruff
Testing pytest
Dependencies pydantic>=2.0, cryptography>=41.0

Ecosystem

Component Description
agent-receipts/ar Monorepo: spec, SDKs, daemon, MCP proxy, hook
Python SDK (this package) PyPI
TypeScript SDK npm
Go SDK go get github.com/agent-receipts/ar/sdk/go
agent-receipts-daemon Out-of-process signer + agent-receipts verify CLI (canonical deployment)
agent-receipts/spec Protocol specification, JSON Schemas, canonical taxonomy
ojongerius/attest MCP proxy + CLI (reference implementation)

License

Apache 2.0 — see LICENSE.

Project details


Download files

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

Source Distribution

agent_receipts-0.11.1.tar.gz (160.7 kB view details)

Uploaded Source

Built Distribution

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

agent_receipts-0.11.1-py3-none-any.whl (73.2 kB view details)

Uploaded Python 3

File details

Details for the file agent_receipts-0.11.1.tar.gz.

File metadata

  • Download URL: agent_receipts-0.11.1.tar.gz
  • Upload date:
  • Size: 160.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for agent_receipts-0.11.1.tar.gz
Algorithm Hash digest
SHA256 6a6b4d463d2a94f8e5be6489d133d36cf524dae5ae8b8e16351c9e2f96ef4302
MD5 1f3cc325fafccfae921a58f51692cf27
BLAKE2b-256 08389e3d8657d3c161c196bca2ffbdfe8277de679bd76fcea87b13d5ec8932fd

See more details on using hashes here.

Provenance

The following attestation bundles were made for agent_receipts-0.11.1.tar.gz:

Publisher: release-sdk-py.yml on agent-receipts/ar

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file agent_receipts-0.11.1-py3-none-any.whl.

File metadata

File hashes

Hashes for agent_receipts-0.11.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d77789ac2c14ce176342d0ee34733ebdabdc7139e87531129f9632754e8115d5
MD5 73efc55b65f2f6124f4aaf36e08a0625
BLAKE2b-256 6e7229d836666ff7d993dd603e1d5b16190f72ae36eccb02326ec11b9359d115

See more details on using hashes here.

Provenance

The following attestation bundles were made for agent_receipts-0.11.1-py3-none-any.whl:

Publisher: release-sdk-py.yml on agent-receipts/ar

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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