Crash-proof workflows, written as plain async Python.
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:
@workflowand@taskon async functions, no DSLDeterministic 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)
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.
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.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| orcher_sdk-0.4.0.tar.gz | 245.9 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| orcher_sdk-0.4.0-cp311-abi3-musllinux_1_2_x86_64.whl | CPython 3.11 | abi3 | Linux musl 1.2+ x86-64 | Details |
| orcher_sdk-0.4.0-cp311-abi3-musllinux_1_2_aarch64.whl | CPython 3.11 | abi3 | Linux musl 1.2+ ARM64 | Details |
| orcher_sdk-0.4.0-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.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.11 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| orcher_sdk-0.4.0-cp311-abi3-macosx_11_0_arm64.whl | CPython 3.11 | abi3 | macOS 11.0+ ARM64 | Details |
| orcher_sdk-0.4.0-cp311-abi3-macosx_10_12_x86_64.whl | CPython 3.11 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 32.0 MB
Release files / orcher_sdk-0.4.0.tar.gz
| Download URL | orcher_sdk-0.4.0.tar.gz |
|---|---|
| Size | 245.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f95edd55fefd5a465e7a1993c7318462a0708cabb9b127b9e09efc513dfb238d
|
|
BLAKE2b-256 checksum How to use checksums |
088d4eb482a668ea9267a434ccd188d20fdf90523709a923cce9e30fcad3cf83
|
| 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 2, 2026.
Transparency logRelease files / orcher_sdk-0.4.0-cp311-abi3-musllinux_1_2_x86_64.whl
| Download URL | orcher_sdk-0.4.0-cp311-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 5.7 MB |
| Tags | CPython 3.11 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
7e0649b42eb84b1313e58c67c8cfeb5e899cd2dee8048c980bb3093b8df7739d
|
|
BLAKE2b-256 checksum How to use checksums |
8586be9b29dbbbd6c1a0de5e4132c1a3fcfb9d29e496f2117bfe9592e0418635
|
| 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 2, 2026.
Transparency logRelease files / orcher_sdk-0.4.0-cp311-abi3-musllinux_1_2_aarch64.whl
| Download URL | orcher_sdk-0.4.0-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 |
29d38a4c1922b983fa5c161f4498d5d356acda5d2e7d0557a004a63610d7808e
|
|
BLAKE2b-256 checksum How to use checksums |
d0974ea6ec542779d50b1a013a2079edcd38a4efd6a886f8ca6b58a3fbfd47a5
|
| 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 2, 2026.
Transparency logRelease files / orcher_sdk-0.4.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | orcher_sdk-0.4.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 5.2 MB |
| Tags | CPython 3.11 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
cc69f7a62d332f5695fef638b481cb177ac0fa6c4292df66e3ccd6181f69ea2a
|
|
BLAKE2b-256 checksum How to use checksums |
eee091d6b9021906a8b1b64cf3ad7bb250acfef0df31560b7a79f68e5a71233c
|
| 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 2, 2026.
Transparency logRelease files / orcher_sdk-0.4.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | orcher_sdk-0.4.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 5.5 MB |
| Tags | CPython 3.11 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
ba1d21cd22dd25dd8b18a7424858857c420d64c0bf856fb7b30700fc74544931
|
|
BLAKE2b-256 checksum How to use checksums |
9f01ff2dab02dc5097ceb1bf293a5ee1bd8ab2944ce5238f8f64f1f24ba7ccf7
|
| 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 2, 2026.
Transparency logRelease files / orcher_sdk-0.4.0-cp311-abi3-macosx_11_0_arm64.whl
| Download URL | orcher_sdk-0.4.0-cp311-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 4.8 MB |
| Tags | CPython 3.11 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
deb1e01e98e75ff3b647d597c6a47b2cdce7dc655d4787f72a63ae96460ec385
|
|
BLAKE2b-256 checksum How to use checksums |
2301de5505389864afb1e9f61545d373572aa9577fa3fb7c0abd34662f5ea8c8
|
| 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 2, 2026.
Transparency logRelease files / orcher_sdk-0.4.0-cp311-abi3-macosx_10_12_x86_64.whl
| Download URL | orcher_sdk-0.4.0-cp311-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 4.9 MB |
| Tags | CPython 3.11 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
42eed35c82daa22c03b221f47a957cac144212313a5bf1b128916eaf196bfd50
|
|
BLAKE2b-256 checksum How to use checksums |
14c3ec7c3adfd867b1bee352baf58bf00641545c9c8c128ac29acfd42734ea87
|
| 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 2, 2026.
Transparency log