SESTER — Metering, Quota, Fail-Closed Policy & Verifiable Receipts for AI-Agent APIs
🪙 "If your agent is going to pay, you set the rules." Sester plugs x402-style metering, quotas, fail-closed spend policy and a tamper-evident hash-chain receipt ledger into your API — as one ASGI middleware, with zero required dependencies.
What it does, in one paragraph: every paid request passes through a 402 →
payment → receipt handshake; spend is metered in integer minor units against
per-agent daily quotas; a fail-closed policy engine (host allow-lists, time
windows, escalation to human approval) decides before your handler runs; and
every decision and charge lands in a hash-chained ledger that third parties can
verify with nothing but sha256. Four agent-commerce protocols — x402,
AP2, ACP, UCP — compile onto one wire-format core
(ChargeIntent/ChargeReceipt), so adding a fifth protocol is one adapter, not
a rewrite.
Why fail-closed matters
Sester is built on one doctrine: every failure path denies spend. A corrupt
policy file → DENY_ALL. An unknown payment scheme → 402. A tampered evidence
bundle → loud RED, never a receipt. A facilitator outage → 402, not a free call.
Silent fallbacks (except: pass) are treated as security bugs — the test suite
pins them. If you are metering money, "fail open" is not a mode, it is a leak.
3-minute demo
# 0) Install (demo extras pull in FastAPI + uvicorn)
pip install "sester[demo]"
# 1) Start the demo API (port 8402)
uvicorn sester.demo_api:app --port 8402
# — or, one command with Docker —
# docker run --rm -p 8402:8402 ghcr.io/goun7/sester-demo:latest
2) Prepare a payment envelope (HMAC issuer)
python -m sester.demo_api --mac f1-telemetry:n1:0.05 /weather
3) Send the paid request (paste the envelope from step 2)
curl -H "X-Sester-Agent: f1-telemetry"
-H "X-Payment: pugio0 f1-telemetry:n1:0.05:"
"http://127.0.0.1:8402/weather?city=istanbul"
4) Open the panel: http://127.0.0.1:8402/panel
The flow: `curl` without payment → **402 + `X-Payment-Required` challenge**;
with payment → **200 + `X-Sester-Receipt`** evidence header; the 5th call →
**402 quota exceeded**; `/panel` shows who called, how much, and a live
chain-integrity badge.
## Feature map (by version)
| Area | What you get | Since |
|---|---|---|
| Metering middleware | x402-style 402 handshake, per-request pricing, quota in integer minor units | v0.1 |
| Policy engine | First-match DSL, host allow-lists, time windows, fail-closed defaults | v0.1 |
| Evidence ledger | Hash-chained SQLite (Postgres parity) receipts, HMAC-sealed, `verify_chain()` | v0.1 |
| Settlement | x402 v2 verify/settle facilitator with injectable transport, fail-closed on outage | v0.2 |
| Protocols | AP2 mandates (`AP2-Mandate`), ACP checkout sessions (`ACP-Session`), UCP web-monetization (`UCP-Checkout`) → one core | v0.2–0.4 |
| Human approval | `then: escalate` → 402 escalation ticket, one-time consumption, TTL expiry, `/approvals` panel | v0.2 |
| Signed mandates | RFC 7515 JWS: HS256 (stdlib) + ES256 (`[jws]`), body-binding, alg-allowlist | v0.3 |
| Signed policy | Owner-signed policy envelopes; tightening is instant, loosening is delayed 24 h | v0.3 |
| Postgres | Identical hash-chains across SQLite/PG (same secret + events → same head) | v0.3 |
| Migration | SQLite → PG hash-preserving replay, nonces preserved, `--plan/--dry-run/--verify` | v0.3.1 |
| On-chain batches | Pure-stdlib keccak-256, Merkle root recomputable in EVM, ABI `settle(...)` calldata, non-custodial | v0.4 |
| Minor-unit column | `amount_minor` with hash-preserving migration — quota decisions end-to-end integer | v0.4 |
| Hosted facilitator | FastAPI service: verify/settle/refund + seller metering (free band + 1% + $0.005) | v0.5 |
| Observability | `GET /metrics` — Prometheus-text counters (requests, charges, replay/quota/rate 402s, chain-valid gauge) | dev |
| Burst limiting | Per-agent token-bucket (`burst_capacity`, `burst_refill_per_sec`) — independent of the daily quota | dev |
| Evidence webhooks | HMAC-signed delivery of charge/settlement events with receiver-side verification, retry+backoff, ledger failure-log | dev |
| Evidence export | External-verifier bundles — anyone can audit with `sha256` alone, no Sester installed | v0.3+ |
## Protocol adapters
| Protocol | Header | Envelope | Signature |
|---|---|---|---|
| x402 (HMAC compat) | `X-Payment` | `pugio0 agent:nonce:amount:mac` | HMAC-SHA256 |
| x402 v2 (EVM) | `X-Payment` | EIP-3009/EIP-712 exact scheme | wallet address = agent identity |
| AP2 | `AP2-Mandate` | base64 JWS mandate | HS256/ES256, body-binding |
| ACP | `ACP-Session` | base64 JWS checkout session | JWS + vendor-side issuing |
| UCP | `UCP-Checkout` | base64 JWS web-monetization | vendor-sealed, merchant-bound |
All four compile to the same `ChargeIntent`/`ChargeReceipt` core — one shared
wallet means one shared quota. Register your own via `register_scheme` /
`ProtocolAdapter`.
## Test suites & acceptance runs
```bash
.venv/bin/python -m pytest tests/ -q # full suite: policy, ledger, middleware, EVM schemes,
# evidence, facilitator, protocol adapters, escalation,
# JWS, signed policy, UCP, settlement, minor units,
# payee registry, S6 joint acceptance — 200+ test legs
python scripts/s1_dogfood.py # S1 acceptance scenario → KABUL (accepted)
python scripts/dogrula.py adoption/s1-kanit-bundle.json # receiver side — no Sester needed
Cross-repo bridge tests (
tests/test_bridges_crossrepo.py) run external evidence receivers end-to-end via subprocess (clean → ACCEPT, tampered → RED). They skip automatically when the counterpart repos are absent — the embedded pure-stdlib mirrors inbridge_receivers/pin the same wire contract in every run.
Documentation
| Document | Contents |
|---|---|
docs/K0_SHARED_ENVELOPE_SPEC.md |
Shared evidence-envelope wire contract (external anchors, audit feeds) |
| Architecture decision records | Scope, doctrine, deliberate limits — see the repository's decision documents |
| Policy DSL specification | Semantics + test-vector discipline — see the repository's spec documents |
| Execution plan | Acceptance milestones (S1–S6) — see the repository's plan documents |
docs/PUBLICATION_CHECKLIST.md |
Release gate — exactly what ships and what remains |
CONTRIBUTING.md |
Hard rules for PRs (fail-closed, K0-frozen, test-first) |
SECURITY.md |
Reporting policy — replay/quota/signature-bypass bugs |
Deliberate v0 limits
Non-goals by decision, not by omission (see the repository's decision records): transaction signing stays out of the library (non-custodial — the signing party is yours), live PSP certification is pending real-world traffic, and the CLI surface is minimal by design. Everything else on the roadmap through v0.5.0 is implemented and gated by the test suite above.
Docker (zero-setup demo)
The demo API — 402 challenge, HMAC/EVM/UCP payment flows, live panel — ships as a single image. All state lives in an isolated SQLite ledger inside the container; mount a volume to keep receipts across restarts.
docker run --rm -p 8402:8402 ghcr.io/goun7/sester-demo:latest
# persistent ledger:
docker run --rm -p 8402:8402 -v "$PWD/ledger:/data" \
-e SESTER_DEMO_LEDGER_DB=/data/sester-demo.sqlite3 \
ghcr.io/goun7/sester-demo:latest
Then run the curl flow above against http://127.0.0.1:8402.
Links
- PyPI: https://pypi.org/project/sester/
- Changelog:
CHANGELOG.md· Release notes per version underdocs/RELEASE_NOTES/ - Roadmap: metrics / burst-limit / evidence webhooks shipped in v0.6.0; PSP adapters, optional transaction signing and hosted-traffic hardening are tracked in the repository's roadmap document
- Discussions: https://github.com/goun7/Sester/discussions — integration questions, protocol interop, evidence-bundle verification
License
Apache-2.0 — see LICENSE. The frozen wire fields
(pugio0, pugio_bundle_version, source: "sikke") are kept for
receiver compatibility and are documented in the repository's identity-migration record; visual and
wire identity are separate layers.
Release files for sester 0.6.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 | |
|---|---|---|---|
| sester-0.6.0.tar.gz | 70.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sester-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 153.7 kB
Release files / sester-0.6.0.tar.gz
| Download URL | sester-0.6.0.tar.gz |
|---|---|
| Size | 70.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1f90f69ed63bd4c0ec28767350f8cff60b25dacac3271cd5da214eec30c94d32
|
|
BLAKE2b-256 checksum How to use checksums |
1bd41029187be2d8fc3d4dd97490e9d1e1596e05d63d315f20eb3d43735555de
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / sester-0.6.0-py3-none-any.whl
| Download URL | sester-0.6.0-py3-none-any.whl |
|---|---|
| Size | 83.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
37284a5a78319cafada9a175e0dadfc49c234ad19f2d27fdef4c24061baae078
|
|
BLAKE2b-256 checksum How to use checksums |
e37dd7f94a7407620e132dd6104f268cc7f590efae5c2dcbc233537b935aba27
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|