Skip to main content

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.

PyPI Python CI License: MIT Typed

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 identicalasync 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 @once decorator and a with 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) and once_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

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-onceyou 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

exactly_once-0.2.1.tar.gz (100.5 kB view details)

Uploaded Source

Built Distribution

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

exactly_once-0.2.1-py3-none-any.whl (42.5 kB view details)

Uploaded Python 3

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

Hashes for exactly_once-0.2.1.tar.gz
Algorithm Hash digest
SHA256 79677c2f8619f0347b95ea9b8d2e1b305577b95f33e91d60baa498c119ae2397
MD5 d9ff0dada09a83819b7f88d517ac85c2
BLAKE2b-256 d9163a2cbfb364bb35b330dffd8c1c96a8f459b324e02d5628b1dcbe4876aadf

See more details on using hashes here.

Provenance

The following attestation bundles were made for exactly_once-0.2.1.tar.gz:

Publisher: release.yml on swarmproof/exactly-once

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

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

Hashes for exactly_once-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c4123cf4156b88a8dd8bba5de68b708d2c4b4263d0b0e037df14483f02316942
MD5 68c7e694368e104d8036c7bad9f17756
BLAKE2b-256 c8f71c23a7a57af75e6baaa6896bebee1586c83cb3b7c9c81eb2b58ae0c2701c

See more details on using hashes here.

Provenance

The following attestation bundles were made for exactly_once-0.2.1-py3-none-any.whl:

Publisher: release.yml on swarmproof/exactly-once

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

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page