Every payload that crosses the worker-protocol boundary is codec-tagged.
Durable Workflow 2.0 has one public payload codec:
avro.
The Python SDK rejects JSON-tagged, unknown, and untagged durable payloads
instead of selecting another decoder. See the
Avro Value protocol.
### Avro support is built in
The pinned 2.0 SDK artifact pulls in `fastavro` as a runtime dependency, so
every outgoing surface (`start_workflow`, `signal_workflow`, `query_workflow`,
`update_workflow`, activity result encoding, schedule actions) emits
Avro-tagged payloads through the optimized production path. There is no
optional codec extra to install.
### Fixed typed values
The SDK explicitly selects named branches in the shared
`durable_workflow.protocol.Value` schema. It does not JSON-encode the value
inside Avro. Integers and doubles, strings and bytes, booleans and integers,
and lists and maps therefore remain distinct across SDKs. The wire uses Avro
single-object framing and the schema fingerprint; unknown fingerprints fail
with `unsupported_payload_schema`.
Every client and worker surface works end-to-end on the Avro default:
- **Client starts, signals, queries, updates** — `start_workflow`, `signal_workflow`, `query_workflow`, and `update_workflow` emit `payload_codec = "avro"` payloads through the fixed Value schema. A Python client can therefore drive workflows that PHP and other polyglot SDKs will replay, and vice-versa.
- **Activity worker** — Avro-tagged activity arguments decode transparently. The worker encodes activity results as Avro so PHP, Python, and future SDK workers share one payload boundary.
- **Activity failures** — `fail_activity_task(..., details=...)` sends `failure.details` as a `{codec, blob}` envelope. The server records the blob plus `details_payload_codec`, so diagnostic failure data from Python workers remains language-neutral in history exports and observability views.
- **Workflow worker history replay** — Avro-tagged start input and activity result events are decoded during replay, so a Python workflow can participate in an Avro-coded run.
- **Workflow query tasks** — Server-routed query tasks carry Avro-tagged workflow arguments, query arguments, and replay history. The worker returns the query result as an Avro envelope.
### Running a Python activity worker against a v2 run
No codec configuration is needed. The SDK validates `payload_codec` before
decoding every claim, accepts only `avro`, runs the activity, and returns an
Avro-tagged result. Any other tag fails closed with
`unsupported_payload_codec`; the worker never sniffs or guesses the format.
### Types that round-trip cleanly across Python and PHP
| Python type | Avro Value branch | PHP type |
|-------------|------|----------|
| `str` | `StringValue` | `string` |
| `bytes` | `BytesValue` | `AvroBinaryValue` |
| `int` | `LongValue` | `int` |
| `float` | `DoubleValue` | `float` |
| `bool` | `BooleanValue` | `bool` |
| `None` | `null` | `null` |
| `list` | `ArrayValue` | `array` (list) |
| `dict[str, ...]` | `MapValue` | `array` (string-keyed map) |
### Typed activity values {#typed-activity-values}
The selected stable SDK supports dataclasses and enums at activity boundaries.
It sends a dataclass as a string-keyed map of its fields and an `Enum` as its
`.value`, preserving the same Avro bytes as explicit neutral values.
| Boundary | Published opt-in behavior |
| --- | --- |
| Activity arguments | `@activity.defn(..., typed=True)` decodes positional arguments using parameter annotations. |
| Activity results | `ctx.schedule_activity(..., result_type=T)` decodes the completed result at the workflow's yield, including parallel groups and `ctx.select(...)`. |
| Workflow, child, client, signal, query, update and validator values | The broader adapters in [SDK #255](https://github.com/durable-workflow/sdk-python/pull/255) are merged but unreleased. Keep explicit neutral-value conversion for these boundaries. |
Supported receiving annotations are dataclasses, `Enum` (including `IntEnum`
and `StrEnum`), `Optional[T]` / `T | None`, `list[T]`, `dict[str, T]`, `str`,
`int`, `float`, `bool`, `bytes` and `Any`. Other annotations, including a union
of two non-`None` types, raise `TypeError` at declaration or call time.
Decoding remains opt-in. `result_type` is local and is never sent, compared or
written to history. See the [published SDK reference](https://github.com/durable-workflow/sdk-python/blob/2.14.1/docs/sdk-reference.md#typed-activity-values)
for defaults, unknown fields and `TypedValueError` handling.
Sets, tuples, datetime, UUID, Decimal, pydantic and attrs objects still require
an explicit adapter to canonical strings, numbers, maps or lists. Changing that
durable value shape is a workflow compatibility change.
## Running Against a Shared Server
The [Quickstart](#quickstart) above shows how to bring up a local server with
the published Docker image. In a team environment you usually point the Python
worker at an existing server (staging, production, or a shared dev instance):
```python
from durable_workflow import Client
client = Client(
"https://workflow.example.internal",
control_token="team-orders-operator-token",
worker_token="team-orders-worker-token",
namespace="team-orders",
)
```
Set the `namespace` argument to whichever tenant namespace the shared server has
provisioned for your team, and use the credentials issued for that namespace.
The Server operator manages namespace creation; see the
[Server guide](/docs/polyglot/server) for details.
The same constructor supports a Cloud managed runtime. Use the namespace's
Cloud-provided runtime URL and namespace value, set `control_token` to the
client runtime credential, and set `worker_token` to the worker runtime
credential. Cloud provisions the namespace; do not substitute a self-hosted
Server address. See
[Cloud Managed Runtime](/docs/polyglot/cloud-control-plane).
## Workflow list and history pages {#workflow-list-and-history-pages}
The selected stable Python SDK provides lazy `Client.iter_workflows`,
`Client.iter_history` and `WorkflowHandle.iter_history`. Use `async for` to
consume workflow executions or history event dictionaries in Server order.
See the [SDK-owned examples and pagination reference](https://python.durable-workflow.com/sdk-reference/#whole-run-histories-and-workflow-lists)
and [published client source](https://github.com/durable-workflow/sdk-python/blob/2.17.2/src/durable_workflow/client.py).
Each iterator fetches one page at a time without retaining earlier result pages.
It follows empty pages that have a continuation and stops when the continuation
is missing, `None` or empty. Stopping iteration fetches no subsequent page.
HTTP errors, cancellation, history pages whose `events` field is not a list of
dictionaries, and malformed or cyclic continuations propagate. Earlier yielded items are not rolled back, so
handle an interrupted traversal as incomplete.
Keep namespace, filters and page size fixed throughout a traversal. History
also requires the same explicit `workflow_id` and `run_id`; the handle helper
refuses a handle without `run_id`. A namespace change is refused before the
next request. Supply `next_page_token` to resume at an opaque continuation
from the same selection. Do not parse, alter or reuse it for different filters,
namespaces or runs. Traversing a changing workflow list does not create a
snapshot.
The existing `await client.list_workflows(...)` and
`await client.get_history(workflow_id, run_id, ...)` still return **one page**.
For manual paging, pass the returned `next_page_token` to the next call and
stop at the final missing/null/empty token. A partial page or a sequence of
event dictionaries is not a replay bundle. Use
`await client.export_history(workflow_id, run_id)` or
`await handle.export_history()` for offline replay.
The [installed SDK 2.17.2 / native Server 3.9.2 pagination check](https://github.com/durable-workflow/sdk-python/pull/332#issuecomment-6877331165)
covered filtered lists, selected-run history, opaque resume, early close and
namespace refusal, with [independent acceptance](https://github.com/durable-workflow/sdk-python/pull/332#issuecomment-6877373099).
# Rust Cloud Quickstart
:::caution Controlled early access
Durable Workflow Cloud is available through controlled early access. Use this
guide only after Cloud has provisioned a namespace and two role-scoped runtime
credentials. The generally available Rust journey does not require Cloud; start
with the [Rust SDK guide](./rust.md) or run `scripts/playground rust` against
the playground's default local runtime.
:::
The Sample App exposes one symmetric playground for PHP, Python, and Rust. This
page selects Rust and changes only the runtime target. The corresponding
[PHP](./php.md) and [Python](./python.md) SDK paths use the same command with
`php` or `python`; the [Sample App playground
contract](https://github.com/durable-workflow/sample-app/blob/main/README.md#symmetric-sdk-playground)
documents all three choices.
The playground resolves the current stable artifact versions from the Sample
App's machine-owned metadata. Use those generated versions instead of copying
version numbers into these commands or adding a separate SDK installation step.
## 1. Open the prepared Sample App
[Create a Codespace from the Sample App `main`
branch](https://codespaces.new/durable-workflow/sample-app?quickstart=1&ref=main),
wait for setup to finish, and open a terminal at the repository root. The
prepared image contains the SDK toolchains and `dw` required by the shared
playground.
Cloud provides the runtime URL, runtime namespace, and two role credentials.
Choose an application task queue, then export these placeholders only after
replacing them with the corresponding values:
```bash
export DURABLE_WORKFLOW_RUNTIME_URL='