{"id":49527456,"url":"https://github.com/durable-workflow/sdk-python","last_synced_at":"2026-05-18T08:08:04.067Z","repository":{"id":351238056,"uuid":"1210022537","full_name":"durable-workflow/sdk-python","owner":"durable-workflow","description":"Durable Workflow Python SDK","archived":false,"fork":false,"pushed_at":"2026-05-09T02:34:26.000Z","size":671,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-09T04:35:19.688Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/durable-workflow.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-14T02:32:54.000Z","updated_at":"2026-05-09T02:34:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/durable-workflow/sdk-python","commit_stats":null,"previous_names":["durable-workflow/sdk-python"],"tags_count":43,"template":false,"template_full_name":null,"purl":"pkg:github/durable-workflow/sdk-python","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/durable-workflow%2Fsdk-python","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/durable-workflow%2Fsdk-python/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/durable-workflow%2Fsdk-python/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/durable-workflow%2Fsdk-python/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/durable-workflow","download_url":"https://codeload.github.com/durable-workflow/sdk-python/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/durable-workflow%2Fsdk-python/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33170451,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-18T05:43:36.989Z","status":"ssl_error","status_checked_at":"2026-05-18T05:43:19.133Z","response_time":71,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-02T04:02:37.926Z","updated_at":"2026-05-18T08:08:04.060Z","avatar_url":"https://github.com/durable-workflow.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Durable Workflow (Python SDK)\n\nA Python SDK for the [Durable Workflow server](https://github.com/durable-workflow/server). Speaks the server's language-neutral HTTP/JSON worker protocol — no PHP runtime required.\n\nStatus: **Beta** — production-readiness validation in progress for the first `1.0.0` release candidate. Core features implemented: workflows, activities, schedules, signals, timers, child workflows, continue-as-new, side effects, version markers, worker-applied accepted updates, replay-verify and history-bundle-verify CLIs, the in-process `WorkflowEnvironment` test harness, and invocable activity carriers for HTTP and serverless runtimes. Client calls for queries and updates exist; Python workflow-side query receiver metadata is available, while server-routed Python query execution and pre-accept update validator routing are still in progress on the server side. Full language-neutral protocol support for cross-PHP/Python orchestration is the release goal.\n\n## Install\n\n```bash\npip install durable-workflow\n```\n\nOr for development:\n\n```bash\npip install -e '.[dev]'\n```\n\n## Quickstart\n\n```python\nimport asyncio\n\nfrom durable_workflow import Client, Worker, workflow, activity\n\n@activity.defn(name=\"greet\")\ndef greet(name: str) -\u003e str:\n    return f\"hello, {name}\"\n\n@workflow.defn(name=\"greeter\")\nclass GreeterWorkflow:\n    def run(self, ctx, name):\n        result = yield ctx.schedule_activity(\"greet\", [name])\n        return result\n\nasync def main():\n    client = Client(\"http://server:8080\", token=\"dev-token-123\", namespace=\"default\")\n    worker = Worker(\n        client,\n        task_queue=\"python-workers\",\n        workflows=[GreeterWorkflow],\n        activities=[greet],\n    )\n    handle = await client.start_workflow(\n        workflow_type=\"greeter\",\n        workflow_id=\"greet-1\",\n        task_queue=\"python-workers\",\n        input=[\"world\"],\n    )\n    await worker.run_until(workflow_id=\"greet-1\", timeout=30.0)\n    result = await client.get_result(handle)\n    print(result)  # \"hello, world\"\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\nFor a fuller deployable example, see\n[`examples/order_processing`](examples/order_processing), which runs a\nmulti-activity order workflow against a local server with Docker Compose.\n\n## Retry policy scopes\n\nRetry and timeout settings are scoped to the layer where you configure them:\n\n- `TransportRetryPolicy` on `Client(...)` retries SDK HTTP requests only. It handles transient connection failures, request timeouts, 5xx responses, and 429 rate limits. It does not retry workflow code, activity code, child workflows, or failed workflow runs.\n- `ActivityRetryPolicy` on `ctx.schedule_activity(...)` is recorded into durable history with that activity command. It controls server-side attempts for that one activity execution.\n- `ChildWorkflowRetryPolicy` on `ctx.start_child_workflow(...)` is recorded with that child-start command. It controls server-side attempts for that child workflow execution.\n- `non_retryable_error_types` belongs to durable activity/child retry policies. `non_retryable=True` on an activity failure bypasses the activity retry budget and surfaces the failure to the workflow.\n\nTimeout names are also layer-specific. `start_to_close_timeout` limits one activity attempt, `schedule_to_start_timeout` limits queue wait before an activity starts, `schedule_to_close_timeout` limits the whole activity execution including retries, and `heartbeat_timeout` limits the gap between activity heartbeats. For child workflows, `execution_timeout_seconds` covers the overall child workflow execution and `run_timeout_seconds` covers one run.\n\n## Activity failure payloads\n\nWhen replay raises `ActivityFailed`, the top-level attributes expose the\nstable cross-language fields: `activity_type`, `failure_category`,\n`exception_type`, `message`, `non_retryable`, and `code`. The\n`exception_payload` dictionary is filtered to language-neutral keys such as\n`type`, `message`, `details`, `details_payload_codec`, and `non_retryable`.\nRuntime diagnostics like PHP or Python exception classes, source file paths,\nline numbers, and traces are not included by default unless the history event\ncontains an explicit `diagnostics` or `runtime_diagnostics` envelope.\n\n## Activity retries and timeouts\n\nConfigure per-call activity retries and deadlines from workflow code:\n\n```python\nfrom durable_workflow import ActivityRetryPolicy\n\nresult = yield ctx.schedule_activity(\n    \"charge-card\",\n    [order],\n    retry_policy=ActivityRetryPolicy(\n        max_attempts=4,\n        initial_interval_seconds=1,\n        backoff_coefficient=2,\n        maximum_interval_seconds=30,\n        non_retryable_error_types=[\"ValidationError\"],\n    ),\n    start_to_close_timeout=120,\n    schedule_to_close_timeout=300,\n    heartbeat_timeout=15,\n)\n```\n\nChild workflow starts use the same retry policy shape and workflow-level\nexecution/run timeout names:\n\n```python\nfrom durable_workflow import ChildWorkflowRetryPolicy\n\nreceipt = yield ctx.start_child_workflow(\n    \"payment.child\",\n    [order],\n    retry_policy=ChildWorkflowRetryPolicy(\n        max_attempts=3,\n        initial_interval_seconds=2,\n        backoff_coefficient=2,\n        non_retryable_error_types=[\"ValidationError\"],\n    ),\n    execution_timeout_seconds=600,\n    run_timeout_seconds=120,\n)\n```\n\n## Workflow signals, queries, and updates\n\nSignals mutate workflow state during replay:\n\n```python\n@workflow.defn(name=\"approval\")\nclass ApprovalWorkflow:\n    def __init__(self) -\u003e None:\n        self.approved = False\n\n    @workflow.signal(\"approve\")\n    def approve(self, by: str) -\u003e None:\n        self.approved = True\n\n    @workflow.query(\"status\")\n    def status(self) -\u003e dict:\n        return {\"approved\": self.approved}\n\n    @workflow.update(\"set_approval\")\n    def set_approval(self, approved: bool) -\u003e dict:\n        self.approved = approved\n        return {\"approved\": self.approved}\n\n    @set_approval.validator\n    def validate_set_approval(self, approved: bool) -\u003e None:\n        if not isinstance(approved, bool):\n            raise ValueError(\"approved must be boolean\")\n```\n\nThe Python SDK records query and update receiver metadata on workflow classes,\nexposes a query-state replay helper, and applies accepted updates on Python\nworkflow tasks by emitting `complete_update` or `fail_update` back to the\nserver. Query routing and synchronous pre-accept update validator execution are\nstill server-side follow-ups; use those paths only with deployments that\nadvertise support for the target workflow type.\n\nUse `yield ctx.wait_condition(lambda: self.approved, key=\"approved\",\ntimeout=30)` to wait for signal- or update-mutated workflow state without\npolling timers by hand. The SDK sends a stable predicate fingerprint with the\ndurable wait command and rejects replay if history records a different wait\nkey or predicate fingerprint, so condition changes fail visibly instead of\nsilently resolving a different wait.\n\nWorkers fingerprint registered workflow class definitions and advertise those\nfingerprints during registration. Re-registering the same `worker_id` with a\nchanged class body for an already advertised workflow type raises immediately;\nrestart the worker process with a new id before serving changed workflow code.\n\nWorkers also advertise their local workflow and activity concurrency limits\nduring registration. Tune `max_concurrent_workflow_tasks` and\n`max_concurrent_activity_tasks` on `Worker(...)` to align local semaphores with\nthe server's task-queue admission and operator visibility surfaces. Use\n`Client.list_task_queues()` or `Client.describe_task_queue(\"orders\")` to read\nthe server-side workflow, activity, and query-task admission status before\ntuning those local limits:\n\n```python\nqueues = await client.list_task_queues()\nfor queue in queues.task_queues:\n    workflow_admission = queue.admission.workflow_tasks if queue.admission else None\n    print(queue.name, workflow_admission.status if workflow_admission else \"unknown\")\n```\n\nThe workflow and activity admission objects expose both queue-level and\nnamespace-level server budgets, including active lease caps and per-minute\ndispatch-rate limits, so automation can detect whether local worker slots,\nqueue caps, namespace caps, or downstream dispatch budget groups are\nconstraining throughput.\n\n## Replay captured histories\n\nUse `Replayer` to debug a captured history without connecting to a live server:\n\n```python\nfrom durable_workflow import Replayer\n\nreplayer = Replayer(workflows=[ApprovalWorkflow])\noutcome = replayer.replay(history_export)\n\nfor command in outcome.commands:\n    print(command)\n```\n\n`history_export` can be the server's event list or a dictionary with an\n`events` key. When the history contains a `WorkflowStarted` event, the replayer\ninfers the workflow type and input from that event; otherwise pass\n`workflow_type=` and `start_input=` explicitly. The returned `ReplayOutcome`\ncontains the commands the workflow would emit next, including determinism\nfailures surfaced as workflow failure commands.\n\nFor CI and operator replay gates, the package also installs offline\nverification commands:\n\n```bash\ndurable-workflow-replay-verify tests/fixtures/golden_history \\\n  --workflows my_app.workflows:all_workflows \\\n  --output replay-report.json\n\ndurable-workflow-replay-verify exported-history-bundles \\\n  --simulate-bundles \\\n  --output replay-simulation.json\n\ndurable-workflow-history-bundle-verify exported-history-bundles/run-001.json \\\n  --output integrity-report.json\n```\n\n`durable-workflow-replay-verify` emits the same verdict and\n`promotion_decision` vocabulary as the platform replay contract. Golden-history\nmode replays cross-runtime fixtures against registered workflow classes;\n`--simulate-bundles` integrity-checks every exported history bundle in a\ndirectory and reports missing bundle evidence as a blocking result. Because\nbundle simulation does not execute workflow code in Python, a clean\nintegrity-only simulation recommends `review_before_promote` rather than\n`safe_to_promote`.\n\n## External payload storage\n\nLarge payload offload is opt-in. `serializer.external_storage_envelope(...)`\nkeeps small encoded payloads inline and stores larger bytes through an\n`ExternalStorageDriver`, returning a stable reference envelope with URI, codec,\nsize, optional expiry, and SHA-256 integrity metadata.\n`serializer.decode_envelope(...)` fetches referenced bytes through the same\ndriver and verifies size/hash before decode.\n\nThe SDK includes a local filesystem driver for development plus dependency-free\nS3, GCS, and Azure Blob adapters. Cloud SDKs stay application-owned: pass an\nalready-configured boto3-compatible S3 client, google-cloud-storage client, or\nazure-storage-blob container client when your deployment enables external\npayload storage.\nRetention cleanup should delete by typed reference rather than by raw URI:\n`delete_external_payload(storage, reference, cache=cache)` calls the configured\ndriver and evicts any verified replay-cache entry for the same reference.\nWhen the server or Cloud API returns an external payload storage policy, use\n`ExternalPayloadStoragePolicy.from_dict(...)` plus\n`external_storage_driver_from_policy(...)` to turn that control-plane payload\ninto the matching SDK driver while keeping provider clients application-owned.\n\n```python\nfrom durable_workflow import (\n    ExternalPayloadStoragePolicy,\n    external_storage_driver_from_policy,\n    serializer,\n)\n\npolicy = ExternalPayloadStoragePolicy.from_dict(namespace_response)\nstorage = external_storage_driver_from_policy(policy, s3_client=s3_client)\npayload = serializer.external_storage_envelope(\n    {\"large\": \"value\"},\n    external_storage=storage,\n    threshold_bytes=policy.threshold_bytes or 2 * 1024 * 1024,\n)\n```\n\n## Features\n\n- **Async-first**: Built on `httpx` and `asyncio`\n- **Type-safe**: Full type hints, passes `mypy --strict`\n- **Polyglot**: Works alongside PHP workers on the same task queue\n- **HTTP/JSON protocol**: No gRPC, no protobuf dependencies\n- **Codec envelopes**: Avro payloads by default, with JSON decode compatibility for existing history\n- **External payload references**: opt-in reference envelopes, local filesystem/S3/GCS/Azure Blob drivers, and a bounded verified-byte cache for large-payload offload experiments\n- **Payload-size warnings**: Structured warnings before oversized workflow, activity, schedule, signal, update, query, or search-attribute payloads reach the server\n- **Workflow definition guard**: Worker registration refuses same-id hot reloads when a workflow class definition changed\n- **Deterministic workflow helpers**: `ctx.now()`, `ctx.random()`, `ctx.uuid4()`, and `ctx.uuid7()` replay from workflow state\n- **Worker interceptors**: Typed hooks around workflow tasks, activity calls, and query tasks for tracing, logging, and custom metrics\n- **Metrics hooks**: Pluggable counters and histograms, with an optional Prometheus adapter\n\n## Payload-size warnings\n\nThe SDK logs a structured warning before an encoded payload reaches 80% of the\ndefault 2 MiB server payload limit. Warnings include context such as\n`workflow_id`, `workflow_type`, `activity_name`, `schedule_id`, `signal_name`,\n`update_name`, `query_name`, `payload_size`, `threshold_bytes`, and\n`limit_bytes` when those fields are known at the call site.\n\nTune or disable the warning threshold on the client:\n\n```python\nclient = Client(\n    \"https://workflow.example.internal\",\n    payload_size_limit_bytes=4 * 1024 * 1024,\n    payload_size_warning_threshold_percent=75,\n)\n\nquiet_client = Client(\n    \"https://workflow.example.internal\",\n    payload_size_warnings=False,\n)\n```\n\n## Avro payload type boundaries\n\nThe default Avro codec uses a generic JSON wrapper so PHP, Python, and other\nworkers can exchange the same wire format. It preserves JSON-native values:\n`None`, booleans, numbers, strings, lists, and dictionaries with string keys.\n\nClass-carrying values are not encoded with type metadata. Convert pydantic\nmodels, attrs classes, dataclasses, pendulum values, `datetime` / `date` /\n`time`, `UUID`, `Decimal`, and plain `Enum` values to explicit dictionaries or\nscalars before passing them to the SDK. `IntEnum` and `StrEnum` encode because\nthey are JSON scalar subclasses, but they decode as `int` and `str`.\n`OrderedDict` decodes as a plain `dict`.\n\nUse `to_avro_payload_value(...)` when a rich value should enter durable\nhistory through the default Avro envelope:\n\n```python\nfrom dataclasses import dataclass\nfrom datetime import datetime, timezone\nfrom decimal import Decimal\nfrom enum import Enum\nfrom uuid import UUID\n\nfrom durable_workflow import Client, to_avro_payload_value\n\n\nclass OrderStatus(Enum):\n    PENDING = \"pending\"\n\n\n@dataclass\nclass OrderInput:\n    order_id: UUID\n    placed_at: datetime\n    amount: Decimal\n    status: OrderStatus\n\n\norder = OrderInput(\n    order_id=UUID(\"12345678-1234-5678-1234-567812345678\"),\n    placed_at=datetime.now(timezone.utc),\n    amount=Decimal(\"10.25\"),\n    status=OrderStatus.PENDING,\n)\n\nclient = Client(\"http://server:8080\", token=\"dev-token-123\")\nawait client.start_workflow(\n    \"order-workflow\",\n    task_queue=\"orders\",\n    workflow_id=\"order-123\",\n    input=[to_avro_payload_value(order)],\n)\n```\n\nThe helper also accepts pydantic-style models with `model_dump(mode=\"json\")`\nand attrs-style classes. Rebuild domain objects explicitly inside workflows or\nactivities, for example `OrderInput(order_id=UUID(data[\"order_id\"]), ...)`.\nAdapter output is part of the durable history contract, so changing that shape\nis a workflow compatibility change.\n\n## Authentication\n\nFor local servers that use one shared bearer token, pass `token=`:\n\n```python\nclient = Client(\"http://server:8080\", token=\"shared-token\", namespace=\"default\")\n```\n\nFor production servers with role-scoped tokens, pass separate credentials for\ncontrol-plane calls and worker-plane polling:\n\n```python\nclient = Client(\n    \"https://workflow.example.internal\",\n    control_token=\"operator-token\",\n    worker_token=\"worker-token\",\n    namespace=\"orders\",\n)\n```\n\nCreate one client per namespace when your deployment issues namespace-scoped\ntokens. The SDK sends the configured token as `Authorization: Bearer ...` and\nthe namespace as `X-Namespace` on every request.\n\n## Metrics\n\nPass a recorder to `Client(metrics=...)` or `Worker(metrics=...)` to collect request, poll, and task metrics. The SDK ships a no-op default, an `InMemoryMetrics` recorder for tests or custom exporter loops, and `PrometheusMetrics` for deployments that install the optional extra:\n\n```bash\npip install 'durable-workflow[prometheus]'\n```\n\n```python\nfrom durable_workflow import Client, PrometheusMetrics\n\nmetrics = PrometheusMetrics()\nclient = Client(\"http://server:8080\", token=\"dev-token-123\", metrics=metrics)\n```\n\nCustom recorders implement `increment(name, value=1.0, tags=None)` and `record(name, value, tags=None)`.\n\n## Worker interceptors\n\nUse `Worker(interceptors=[...])` when instrumentation needs the task payload,\nresult, or exception around worker execution instead of only aggregate counters.\nInterceptors run in list order; the first interceptor is the outermost wrapper.\n\n```python\nfrom durable_workflow import (\n    ActivityInterceptorContext,\n    ActivityHandler,\n    PassthroughWorkerInterceptor,\n    Worker,\n)\n\nclass LoggingInterceptor(PassthroughWorkerInterceptor):\n    async def execute_activity(\n        self,\n        context: ActivityInterceptorContext,\n        next: ActivityHandler,\n    ) -\u003e object:\n        print(\"activity started\", context.activity_type)\n        try:\n            result = await next(context)\n        except Exception:\n            print(\"activity failed\", context.activity_type)\n            raise\n        print(\"activity completed\", context.activity_type)\n        return result\n\nworker = Worker(\n    client,\n    task_queue=\"python-workers\",\n    workflows=[GreeterWorkflow],\n    activities=[greet],\n    interceptors=[LoggingInterceptor()],\n)\n```\n\n## Documentation\n\nFull documentation is available at\n[durable-workflow.github.io/docs/2.0/polyglot/python](https://durable-workflow.github.io/docs/2.0/polyglot/python):\n\n- [Python SDK guide](https://durable-workflow.com/docs/2.0/polyglot/python)\n- [API reference](https://python.durable-workflow.com/)\n\n## Requirements\n\n- Python ≥ 3.10\n- A running [Durable Workflow server](https://github.com/durable-workflow/server)\n\n## Compatibility\n\nSDK version 0.4.x is compatible with servers that advertise these protocol\nmanifests from `GET /api/cluster/info`:\n\n- `control_plane.version: \"2\"`\n- `control_plane.request_contract.schema: durable-workflow.v2.control-plane-request.contract` version `1`\n- `auth_composition_contract.schema: durable-workflow.v2.auth-composition.contract` version `1`\n- `worker_protocol.version: \"1.1\"`\n- `worker_protocol.external_task_input_contract.schema: durable-workflow.v2.external-task-input.contract` version `1`\n- `worker_protocol.external_task_result_contract.schema: durable-workflow.v2.external-task-result.contract` version `1`\n\nThe top-level server `version` is build identity only. The worker checks these\nprotocol manifests at startup and fails closed when compatibility is missing,\nunknown, or undiscoverable.\n\nCarriers and support tooling can validate `auth_composition_contract` with\n`parse_auth_composition_contract()` before resolving connection, namespace,\ntoken, TLS, profile, and redacted effective-configuration diagnostics.\n\nExternal task carriers can validate fixture artifacts from\n`worker_protocol.external_task_input_contract.fixtures` with\n`parse_external_task_input_artifact()` and parse leased task envelopes with\n`parse_external_task_input()`.\n\nThey can also validate result fixture artifacts from\n`worker_protocol.external_task_result_contract.fixtures` with\n`parse_external_task_result_artifact()` and parse result envelopes with\n`parse_external_task_result()`. The result parser exposes stable carrier\ndecisions for success, retryability, malformed output, cancellation, deadline\nexceeded, handler crash, decode failure, and unsupported payload\ncodec/reference states without treating stderr as a machine signal.\n\nInvocable activity carriers can use `InvocableActivityHandler` as a reference\nadapter for HTTP or serverless runtimes. It accepts the same external-task input\nenvelope, invokes a registered activity handler, and returns the same\nexternal-task result envelope while rejecting workflow-task inputs:\n\n```python\nfrom durable_workflow import InvocableActivityHandler\n\nadapter = InvocableActivityHandler({\"billing.charge-card\": charge_card})\nresult_envelope = await adapter.handle(request_json)\n```\n\nBridge adapters can hand bounded webhook ingress into the server through\n`Client.send_webhook_bridge_event()`. The method returns the server's typed\nbridge outcome for accepted, duplicate, and rejected events, including\nmachine-readable HTTP 422 rejection outcomes:\n\n```python\noutcome = await client.send_webhook_bridge_event(\n    \"pagerduty\",\n    action=\"signal_workflow\",\n    idempotency_key=\"pagerduty-event-3003\",\n    target={\"workflow_id\": \"wf-remediation-42\", \"signal_name\": \"incident_escalated\"},\n    input={\"severity\": \"critical\", \"service\": \"checkout\"},\n    correlation={\"provider\": \"pagerduty\", \"event_type\": \"incident.triggered\"},\n)\n\nif outcome.accepted:\n    print(outcome.workflow_id, outcome.control_plane_outcome)\nelse:\n    print(outcome.outcome, outcome.reason)\n```\n\n## Development\n\n```bash\n# Install dev dependencies\npip install -e '.[dev]'\n\n# Run tests\npytest\n\n# Run integration tests (requires Docker)\npytest -m integration\n\n# Type check\nmypy src/durable_workflow/\n\n# Lint\nruff check src/ tests/\n\n# Preview the API reference site locally\npip install -e '.[docs]'\nmkdocs serve\n```\n\nThe API reference is published to [python.durable-workflow.com](https://python.durable-workflow.com/) and rebuilt automatically on push to `main`.\n\n## License\n\nMIT\n\n## Public Boundary Checks\n\nThis is a public repository. Do not add private tracker names, workspace-only absolute paths, or internal automation metadata to files or new commit metadata. Run `scripts/check-public-boundary.sh` before publishing changes; CI runs the same scan on pushes and pull requests.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdurable-workflow%2Fsdk-python","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdurable-workflow%2Fsdk-python","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdurable-workflow%2Fsdk-python/lists"}