Skip to main content

ORCHER Python SDK

Crash-proof workflows, written as plain async Python.


PyPI Python versions CI Apache 2.0

Write async functions; ORCHER journals each step and resumes interrupted runs where they stopped.

  • Durable: every step journaled; crashed runs resume on another worker
  • Plain Python: @workflow and @task on async functions, no DSL
  • Deterministic replay: step ids, time and randomness stay the same on every replay
  • Smart retries: per-task policies and never-retry error types
  • Timers and events: sleep for days or wait for an outside event
  • Child workflows: compose and run workflows in parallel
  • Actors: stateful objects with a single writer per key
  • Testing: run workflows in memory with mocks and a fake clock
  • Multi-tenant: API keys, organizations and namespaces built in
  • Native core: the engine protocol, replay and the worker run in Rust, shared with the Rust and TypeScript SDKs

Install

pip install orcher-sdk

The package installs as orcher:

import orcher

Quick start

Decorate a task and a workflow that calls it. Both register themselves when their module is imported, and the worker runs everything registered:

import asyncio
import os

from orcher import TaskContext, Worker, WorkflowContext, task, workflow


@task(name="send-confirmation", retry=3)
async def send_confirmation(ctx: TaskContext, order_id: str, email: str) -> str:
    return f"sent confirmation for {order_id} to {email}"


@workflow(name="confirm-order")
async def confirm_order(ctx: WorkflowContext, order_id: str, email: str) -> str:
    return await ctx.execute_task(send_confirmation, order_id=order_id, email=email)


async def main() -> None:
    worker = (
        Worker.builder()
        .server_url("http://localhost:50051")
        .namespace("default")
        .task_queue("orders")
        .api_key(os.environ.get("ORCHER_API_KEY"))  # for an engine that requires one
        .build()
    )
    await worker.run()


if __name__ == "__main__":
    asyncio.run(main())

Tasks that need clients or connections can be methods of a @tasks class instead; hand the worker a built instance with worker.register_task_instance().

Start it from any process and wait for the result. A dict input reaches the workflow as keyword arguments:

import os

from orcher import Client, ClientConfig

config = ClientConfig(
    server_url="http://localhost:50051",
    api_key=os.environ.get("ORCHER_API_KEY"),  # for an engine that requires one
)
async with Client(config) as client:
    handle = await client.start_workflow(
        "confirm-order",
        task_queue="orders",
        args=({"order_id": "order-1", "email": "ada@example.com"},),
    )
    receipt = await handle.result()

Guide

Timers: sleep for minutes or months

A timer is recorded by the engine, so no worker is busy while it runs, and a restart doesn't reset it:

from datetime import timedelta

from orcher import WorkflowContext, workflow


@workflow(name="trial")
async def trial(ctx: WorkflowContext, email: str) -> None:
    await ctx.sleep(timedelta(days=14))
    await ctx.execute_task(send_trial_ended, email=email)
Events: wait for something outside the workflow

A workflow can park until a named event arrives, optionally with a deadline:

from datetime import timedelta

from orcher import WorkflowContext, workflow


@workflow(name="approval")
async def approval(ctx: WorkflowContext, request_id: str) -> str:
    try:
        approved = await ctx.wait_for_event_with_timeout("approved", timedelta(days=3))
    except TimeoutError:
        return f"{request_id} expired"
    return f"{request_id} approved" if approved else f"{request_id} rejected"

Send the event from a client:

handle = await client.get_workflow("approval-42")
await handle.send_event("approved", True)

Cancel it the same way, optionally limiting how long its cleanup may take before the engine terminates it. Engines from before cancellation cleanup ignore the limit and cancel at once:

await handle.cancel(cleanup_timeout=timedelta(seconds=30))
Child workflows: compose workflows from workflows
from orcher import WorkflowContext, workflow


@workflow(name="ship-order")
async def ship_order(ctx: WorkflowContext, order_id: str) -> str:
    label = await ctx.execute_child_workflow("print-label", args=(order_id,))
    return f"{order_id} shipped with {label}"

ctx.start_child_workflow() returns a handle instead, to run several children at once and collect their results later.

Retries: decide which failures are worth retrying

A failure's type is the exception's class name (CardDeclined, not payments.CardDeclined). List the types never to retry in the task's policy. An exception whose non_retryable attribute is true is never retried, whatever the policy allows:

from orcher import RetryPolicy, TaskContext, task


class CardDeclined(Exception):
    pass


class AccountClosed(Exception):
    non_retryable = True


@task(
    name="charge",
    retry_policy=RetryPolicy(max_attempts=5, non_retryable_error_types=["CardDeclined"]),
)
async def charge(ctx: TaskContext, order_id: str) -> str:
    # Runs once:
    raise CardDeclined(f"card declined for {order_id}")
    # Also runs once, listed or not:
    # raise AccountClosed(f"account closed for {order_id}")

Any other exception is retried. non_retryable can also be set on a single instance before it is raised.

A workflow is marked the same way: an exception whose non_retryable attribute is true, raised from workflow code, fails the workflow without a retry, whatever its retry policy allows. Any other exception a workflow raises is retried as its policy says.

A workflow that catches the failure reads its type and whether the task was cancelled from the TaskError:

try:
    await ctx.execute_task(charge, order_id)
except TaskError as e:
    if e.is_cancelled:
        ...  # the task was cancelled
    elif e.error_type == "CardDeclined":
        ...  # ask for another card
Large payloads: what fits, and what happens when it doesn't

Workers and clients send and receive gRPC messages of up to 32 MiB; set ORCHER_MAX_MESSAGE_BYTES to change that. The engine accepts one payload (an input, a result, an event) of up to 8 MiB unless configured otherwise. A task whose result is too large fails straight away with a PayloadTooLarge failure that says how large it was, and is not retried: store large data elsewhere and pass a reference.

Time and randomness: the replay-safe way

ctx.time reads the time the engine recorded, and ctx.random is seeded from the run, so both give the same answer every time the workflow is replayed:

from orcher import WorkflowContext, workflow


@workflow(name="invoice")
async def invoice(ctx: WorkflowContext, customer: str) -> str:
    number = ctx.random.uuid()
    issued_at = ctx.time.now()
    return f"invoice {number} for {customer}, issued at {issued_at.isoformat()}"
Testing: run workflows in memory

orcher.testing runs a workflow without an engine, with its tasks mocked by name. With pytest and pytest-asyncio:

import pytest

from orcher.testing import TestWorkflowEnvironment

from orders import confirm_order


@pytest.mark.asyncio
async def test_confirms_the_order() -> None:
    env = TestWorkflowEnvironment()
    env.mock_task("send-confirmation").returns("sent")

    receipt = await env.execute_workflow(
        confirm_order, {"order_id": "order-1", "email": "ada@example.com"}
    )

    assert receipt == "sent"
    env.assert_task_called_with(
        "send-confirmation", {"order_id": "order-1", "email": "ada@example.com"}
    )

Mocks can also raise, return a sequence or compute their result, and the environment controls the clock.

Workers: concurrency and stopping

A worker runs at most max_concurrent_workflows workflow activations, max_concurrent_tasks tasks and max_concurrent_actor_operations actor operations at once (set on the builder), and takes no more work from the engine while it is full. It registers with the engine when it starts, and the engine sees which SDK and release it runs.

await worker.shutdown() stops it taking work at once, and gives the work it is running the shutdown grace (shutdown_grace_time, 30 seconds unless set) to finish. Workflow activations still unfinished after that are handed back to the engine for another worker; tasks still running are asked to cancel (ctx.cancellation_token) and are retried once they time out. shutdown(force=True) cancels the running work without waiting for it.


Contributing

Issues and pull requests are welcome. See CONTRIBUTING.md for how to build, test and propose a change.

License

Licensed under the Apache License, Version 2.0.

"Python" and the Python logos are trademarks or registered trademarks of the Python Software Foundation, shown here to indicate the language this SDK is for.

Metadata

Release files for orcher-sdk 0.4.12

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

Source distribution (sdist)

Source distribution for orcher-sdk 0.4.12
File Size Uploaded Provenance
orcher_sdk-0.4.12.tar.gz 288.8 kB With provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for orcher-sdk 0.4.12
File
orcher_sdk-0.4.12-cp311-abi3-musllinux_1_2_x86_64.whl CPython 3.11 abi3 Linux musl 1.2+ x86-64 Details
orcher_sdk-0.4.12-cp311-abi3-musllinux_1_2_aarch64.whl CPython 3.11 abi3 Linux musl 1.2+ ARM64 Details
orcher_sdk-0.4.12-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.11 abi3 Linux glibc 2.17+ x86-64 Details
orcher_sdk-0.4.12-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.11 abi3 Linux glibc 2.17+ ARM64 Details
orcher_sdk-0.4.12-cp311-abi3-macosx_11_0_arm64.whl CPython 3.11 abi3 macOS 11.0+ ARM64 Details
orcher_sdk-0.4.12-cp311-abi3-macosx_10_12_x86_64.whl CPython 3.11 abi3 macOS 10.12+ x86-64 Details

Total release size: 34.0 MB

Release files / orcher_sdk-0.4.12.tar.gz

Download URL orcher_sdk-0.4.12.tar.gz
Size 288.8 kB
Tags Source
SHA-256 checksum
How to use checksums
0d459001a99b70553be623ad9357d75cb020d2b0727f8a2ab764a19d1192916a
BLAKE2b-256 checksum
How to use checksums
e927997871ee6996cdaa64847486bc2fb43c7b3e69300158645dcbccc8e39ea9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / orcher_sdk-0.4.12-cp311-abi3-musllinux_1_2_x86_64.whl

Download URL orcher_sdk-0.4.12-cp311-abi3-musllinux_1_2_x86_64.whl
Size 6.0 MB
Tags CPython 3.11 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
687a65cc42b0df38666986bb476289bb3082bf34d5d1f562bddc600b9a0bc5d1
BLAKE2b-256 checksum
How to use checksums
d9f645e3c7eeafbc6f421c181b5b74244294d6aebf484e6a1b56dda27305e314
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / orcher_sdk-0.4.12-cp311-abi3-musllinux_1_2_aarch64.whl

Download URL orcher_sdk-0.4.12-cp311-abi3-musllinux_1_2_aarch64.whl
Size 6.0 MB
Tags CPython 3.11 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
89f0ae0a3941529d9cf060dd5e7741fc6e3c763b2e7b29ab26d8df6e34467011
BLAKE2b-256 checksum
How to use checksums
30a6294c67d4c98c2b55d8963d65cbdbe94244263bf97c8a90d6a320da11127d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / orcher_sdk-0.4.12-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL orcher_sdk-0.4.12-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 5.6 MB
Tags CPython 3.11 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
dfb07a4e9eeae86aed444688629e39cd507757824c32a7e13bd714d9bdfaa485
BLAKE2b-256 checksum
How to use checksums
06c4b492e3a1990a345d38f114560f20bf651229443128d8c6517adaa28574b4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / orcher_sdk-0.4.12-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL orcher_sdk-0.4.12-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 5.8 MB
Tags CPython 3.11 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
d0ceb07a730cc18514a4135c23abde8cea9ebfe951dd2f25d110cc523110bded
BLAKE2b-256 checksum
How to use checksums
d4556d410427960ca3234cdd9e3397d6fe0fd195a28f22d1e6bc9f0c6f492b3e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / orcher_sdk-0.4.12-cp311-abi3-macosx_11_0_arm64.whl

Download URL orcher_sdk-0.4.12-cp311-abi3-macosx_11_0_arm64.whl
Size 5.1 MB
Tags CPython 3.11 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
261450387562f4cc7d642b826c825ed7f7bb2a29dd996ca084ca13fa6d03b46b
BLAKE2b-256 checksum
How to use checksums
a9b85aa2e09638efcae9cc723b42de65019da6ba4a20950f4494de3b4eb79e9e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / orcher_sdk-0.4.12-cp311-abi3-macosx_10_12_x86_64.whl

Download URL orcher_sdk-0.4.12-cp311-abi3-macosx_10_12_x86_64.whl
Size 5.2 MB
Tags CPython 3.11 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
f35098d76c14011d1c671b1477eb6b288b98fa2a71ca74dd11c28e228baa5bbd
BLAKE2b-256 checksum
How to use checksums
76857f5e550a359ad5bc3509d5a69d3cf39fd00bcad9a58554a09657f223113c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Security for orcher-sdk 0.4.12

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.

Full provenance

All 7 files in this release have been uploaded with provenance metadata.

Select from 7 files

Attestation for orcher_sdk-0.4.12.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

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.

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