This is a deterministic inventory demo using the official
A2A Python SDK HTTP+JSON 1.0 binding.
The client discovers the public Agent Card, then calls SendMessage with
POST /message:send. WIDGET-001 always has 42 units.
From this repository's python/ directory, with Python 3.11 or newer:
uv sync --extra demo
uv run --extra demo pytest tests/test_stock_demo.pyThe tests use HTTPS ASGI requests, generated keys, temporary SQLite databases and a simulated registry.
Use one shell to configure a local test identity, then run the server. The generator creates a buyer key, its public key, and a separate localhost TLS key/certificate valid for two days. It refuses to write credentials inside this repository or overwrite existing files.
stock_demo_dir="$(mktemp -d)"
uv run --extra demo python -m examples.stock.local_setup "$stock_demo_dir"
export AITHOS_STOCK_MODE=local
export AITHOS_STOCK_BASE_URL=https://localhost:8443
export AITHOS_STOCK_LOCAL_PUBLIC_KEY="$stock_demo_dir/buyer.pub.pem"
export AITHOS_STOCK_NONCE_DB="$stock_demo_dir/nonces.sqlite3"
export AITHOS_STOCK_REQUIRED_DOMAINS=supplier.example
export AITHOS_STOCK_KEY_ID=local-stock-buyer
export AITHOS_STOCK_PRIVATE_KEY="$stock_demo_dir/buyer.key.pem"
export AITHOS_STOCK_CA_FILE="$stock_demo_dir/server.cert.pem"
uv run --extra demo uvicorn examples.stock.server:app_from_env --factory \
--host 127.0.0.1 --port 8443 --no-proxy-headers \
--ssl-keyfile "$stock_demo_dir/server.key.pem" \
--ssl-certfile "$stock_demo_dir/server.cert.pem"In another shell, set AITHOS_STOCK_PRIVATE_KEY and AITHOS_STOCK_CA_FILE
to the paths printed above and AITHOS_STOCK_KEY_ID=local-stock-buyer, then:
uv run --extra demo python -m examples.stock.client --sku WIDGET-001The response is an SDK-parsed A2A SendMessageResponse whose message contains
{"sku": "WIDGET-001", "quantity": 42, "mock": true, ...}. TLS is verified
against the explicitly trusted test certificate. The local resolver asserts
supplier.example only as a fixture; it is not a registry domain certification.
The local files are throwaway development credentials. Retain the nonce database while the same keys/server remain in use, including across server restarts.
The default server mode is registry, using AithosResolver() and
https://registry.aithos.world. Configure your own registered buyer's key
without copying a real private key into this repository:
export AITHOS_STOCK_MODE=registry
export AITHOS_STOCK_BASE_URL=https://your-stock-server.example
export AITHOS_STOCK_ALLOWED_AGENTS='YOUR_REGISTERED_AGENT_ID'
export AITHOS_STOCK_REQUIRED_DOMAINS=your-verified-domain.example
export AITHOS_STOCK_NONCE_DB=/persistent/shared/stock-nonces.sqlite3
export AITHOS_STOCK_KEY_ID='YOUR_REGISTERED_AGENT_ID:YOUR_KEY_THUMBPRINT'
export AITHOS_STOCK_PRIVATE_KEY=/secure/path/to/your-buyer-key.pem
unset AITHOS_STOCK_CA_FILERun the same Uvicorn factory with your server TLS certificate and key.
AITHOS_STOCK_BASE_URL is trusted deployment configuration and must match
the externally visible HTTPS origin. This example serves at the origin root;
it does not configure proxy path rewriting. Set it on the client too.
Do not expose the explicit local fixture mode as a public identity service.
Optional server settings:
| Variable | Default / effect |
|---|---|
AITHOS_STOCK_REGISTRY_ORIGIN |
The official registry; can be another HTTPS registry origin |
AITHOS_STOCK_ALLOWED_AGENTS |
*, or comma-separated agent identifiers |
AITHOS_STOCK_REQUIRED_DOMAINS |
supplier.example; comma-separated required domains |
AITHOS_STOCK_DOMAIN_OPERATOR |
or, or and |
AITHOS_STOCK_NONCE_DB |
.aithos-auth/stock-nonces.sqlite3 |
All instances accepting the same signed requests must share durable replay state. A SQLite file on one host can serve multiple workers; deployments across hosts need a shared atomic nonce store satisfying the library contract.
server.py serves /.well-known/agent-card.json publicly and mounts every
SDK operation behind the A2A activation adapter and AithosAuthMiddleware.
The adapter uses the SDK's ExtensionSupportRequiredError serialization for
missing activation (400), then the middleware enforces identity, domain
policy and replay protection (401/403). Successful responses include
A2A-Extensions, preserving other extensions already activated by the server.
Unsupported operations still pass through activation and authentication.
client.py uses the SDK's A2ACardResolver and RestTransport.
Its HTTPX request hook adds A2A-Extensions, A2A-Version and the A2A media
type before signing the final request.content bytes. It refuses another
origin and disables redirects. The exact prepared request is then sent by
HTTPX, with no JSON reserialization after signing.
The end-to-end tests cover public discovery, SDK SendMessage, signed body and header tampering, unknown keys, replay across a server restart, domain denial, missing activation, extension response headers and registry key withdrawal.