exactly-once
Idempotency middleware for agent side-effects
The primitive agent frameworks forgot. Wrap any tool call that must never fire twice — a payment, an email, an onchain transaction — and exactly-once guarantees it runs a single time, even across retries, crashes, and replays.
See it prevent a double-charge in one command:python examples/crash_mid_payment.py — an agent crashes mid-payment, resumes, and does not double-charge (with vs without).
v0.1 — zero-LLM, tiny surface, drop-in.
Why
Agents retry. They crash and resume. They get replayed during debugging. Every one of those can fire a side-effect twice — a card charged twice, an email sent twice, a transaction submitted twice. In systems that touch money, duplicate side-effects are the difference between a bug and a lawsuit. Frameworks give you retries and checkpoints but not idempotency for the effects those retries cause.
Install
pip install exactly-once # core, zero heavy deps
pip install "exactly-once[redis]" # + Redis store (also: [postgres])
Quickstart
from exactly_once import once, Store, current_key
store = Store.sqlite("effects.db") # or .memory() / .redis(url) / .postgres(dsn)
@once(store, key=lambda order, **_: f"charge:{order.id}")
def charge_card(order):
# pass our key through as Stripe's own idempotency key — belt and suspenders
return stripe.charge(order.customer, order.amount, idempotency_key=current_key())
charge_card(order) # runs the charge
charge_card(order) # replays the stored result — Stripe is NOT called again
# context-manager form for an inline effect
with once(store, key="send-welcome:user-4471") as guard:
if guard.fresh:
guard.result = send_email(...) # skipped on any replay
⚠️ Key on stable business identity (
order_id), never on a mutable value like amount — two distinct $50 charges must not collapse into one.
See it prevent a double-charge in 15 seconds:
python examples/crash_mid_payment.py # agent crashes mid-payment, with vs without
More runnable examples in examples/.
The guarantee (honestly scoped)
Classic idempotency-key pattern adapted for agents: compute a stable key → atomically check-and-claim → if committed, return the stored result without re-running; if in-flight, block/deny per policy; if new, run and commit. On a crash mid-effect the key is left in-flight and quarantined — a half-completed payment must never silently re-fire. Single-writer semantics are strong; multi-writer needs a real transactional store (documented, not overpromised).
Pluggable stores: memory · SQLite · Redis · Postgres. An onchain adapter (dedupe by nonce + calldata-hash) is planned for v0.2. See SPEC.md and ROADMAP.md.
What it guarantees — and what it doesn't
Guarantees (given a store with an atomic claim): the effect is entered at most once per key across retries, concurrent workers, crashes, and replays; after a commit every later call replays the stored result; a concurrent second caller never runs in parallel; a crash mid-effect never auto-re-fires.
Does not: it is exactly-once effect (at-most-once execution + replay-on-success), not exactly-once delivery — that's impossible (Two Generals / FLP). It can't know the outcome of a crash-mid-effect; it refuses to guess (quarantine) and lets a prober or a provider idempotency key narrow the window. It's only as strong as the store you pick:
| Store | Guarantee | Use for |
|---|---|---|
| memory | strong within one process | tests, dev |
| SQLite | strong on one host | single-node agents, jobs, CI |
| Redis | strong single-instance, best-effort under failover | distributed workers, one Redis |
Postgres SERIALIZABLE |
true multi-writer, linearizable | multi-host production |
Full boundary in docs/ARCHITECTURE.md §9.
Development
uv venv --python 3.11 && uv pip install -e ".[dev]"
uv run pytest # full suite (Redis/Postgres tests need Docker)
uv run mypy src/exactly_once # strict typing
uv run ruff check src tests examples # lint
uv run python scripts/check_docs_honesty.py # the docs-honesty gate
uv run python scripts/benchmark.py # per-call overhead (NFR-8)
Overhead per guarded call is one store round-trip plus key/codec work — a few microseconds on the in-memory store; real deployments are dominated by the store's own latency.
Part of the Swarm Proof toolkit
Trust infrastructure for the agent economy — seven projects, one thesis.
| Project | What it does |
|---|---|
| stampede | Point a herd of realistic agents at your system before real ones arrive |
| mockworld | A synthetic internet for agents — fake Stripe, Gmail, exchange, instantly |
| mcp-probe | The CI quality suite for MCP servers — lint, contract-test, benchmark, load |
| costbomb | Denial-of-wallet fuzzing — find the inputs that make your agent spend $500 |
| exactly-once ← you are here | Idempotency middleware so agent side-effects fire once |
| agent-postmortems | A structured incident database + post-mortem standard for agent failures |
| awesome-agent-reliability | The curated map of the field |
License
MIT. No LLM anywhere — it's plumbing. Citable via CITATION.cff.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file exactly_once-0.2.0.tar.gz.
File metadata
- Download URL: exactly_once-0.2.0.tar.gz
- Upload date:
- Size: 97.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a96259f9dc8b92992cd108e056366d78f0f66e7d4fd7bfcf2c4b3973264cc373
|
|
| MD5 |
2207fe70811d5d2ca6006e020bdde608
|
|
| BLAKE2b-256 |
48c98dd626107d0a223b42c2728584d00c6f862d8216a134e6eda3196ee15982
|
Provenance
The following attestation bundles were made for exactly_once-0.2.0.tar.gz:
Publisher:
release.yml on swarmproof/exactly-once
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
exactly_once-0.2.0.tar.gz -
Subject digest:
a96259f9dc8b92992cd108e056366d78f0f66e7d4fd7bfcf2c4b3973264cc373 - Sigstore transparency entry: 2625197627
- Sigstore integration time:
-
Permalink:
swarmproof/exactly-once@7ad43b640efa5837ac4fea1ae1a1f2111ffe3322 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/swarmproof
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7ad43b640efa5837ac4fea1ae1a1f2111ffe3322 -
Trigger Event:
release
-
Statement type:
File details
Details for the file exactly_once-0.2.0-py3-none-any.whl.
File metadata
- Download URL: exactly_once-0.2.0-py3-none-any.whl
- Upload date:
- Size: 40.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
231f016c3f48d378afb59d522e193d6c02a52369333398db6ebc9215ce7f1af4
|
|
| MD5 |
994f28c78f8cd03e49d172b0044e3e92
|
|
| BLAKE2b-256 |
9bbd19d0c23fc65fd43d7b9c9f17b55951d1aff291133d902f95e5a2ebb426e0
|
Provenance
The following attestation bundles were made for exactly_once-0.2.0-py3-none-any.whl:
Publisher:
release.yml on swarmproof/exactly-once
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
exactly_once-0.2.0-py3-none-any.whl -
Subject digest:
231f016c3f48d378afb59d522e193d6c02a52369333398db6ebc9215ce7f1af4 - Sigstore transparency entry: 2625198146
- Sigstore integration time:
-
Permalink:
swarmproof/exactly-once@7ad43b640efa5837ac4fea1ae1a1f2111ffe3322 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/swarmproof
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7ad43b640efa5837ac4fea1ae1a1f2111ffe3322 -
Trigger Event:
release
-
Statement type: