Skip to main content

duroxide-python

Python SDK for the Duroxide durable execution runtime.

Write durable workflows as Python generators. The Rust runtime handles replay, persistence, and fault tolerance.

Features

  • Generator-based orchestrationsyield task descriptors, Rust handles DurableFutures
  • Activities — regular Python functions for side effects (I/O, network calls)
  • Timers — durable delays that survive process restarts
  • Events — wait for external signals
  • Sub-orchestrations — compose workflows hierarchically
  • Fan-out/Fan-inctx.all() for parallel execution, ctx.race() for first-to-complete
  • Continue-as-new — long-running orchestrations with bounded history
  • Deterministic replay — safe resume after crashes
  • SQLite & PostgreSQL — pluggable storage providers, including Microsoft Entra ID auth for Azure Database for PostgreSQL
  • Custom Statusctx.set_custom_status() / ctx.reset_custom_status() for orchestration progress reporting, client.wait_for_status_change() for efficient polling
  • KV Store — durable per-instance state via ctx.set_kv_value() / ctx.get_kv_value() / ctx.get_kv_all_values() / ctx.get_kv_all_keys() / ctx.get_kv_length() / ctx.clear_kv_value() / ctx.clear_all_kv_values() / ctx.prune_kv_values_updated_before(), plus client.get_kv_value() / client.wait_for_kv_value()
  • Event Queuesctx.dequeue_event(queue_name) for FIFO mailbox-style message passing, client.enqueue_event() to send messages
  • Retry on Sessionctx.schedule_activity_with_retry_on_session() for retry with session affinity
  • Tag Routing — worker tags for activity affinity (MAX_WORKER_TAGS=5, MAX_TAG_NAME_BYTES=256, MAX_KV_KEYS=150, MAX_KV_VALUE_BYTES=65536)
  • Admin APIs — instance management, metrics, pruning
  • Activity client accessctx.get_client() lets activities start new orchestrations
  • Runtime metricsmetrics_snapshot() for orchestration/activity counters

Installation

pip install duroxide

Prebuilt wheels are published for macOS arm64/x64, Linux manylinux x86_64 and aarch64, and Windows x86_64.

Quick Start

from duroxide import SqliteProvider, Client, Runtime

# Create provider and runtime
provider = SqliteProvider.in_memory()
runtime = Runtime(provider)

# Register an activity
@runtime.register_activity("greet")
def greet(ctx, input):
    return f"Hello, {input['name']}!"

# Register an orchestration (generator function)
@runtime.register_orchestration("GreetWorkflow")
def greet_workflow(ctx, input):
    result = yield ctx.schedule_activity("greet", input)
    return result

# Start runtime and run orchestration
import threading
runtime.start()

client = Client(provider)
client.start_orchestration("greet-1", "GreetWorkflow", {"name": "World"})
status = client.wait_for_orchestration("greet-1", 10000)
print(status.output)  # "Hello, World!"

runtime.shutdown()

Orchestrations

Orchestrations are Python generator functions. They must be deterministic — no I/O, no randomness, no time.time(). Use only ctx.* methods for side effects.

@runtime.register_orchestration("MyWorkflow")
def my_workflow(ctx, input):
    # Schedule activities
    result = yield ctx.schedule_activity("DoWork", input)

    # Fan-out / Fan-in
    results = yield ctx.all([
        ctx.schedule_activity("TaskA", {"id": 1}),
        ctx.schedule_activity("TaskB", {"id": 2}),
    ])

    # Timer
    yield ctx.schedule_timer(5000)  # 5 seconds

    # Wait for external event
    approval = yield ctx.wait_for_event("approval")

    # Sub-orchestration
    sub_result = yield ctx.schedule_sub_orchestration("SubWorkflow", input)

    # Race (first to complete wins)
    winner = yield ctx.race(
        ctx.schedule_activity("Fast", None),
        ctx.schedule_timer(10000),
    )

    # Custom status (fire-and-forget, no yield)
    ctx.set_custom_status("processing complete")

    # Dequeue from event queue (FIFO, blocks until message available)
    msg = yield ctx.dequeue_event("inbox")

    return {"result": result, "winner": winner}

Activities

Activities are regular Python functions that perform side effects. They run outside the replay engine and are safe for I/O operations.

@runtime.register_activity("SendEmail")
def send_email(ctx, input):
    ctx.trace_info(f"Sending email to {input['to']}")
    # ... actual email sending ...
    return {"sent": True}

PostgreSQL Provider

from duroxide import PostgresEntraOptions, PostgresProvider, Client, Runtime

provider = PostgresProvider.connect("postgresql://user:pass@localhost:5432/mydb")
# or with custom schema:
provider = PostgresProvider.connect_with_schema("postgresql://...", "duroxide_python")

runtime = Runtime(provider)
client = Client(provider)

PostgreSQL with Microsoft Entra ID

For Azure Database for PostgreSQL Flexible Server, use Entra ID token authentication instead of a password:

provider = PostgresProvider.connect_with_entra(
    host="my-server.postgres.database.azure.com",
    port=5432,
    database="appdb",
    user="my-managed-identity",
    options=PostgresEntraOptions(max_connections=10),
)

provider = PostgresProvider.connect_with_schema_and_entra(
    host="my-server.postgres.database.azure.com",
    port=5432,
    database="appdb",
    user="my-managed-identity",
    schema="duroxide_python",
    options=PostgresEntraOptions(refresh_interval_ms=1_200_000),
)

PostgresEntraOptions also accepts audience, acquire_timeout_ms, and refresh_interval_ms.

Admin APIs

client = Client(provider)

# Metrics
metrics = client.get_system_metrics()
stats = client.get_orchestration_stats("instance-1")
depths = client.get_queue_depths()

# Instance management
instances = client.list_all_instances()
info = client.get_instance_info("instance-1")
tree = client.get_instance_tree("instance-1")

# Execution history with full event data
executions = client.list_executions("instance-1")
events = client.read_execution_history("instance-1", executions[0])
for event in events:
    print(event.kind, event.data)
    # event.kind: "OrchestrationStarted" | "ActivityCompleted" | ...
    # event.data: JSON string with event-specific content (result, input, error, etc.)

# Cleanup
client.delete_instance("instance-1", force=True)
client.prune_executions("instance-1", PruneOptions(keep_last=5))

Custom Status

Report orchestration progress visible to external clients:

@runtime.register_orchestration("ProgressWorkflow")
def progress_workflow(ctx, input):
    ctx.set_custom_status("step 1: validating")
    yield ctx.schedule_activity("Validate", input)

    ctx.set_custom_status("step 2: processing")
    result = yield ctx.schedule_activity("Process", input)

    ctx.reset_custom_status()  # clear status
    return result

# Poll for status changes from outside
status = client.wait_for_status_change("instance-1", 0, 50, 10000)
if status:
    print(status.custom_status)          # "step 1: validating"
    print(status.custom_status_version)  # monotonically increasing counter

KV Store

Durable per-instance key-value state for orchestration coordination and request/response patterns:

@runtime.register_orchestration("KvWorkflow")
def kv_workflow(ctx, input):
    ctx.set_kv_value("status", "running")
    result = yield ctx.schedule_activity("Compute", input)
    ctx.set_kv_value("result", str(result))
    snapshot = ctx.get_kv_all_values()
    keys = ctx.get_kv_all_keys()
    count = ctx.get_kv_length()
    return {"result": result, "snapshot": snapshot, "keys": keys, "count": count}

# External reads
status = client.wait_for_kv_value("instance-1", "status", 10000)
result = client.get_kv_value("instance-1", "result")

KV entries are scoped to a single orchestration instance and remain readable after completion until the instance is deleted or pruned. Use ctx.prune_kv_values_updated_before(cutoff_ms) to deterministically clear stale keys from prior turns when you only want to retain newer state.

Event Queues

Persistent FIFO message passing between clients and orchestrations:

@runtime.register_orchestration("ChatBot")
def chat_bot(ctx, input):
    msg_json = yield ctx.dequeue_event("inbox")
    msg = json.loads(msg_json)
    response = yield ctx.schedule_activity("Generate", msg["text"])
    ctx.set_custom_status(json.dumps({"state": "replied", "response": response, "seq": msg["seq"]}))
    if "bye" in msg["text"].lower():
        return f"Done after {msg['seq']} msgs"
    return (yield ctx.continue_as_new(""))

# Send messages from outside
client.enqueue_event(instance_id, "inbox", json.dumps({"seq": 1, "text": "Hello!"}))
status = client.wait_for_status_change(instance_id, 0, 50, 10000)
reply = json.loads(status.custom_status)

Development

# Create and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate

# Install build tools and test dependencies
pip install maturin pytest

# Build the native extension and install in development mode
maturin develop

# Run all 59 tests
pytest

# Run tests with verbose output
pytest -v

# Run a single test file
pytest tests/test_e2e.py -v

# Run a single test
pytest tests/test_e2e.py::test_hello_world

# Stop on first failure
pytest -v -x

# Build release wheel
maturin build --release

After Rust source changes (src/*.rs), re-run maturin develop to rebuild. Python-only changes (python/duroxide/, tests/) take effect immediately.

Changelog

See CHANGELOG.md for release notes.

Documentation

  • User Guide — orchestration patterns, activities, providers, tracing, determinism rules
  • Architecture — PyO3 interop, GIL deadlock fix, generator driver, tracing internals

Support

Use GitHub Issues for bug reports and feature requests. Do not report security vulnerabilities through public GitHub issues; follow the instructions in SECURITY.md instead.

Code of Conduct

This project has adopted the Microsoft Open Source Code of Conduct. For more information, see the Code of Conduct FAQ or contact opencode@microsoft.com with questions or comments.

Security

Microsoft takes the security of our software products and services seriously. Please do not report security vulnerabilities through public GitHub issues. See SECURITY.md for security reporting instructions.

Privacy and Telemetry

duroxide-python does not send telemetry to Microsoft. Applications may configure their own logging or metrics exporters; those signals are controlled by the application owner.

Trademarks

This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos is subject to those third-party policies.

License

MIT License - see LICENSE for details.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

duroxide-0.1.27.tar.gz (120.5 kB view details)

Uploaded Source

Built Distributions

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

duroxide-0.1.27-cp312-cp312-win_amd64.whl (5.8 MB view details)

Uploaded CPython 3.12Windows x86-64

duroxide-0.1.27-cp312-cp312-manylinux_2_28_x86_64.whl (7.6 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.28+ x86-64

duroxide-0.1.27-cp312-cp312-manylinux_2_28_aarch64.whl (7.3 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.28+ ARM64

duroxide-0.1.27-cp312-cp312-macosx_11_0_arm64.whl (5.3 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

duroxide-0.1.27-cp312-cp312-macosx_10_12_x86_64.whl (5.7 MB view details)

Uploaded CPython 3.12macOS 10.12+ x86-64

File details

Details for the file duroxide-0.1.27.tar.gz.

File metadata

  • Download URL: duroxide-0.1.27.tar.gz
  • Upload date:
  • Size: 120.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for duroxide-0.1.27.tar.gz
Algorithm Hash digest
SHA256 9ed2969f737094d1e04e9a0c7524d9a929dc90eb310f229f7924c4d8835b5c7c
MD5 3462f748f0b3c0edb842a56c75963233
BLAKE2b-256 7c39d1376e7b8cc84baa1a1c4885ad7f8b413ab3103c211255d4ee73f157e850

See more details on using hashes here.

Provenance

The following attestation bundles were made for duroxide-0.1.27.tar.gz:

Publisher: publish.yml on microsoft/duroxide-python

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

File details

Details for the file duroxide-0.1.27-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: duroxide-0.1.27-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 5.8 MB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for duroxide-0.1.27-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 dec1130cf745633a92f4934c0c1504e1e3b97fd42519b7a09284dd207fe9ff45
MD5 b371fbeb240e1354b10de19294122ba2
BLAKE2b-256 0e6a49709d3f12e93222647aa04db182c7a0f2c40fe2da696755fee93759af71

See more details on using hashes here.

Provenance

The following attestation bundles were made for duroxide-0.1.27-cp312-cp312-win_amd64.whl:

Publisher: publish.yml on microsoft/duroxide-python

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

File details

Details for the file duroxide-0.1.27-cp312-cp312-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for duroxide-0.1.27-cp312-cp312-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 88756974f3df474bf13c06fe89331c201cdc729a0adeec6326b004ed2ae0c0b1
MD5 f5fc9751c1c119a3d55c3480080676ea
BLAKE2b-256 823954ea60ef30ff3f2028527f8e5fbcf1cf9f180908cc45ad83713e89390469

See more details on using hashes here.

Provenance

The following attestation bundles were made for duroxide-0.1.27-cp312-cp312-manylinux_2_28_x86_64.whl:

Publisher: publish.yml on microsoft/duroxide-python

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

File details

Details for the file duroxide-0.1.27-cp312-cp312-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for duroxide-0.1.27-cp312-cp312-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 590f21f6773a7bf6877d2294730b0c8bbde2634ea7bb362bd2f5b86ffddec83a
MD5 845743ef0b2569901700bee742b26416
BLAKE2b-256 b7bf7e7d392f36e3e881091528ae62e4428a24b6acbcd3529ba2d50c0efba0dd

See more details on using hashes here.

Provenance

The following attestation bundles were made for duroxide-0.1.27-cp312-cp312-manylinux_2_28_aarch64.whl:

Publisher: publish.yml on microsoft/duroxide-python

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

File details

Details for the file duroxide-0.1.27-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for duroxide-0.1.27-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 9dd0be38d48a6a3cf31f5910266a620e262fa4119c62153c04074fee55144e82
MD5 e03e4fd701e1b8b9bfd83a21df3107f3
BLAKE2b-256 844bb3a8b96d2ae3ada656ce343203f9bab0c54539c4c65ced3db6cbeef31a73

See more details on using hashes here.

Provenance

The following attestation bundles were made for duroxide-0.1.27-cp312-cp312-macosx_11_0_arm64.whl:

Publisher: publish.yml on microsoft/duroxide-python

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

File details

Details for the file duroxide-0.1.27-cp312-cp312-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for duroxide-0.1.27-cp312-cp312-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 aee9cd63dec118240763582e922dbf90c76865f26e42ba153195e8a1a1260084
MD5 275e0c89333c4471b5aaa2896772169d
BLAKE2b-256 4b604ff7804358c29eb639b35e8f8e63358d9142434be5188edd0cd31f126084

See more details on using hashes here.

Provenance

The following attestation bundles were made for duroxide-0.1.27-cp312-cp312-macosx_10_12_x86_64.whl:

Publisher: publish.yml on microsoft/duroxide-python

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

Release history Release notifications | RSS feed

0.1.28

6 files

This release

0.1.27 This release

6 files

0.1.26

6 files

0.1.25

6 files

0.1.24

6 files

0.1.22

5 files

0.1.20

5 files

0.1.19

5 files

0.1.18

5 files

0.1.16

5 files

0.1.15

5 files

0.1.14

5 files

0.1.13

5 files

0.1.12

5 files

0.1.11

4 files

0.1.10

4 files

0.1.9

4 files

0.1.8

3 files

0.1.7

4 files

0.1.6

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

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