exactly-once
Idempotency middleware for AI-agent side-effects. Wrap any tool call that must never fire twice — a payment, an email, an onchain transaction — and it runs at most once per key, replaying its stored result across retries, concurrent workers, crashes, and replays.
This is exactly-once effect (at-most-once execution + replay-on-success) — not exactly-once delivery, which is impossible (Two Generals / FLP). The library is scrupulous about that line; see Guarantees & limits.
The problem
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. Frameworks give you retries and checkpoints, but not idempotency for the effects those retries cause — so you hand-roll dedupe logic, badly, every time.
exactly-once is the missing primitive: two lines that make an unsafe retry safe.
Install
pip install exactly-once # core — zero required dependencies
pip install "exactly-once[redis]" # + a Redis store
# extras: [redis] · [postgres] · [onchain] · [langgraph] · [crewai]
Python 3.11+ · fully typed (py.typed) · no LLM, no model costs.
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
Inline effects use the context manager. Async is identical — async with and async callables have the same semantics:
with once(store, key="welcome:user-4471") as guard:
if guard.fresh:
guard.result = send_email(...) # skipped on every replay
async with once(store, key=f"notify:{event_id}") as guard:
if guard.fresh:
await post_to_slack(...)
⚠️ Key on business identity (
order_id), never a mutable value like amount — two distinct $50 charges must not collapse into one.
See it stop a double-charge in 15 seconds:
python examples/crash_mid_payment.py
It crashes an agent mid-payment, resumes, and shows one charge with @once versus two without — side by side. More runnable examples in examples/.
What you get
- Two-line API — a
@oncedecorator and awith once(...)context manager. Sync and async, identical semantics. - Pluggable stores — memory · SQLite · Redis · Postgres — each with a documented atomicity and writer model (see the table below).
- Safe by default on crash — a crash mid-effect is quarantined, never silently re-fired. Opt-in policies (
check_then_decide,wait,auto_retry) when you want more. - Concurrency-safe — an ownership/fencing token on every claim; an optional lease + heartbeat makes reconciliation safe even across live workers.
- Onchain adapter — dedupe transactions by
(chain_id, from, nonce, calldata); a resumed agent never double-submits. - Framework helpers — thin
once_node(LangGraph) andonce_tool_run(CrewAI) wrappers. - Honest by policy — leads with its limits, and a CI lint fails the build if the docs ever overclaim.
- Zero required deps, fully typed, zero LLM. It's plumbing — it works offline, forever.
Guarantees & limits
The mechanism: compute a stable key → atomically claim it → if committed, replay the stored result without re-running; if in-flight, block/deny per policy; if new, run the effect and commit. On a crash mid-effect the key is left in-flight and quarantined — a half-completed payment must never silently re-fire.
It 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.
It does not: promise exactly-once delivery (impossible — it's at-most-once execution + replay-on-success, and end-to-end "the world changed once" holds only when composed with an idempotent provider). It cannot 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 is 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 sharing one Redis |
Postgres SERIALIZABLE |
true multi-writer, linearizable | multi-host production |
The full boundary — every guarantee and every limit — is in docs/ARCHITECTURE.md §9.
Recipes
Crash recovery for money movement — observe the world instead of guessing:
from exactly_once import once, check_then_decide, ProbeResult, Verdict
def prober(key): # observe the world: did the charge actually land?
charge = find_stripe_charge_by_idempotency_key(key) # your lookup against Stripe
return ProbeResult(Verdict.COMMITTED, charge) if charge else ProbeResult(Verdict.NOT_COMMITTED)
@once(store, key=lambda o: f"charge:{o.id}", policy=check_then_decide(prober))
def charge_card(order): ...
Concurrent workers — a lease makes reconciliation safe across live workers (a dead worker's orphan is adopted by exactly one; a live one is never adopted):
@once(store, key=..., policy=check_then_decide(prober), lease_ttl=30.0)
def charge_card(order): ...
Onchain — at-most-once transactions:
from exactly_once.onchain import onchain_once, TxIntent, Web3ChainClient
chain = Web3ChainClient(w3, private_key)
@onchain_once(store, chain) # key = (chain_id, from, nonce, calldata)
def payout(to, amount) -> TxIntent:
return TxIntent(to=to, value=amount)
LangGraph / CrewAI:
from exactly_once.integrations.langgraph import once_node
from exactly_once.integrations.crewai import once_tool_run
@once_node(store) # keys on the run's thread_id + node name
def charge(state, config): ...
class ChargeTool(BaseTool):
@once_tool_run(store, key=lambda self, order_id, **_: f"charge:{order_id}")
def _run(self, order_id): ...
How it compares
exactly-once is a library that guards the effect boundary — not a replacement for a workflow engine. It composes with all of these.
| exactly-once | Temporal / Restate / DBOS | AWS Lambda Powertools | Stripe idempotency keys | |
|---|---|---|---|---|
| What | a two-line library | a durable-execution runtime you adopt | a Lambda-only utility | a single provider's feature |
| Scope | the effect boundary, anywhere | orchestration and the effect boundary | Lambda handlers | one API |
| Crash mid-effect | quarantine — never auto-re-fire | activity re-runs; you make it idempotent | deletes the record and re-runs | replays the cached response |
| Adoption cost | pip install, two lines |
adopt a runtime | be on AWS Lambda | be on Stripe |
Every durable-execution engine, at the effect boundary, reduces to "at-least-once + an idempotency key." exactly-once is that reduction as a drop-in — with a safe default for the crash it can't otherwise resolve.
Documentation
- SPEC.md — the design spec & PRD.
- docs/ARCHITECTURE.md — the state machine, the store contract, and §9 Guarantees & Limits (read this before trusting anything).
- CHANGELOG.md · examples/ · ROADMAP.md
Development
uv venv --python 3.11 && uv pip install -e ".[dev]"
uv run pytest # full suite (Redis/Postgres need Docker; onchain needs Foundry)
uv run mypy src/exactly_once # strict typing
uv run ruff check src tests examples scripts # lint
uv run python scripts/check_docs_honesty.py # the docs-honesty gate
uv run python scripts/benchmark.py # per-call overhead
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. 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.1.tar.gz.
File metadata
- Download URL: exactly_once-0.2.1.tar.gz
- Upload date:
- Size: 100.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79677c2f8619f0347b95ea9b8d2e1b305577b95f33e91d60baa498c119ae2397
|
|
| MD5 |
d9ff0dada09a83819b7f88d517ac85c2
|
|
| BLAKE2b-256 |
d9163a2cbfb364bb35b330dffd8c1c96a8f459b324e02d5628b1dcbe4876aadf
|
Provenance
The following attestation bundles were made for exactly_once-0.2.1.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.1.tar.gz -
Subject digest:
79677c2f8619f0347b95ea9b8d2e1b305577b95f33e91d60baa498c119ae2397 - Sigstore transparency entry: 2626653830
- Sigstore integration time:
-
Permalink:
swarmproof/exactly-once@0b266dc0b5e3ec269d286fb4b2b43b2d9d3c3092 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/swarmproof
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0b266dc0b5e3ec269d286fb4b2b43b2d9d3c3092 -
Trigger Event:
release
-
Statement type:
File details
Details for the file exactly_once-0.2.1-py3-none-any.whl.
File metadata
- Download URL: exactly_once-0.2.1-py3-none-any.whl
- Upload date:
- Size: 42.5 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 |
c4123cf4156b88a8dd8bba5de68b708d2c4b4263d0b0e037df14483f02316942
|
|
| MD5 |
68c7e694368e104d8036c7bad9f17756
|
|
| BLAKE2b-256 |
c8f71c23a7a57af75e6baaa6896bebee1586c83cb3b7c9c81eb2b58ae0c2701c
|
Provenance
The following attestation bundles were made for exactly_once-0.2.1-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.1-py3-none-any.whl -
Subject digest:
c4123cf4156b88a8dd8bba5de68b708d2c4b4263d0b0e037df14483f02316942 - Sigstore transparency entry: 2626653831
- Sigstore integration time:
-
Permalink:
swarmproof/exactly-once@0b266dc0b5e3ec269d286fb4b2b43b2d9d3c3092 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/swarmproof
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0b266dc0b5e3ec269d286fb4b2b43b2d9d3c3092 -
Trigger Event:
release
-
Statement type: