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

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.6
File Size Uploaded
orcher_sdk-0.4.6.tar.gz 259.4 kB Details

Built distributions (wheels)

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

Total release size: 32.5 MB

Release files / orcher_sdk-0.4.6.tar.gz

Download URL orcher_sdk-0.4.6.tar.gz
Size 259.4 kB
Tags Source
SHA-256 checksum
How to use checksums
0928834f775d269753568786b181784988e7bfb2e32e9fdd50c0cf2f4a26578b
BLAKE2b-256 checksum
How to use checksums
857702fcb3a62ab1b190a5d0511e1505d7f870621023c114d88c530df123adbf
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 9, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.6-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
dd722152efd077a54a720fa976d1ce4ef4579df054d1317afe67a92fadac1bb5
BLAKE2b-256 checksum
How to use checksums
8dc872c037daf4842aecb23e99d2e1ad3fd62d3dabed8fb7a648b27e136aae96
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 9, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.6-cp311-abi3-musllinux_1_2_aarch64.whl
Size 5.7 MB
Tags CPython 3.11 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
f7f0163161eda1de2499291703cb3f57eeb8e7b7d26b5956c5a2e3654e92e6aa
BLAKE2b-256 checksum
How to use checksums
2133b45787fbdfa8e9f3310b81a2acdad133159d79ea51315069b372fc5fc24f
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 9, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.6-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
00b2f0093cd28d08d835e4ef4f4a149eb3d0674324df8c473522dfd096dd4840
BLAKE2b-256 checksum
How to use checksums
70246f3ef50dae0222f07e599eb4d9968288290e59088df4a9a069ff9cf5b47c
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 9, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.6-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
9146b74d193a95deb426254692e8fbcae4202edbea6533cbfad978cb1d293873
BLAKE2b-256 checksum
How to use checksums
29091a3615328039abc02378f8ff5bf52329bd4c042085aa5b6ad0579f640df9
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 9, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.6-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
297ef2020f65f190a9ce41471803f3fe0ac60f6959d099d4b60867cc1d926306
BLAKE2b-256 checksum
How to use checksums
270bf7be2248aabe72cec17d620fbd026fd1df0d5d64aa07427fff34730c571a
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 9, 2026.

Transparency log

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

Download URL orcher_sdk-0.4.6-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
005da7c85460bbdae7ff9264716d3375e1ba0449d27225de3a404c097391ed92
BLAKE2b-256 checksum
How to use checksums
95f9171aebe88b1c7eb53fe28091267de74b784c7dd3f3530f1369836ec3ffdc
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 9, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.9

7 release files

0.4.8

7 release files

0.4.7

7 release files

This release

0.4.6 This release

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