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 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.10

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.10
File Size Uploaded Provenance
orcher_sdk-0.4.10.tar.gz 283.1 kB With provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for orcher-sdk 0.4.10
File
orcher_sdk-0.4.10-cp311-abi3-musllinux_1_2_x86_64.whl CPython 3.11 abi3 Linux musl 1.2+ x86-64 Details
orcher_sdk-0.4.10-cp311-abi3-musllinux_1_2_aarch64.whl CPython 3.11 abi3 Linux musl 1.2+ ARM64 Details
orcher_sdk-0.4.10-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.10-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.11 abi3 Linux glibc 2.17+ ARM64 Details
orcher_sdk-0.4.10-cp311-abi3-macosx_11_0_arm64.whl CPython 3.11 abi3 macOS 11.0+ ARM64 Details
orcher_sdk-0.4.10-cp311-abi3-macosx_10_12_x86_64.whl CPython 3.11 abi3 macOS 10.12+ x86-64 Details

Total release size: 33.9 MB

Release files / orcher_sdk-0.4.10.tar.gz

Download URL orcher_sdk-0.4.10.tar.gz
Size 283.1 kB
Tags Source
SHA-256 checksum
How to use checksums
001d67ccd1a0062857fc2509f01e52fceed14b0e50d2cb8320b26c8f36bdc119
BLAKE2b-256 checksum
How to use checksums
60db16a4091a2577e0bfa65d218da38514eb2228f0a1afe5a6e33bf8d5b9a702
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 17, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.10-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
c78ff064a2faa54cee8cb194bb027034fe937002cdc617366716e8f576da80cf
BLAKE2b-256 checksum
How to use checksums
e32669db46c06bf579865a42d1fe87e4cd5fa59754098a72181984c74c541de7
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 17, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.10-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
7a18557d5545c0dcddac8c20a85c6c264e76fa84756d697223c32596329cfa9a
BLAKE2b-256 checksum
How to use checksums
9b9535036d7c9ab4e910207ed009dfd53d24397f4c23e35f74e224fef78860cf
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 17, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.10-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 5.5 MB
Tags CPython 3.11 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
f75f7f5577b496d859cebf90214c92e324a036750cd07ce60c6bd07ee7ab010b
BLAKE2b-256 checksum
How to use checksums
7a66248be9a923d04e8455590a2f16ed65eb3b3a663cfde25fcce26eb5d3085b
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 17, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.10-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
dc6f939416d41ad1edbb68a6fb0f899723b115e6fa69d48ea5b1aca6009cf2b8
BLAKE2b-256 checksum
How to use checksums
2c0f44507b28ea3b9798dcb0e4d718f4ae5af0e2d253f70356c7b5c55efab751
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 17, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.10-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
e09287282c4cef01ed437562cb1c33d2e59e7f2675b3f0937f0452025cc60ad6
BLAKE2b-256 checksum
How to use checksums
8b4535cce52569343256fd57c66b7387ae45ab75017d8b0d3b85403e54eaa58c
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 17, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.10-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
345d749f3af83fa1137c8ef1cc27c1d46f85807ad52e887369f66cff5958d39e
BLAKE2b-256 checksum
How to use checksums
17ef618c7b817d2a6475550a3d4344050cb7a2067c6bf920fa4e4bd2b914c9af
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 17, 2026.

Transparency log

Security for orcher-sdk 0.4.10

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.10.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 17, 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