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 and state machine run in Rust

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.

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.


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

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.8
File Size Uploaded
orcher_sdk-0.4.8.tar.gz 272.9 kB Details

Built distributions (wheels)

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

Total release size: 32.6 MB

Release files / orcher_sdk-0.4.8.tar.gz

Download URL orcher_sdk-0.4.8.tar.gz
Size 272.9 kB
Tags Source
SHA-256 checksum
How to use checksums
a33e198395d73ee71b281b18ba46f202645c1f96ac2989be43dd20336e8f4d87
BLAKE2b-256 checksum
How to use checksums
b1b1ea30b7d22752722f5a4a56ce6e79a07ea9b167fc3d44264016c2b1f90b25
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 11, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.8-cp311-abi3-musllinux_1_2_x86_64.whl
Size 5.8 MB
Tags CPython 3.11 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
ac367c32148171358ef3bfa91320c21a8d7ed8525d9ce7960c47ce6c6d3dbf45
BLAKE2b-256 checksum
How to use checksums
4a33ca5e807321774f399c1222ab06be9b9c41664e3642999a24a7c3166dfc5c
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 11, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.8-cp311-abi3-musllinux_1_2_aarch64.whl
Size 5.8 MB
Tags CPython 3.11 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
8d4decfbd8bc6147d2d9e5e37b976ac17f012efcf92c3dc35c61a7564cc55ef3
BLAKE2b-256 checksum
How to use checksums
81a525148aead5f48a9faf1adf7ba7f8e6c09bbeed4889839941b8d05e7bb36e
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 11, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.8-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 5.3 MB
Tags CPython 3.11 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
a5a3fdaf5b45c77ce0a6917966863127c3ba52baf20184a4fcdcca92cfb7fe7b
BLAKE2b-256 checksum
How to use checksums
b73b9a2520a894ea9c1d9f2f552e76ee654cb82c2e1400ff40a893efea5a9ebd
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 11, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.8-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 5.6 MB
Tags CPython 3.11 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
3ce53829109c8356472281dc40e40ce974f996bd639b8431cb58729b3cd5ab00
BLAKE2b-256 checksum
How to use checksums
70cd1d8779414db17d1111bd320728d68b97a7c3302876bfb020b1573db97573
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 11, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.8-cp311-abi3-macosx_11_0_arm64.whl
Size 4.9 MB
Tags CPython 3.11 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
880ae0921222d81c9fd0edbed1fcd0d850ff0f5b61eeafc78ef9220e0b7b1003
BLAKE2b-256 checksum
How to use checksums
9d78b9513505a2e605e071c32c4fc014f9f8aca82a1623f99cbe91e3efcb1248
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 11, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.8-cp311-abi3-macosx_10_12_x86_64.whl
Size 5.0 MB
Tags CPython 3.11 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
2abfbd26be6f2d108e0b870a1c197cce5270d9ec80525d784fe51411af36f579
BLAKE2b-256 checksum
How to use checksums
21d458640e5af9ebdb02838a3697d096d8ab613e5f39ed571c6a51b485d02276
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 11, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.9

7 release files

This release

0.4.8 This release

7 release files

0.4.7

7 release files

0.4.6

7 release files

0.4.5

7 release files

0.4.4

7 release files

0.4.3

7 release files

0.4.2

7 release files

0.4.1

7 release files

0.4.0

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