Skip to main content

replyonce (Python)

Stop your agent from replying, refunding or ticketing twice.

The Python port of replyonce. It uses the same state machine and stores the same JSON state as the TypeScript package, so Python and Node workers can share one Postgres table or Redis prefix. The full guide, design notes, guarantees and limits are in the main README.

pip install replyonce            # memory store
pip install "replyonce[postgres]" # + psycopg 3
pip install "replyonce[redis]"    # + redis-py
replyonce demo                    # chaos simulation: duplicates without vs with replyonce

30-second quickstart

import asyncio
from replyonce import Key, MemoryStore, ReplyOnce

ro = ReplyOnce(MemoryStore())  # PostgresStore / RedisStore in production
refunds_issued = 0

async def issue_refund(ctx):
    # Your provider call goes here; pass ctx.idempotency_key if it accepts one.
    global refunds_issued
    refunds_issued += 1
    return {"refund_id": "re_1"}

async def main():
    key = Key("order-A-1001", "refund", {"order": "A-1001", "cents": 2500})
    raced = await asyncio.gather(*(ro.once(key, issue_refund) for _ in range(3)))
    late = await ro.once(key, issue_refund)
    print([r.status for r in raced])  # ['executed', 'in_flight', 'in_flight']
    print(late.status, late.value)    # replayed {'refund_id': 're_1'}
    print(refunds_issued)             # 1

asyncio.run(main())

A conversational agent opens a run per unit of work and sends every customer-visible write through it:

run = await ro.begin(conversation_id, run_id=job_id, context_at=snapshot_ms)
if run.can_write:
    r = await run.once("reply", {"in_reply_to": message_id}, send_reply, summary=text)
    if r.status == "refused" and r.reason == "conversation_changed":
        ...  # r.conflicts says what another run did; re-read and decide again
    await run.end()

Stores

from psycopg_pool import AsyncConnectionPool
from replyonce.stores.postgres import PostgresStore
store = PostgresStore(AsyncConnectionPool(dsn, open=False))  # await pool.open(); await store.migrate()

import redis.asyncio as redis
from replyonce.stores.redis import RedisStore
# A blocking pool waits for a free connection under bursts instead of raising "Too many connections".
store = RedisStore(redis.Redis(connection_pool=redis.BlockingConnectionPool.from_url(url, max_connections=50)))

Adapters

replyonce.adapters has guard_tool, check_tool, dispatch_tool, describe_outcome, to_openai_tool, to_anthropic_tool, webhook_once and http_status_for. They behave as the TypeScript adapters do. The BullMQ adapter is Node-only.

Differences from the TypeScript package

  • The API is asyncio-only.
  • Names are snake_case (pending_ttl_ms, run_id, context_at). Results are dataclasses, and ledger entries are plain dicts with the shared camelCase field names.
  • The effect gets ctx.deadline_ms (epoch ms) instead of an AbortSignal.

Apache-2.0.

Metadata

Release files for replyonce 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for replyonce 0.1.1
File Size Uploaded Provenance
replyonce-0.1.1.tar.gz 46.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for replyonce 0.1.1
File Interpreter ABI Platform
replyonce-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 74.1 kB

Release files / replyonce-0.1.1.tar.gz

Download URL replyonce-0.1.1.tar.gz
Size 46.0 kB
Tags Source
SHA-256 checksum
How to use checksums
d86c8a509c2c9db8cb34320461d972b9ced6d0d4b60746647e172e5c3e091c4a
BLAKE2b-256 checksum
How to use checksums
79ffbddf22eaba9b42aec52cf09f18b9e529085135215099426f32068f2b0df3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / replyonce-0.1.1-py3-none-any.whl

Download URL replyonce-0.1.1-py3-none-any.whl
Size 28.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f7da3c8dbb9ac9972ec86f07ffedca5219a2acd0be408fe384925245e6d0a3fd
BLAKE2b-256 checksum
How to use checksums
1c86eda0a4f48be3f2d7b8c02da1be3f03d7eaf764f2fc906a5db9341060190f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for replyonce 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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