Durable Workflow 2.0 Quickstart
Before you begin
Goal: run one service-mode workflow and read its completed durable result from PHP, Python, or Rust.
Expected time: about 15 minutes after your runtime is available.
Completed outcome: the selected SDK starts a worker and workflow, then
prints a workflow ID, status=completed, and Hello, <language>!.
Prerequisites:
curland a terminal- Docker for the self-hosted local path, or a provisioned Durable Workflow Cloud namespace
- one language toolchain: PHP 8.1+ with Composer, Python 3.10+, or Rust 1.86+
You do not need Laravel for service mode. The embedded Laravel path is separate at the end of this guide.
1. Choose your service-mode runtime
| Runtime | Choose it when | Next action |
|---|---|---|
| Durable Workflow Cloud | You want Durable Workflow to operate the runtime, persistence, and Managed Waterline. | Follow the executable Cloud first workflow, which maps PHP, Python, and Rust complete sources to provisioned credentials and a completed result. Do not run Server or a separate Waterline service. |
| Self-hosted Server | You want to operate the runtime yourself or run this exact local published-artifact exercise. | Continue below with Docker and curl; deploy Waterline separately only when you want its operator UI. |
The runnable source below uses a local self-hosted Server so it can be exercised without an account or source checkout. Cloud uses the same SDK and worker model; replace the local development connection with the provisioned values shown in Cloud Managed Runtime.
2. Start the local Server
Skip this action when you chose Cloud. For the self-hosted path, expand and run the exact pinned setup. It starts a source-free Server with SQLite and a development token.
Start the pinned Server image
export DW_SERVER_IMAGE=durableworkflow/server:2.0.0
export DW_AUTH_TOKEN=dev-token
docker volume create durable-workflow-quickstart
docker run --rm \
-v durable-workflow-quickstart:/app/database \
-e DW_AUTH_DRIVER=token \
-e DW_AUTH_TOKEN="$DW_AUTH_TOKEN" \
"$DW_SERVER_IMAGE" server-bootstrap
docker rm -f durable-workflow-server >/dev/null 2>&1 || true
docker run -d --name durable-workflow-server \
-p 8080:8080 \
-v durable-workflow-quickstart:/app/database \
-e DW_AUTH_DRIVER=token \
-e DW_AUTH_TOKEN="$DW_AUTH_TOKEN" \
"$DW_SERVER_IMAGE"
until curl -sf http://localhost:8080/api/ready >/dev/null; do sleep 1; done
curl -H "Authorization: Bearer $DW_AUTH_TOKEN" \
http://localhost:8080/api/cluster/info
Expected result: the readiness request succeeds and cluster info identifies the local standalone Server. Keep it running while you complete one language route.
3. Choose one language
All three first-party SDKs are available at the same level. Only the selected tab is shown, so you can follow one path without scrolling past two other programs.
- PHP
- Python
- Rust
Requirements: PHP 8.1 or newer and Composer. This is the framework-neutral
durable-workflow/sdk package, not the embedded Laravel engine.
- Install the SDK.
mkdir durable-workflow-php-quickstart
cd durable-workflow-php-quickstart
composer require durable-workflow/sdk:2.0.0
- Add the worker and client. Expand the complete source, then copy both files into the new project.
Complete runnable PHP source
The worker registers one workflow type and one activity type on its own task queue.
cat > worker.php <<'PHP'
<?php
declare(strict_types=1);
require __DIR__.'/vendor/autoload.php';
use DurableWorkflow\Client;
use DurableWorkflow\Worker;
use DurableWorkflow\Worker\ActivityContext;
use DurableWorkflow\Worker\WorkflowContext;
$client = new Client('http://localhost:8080', token: 'dev-token');
$worker = new Worker($client, 'quickstart-php');
$worker->registerActivity(
'quickstart.greet',
static fn (ActivityContext $context, string $name): string => "Hello, {$name}!",
);
$worker->registerWorkflow(
'quickstart.greeter',
static function (WorkflowContext $context, string $name): array {
$greeting = $context->activity('quickstart.greet', [$name]);
return ['greeting' => $greeting, 'language' => 'php'];
},
);
$worker->run();
PHP
Client and result reader
This client starts a uniquely named workflow, waits for its selected run, and then describes the durable terminal state held by the server.
cat > start.php <<'PHP'
<?php
declare(strict_types=1);
require __DIR__.'/vendor/autoload.php';
use DurableWorkflow\Client;
$client = new Client('http://localhost:8080', token: 'dev-token');
$workflowId = 'quickstart-php-greeter-'.bin2hex(random_bytes(4));
$handle = $client->startWorkflow(
workflowType: 'quickstart.greeter',
workflowId: $workflowId,
taskQueue: 'quickstart-php',
input: ['PHP'],
);
$result = $handle->result(timeoutSeconds: 30);
$execution = $handle->describeSelectedRun();
echo "workflow_id={$execution->workflowId}\n";
echo "status={$execution->status}\n";
echo 'result='.json_encode($result, JSON_THROW_ON_ERROR)."\n";
PHP
- Run the worker and client.
php worker.php > quickstart-worker.log 2>&1 &
export QUICKSTART_WORKER_PID=$!
trap 'kill "$QUICKSTART_WORKER_PID" 2>/dev/null || true' EXIT
php start.php
kill "$QUICKSTART_WORKER_PID" 2>/dev/null || true
trap - EXIT
Expected result: status=completed and a result containing
"greeting":"Hello, PHP!". You have run a standalone PHP worker and
inspected its durable result without Laravel.
Continue with the PHP SDK guide.
Requirements: Python 3.10 or newer. The program keeps the worker and client in one process, but they still communicate with the server through the public worker and control-plane APIs.
-
Install the SDK.
Use the stable Python SDK release listed in the manifest above. The generated exact requirement keeps this executable path on the documented stable release line.
mkdir durable-workflow-python-quickstart
cd durable-workflow-python-quickstart
python3 -m venv .venv
. .venv/bin/activate
pip install durable-workflow==2.0.0
- Create and run the worker and client. Expand the complete program; its final command runs it.
Complete runnable Python source
cat > greeter.py <<'PY'
import asyncio
import time
from durable_workflow import Client, Worker, activity, workflow
@activity.defn(name="quickstart.greet")
async def greet(name: str) -> dict:
return {"greeting": f"Hello, {name}!", "language": "python"}
@workflow.defn(name="quickstart.greeter")
class GreeterWorkflow:
def run(self, ctx, name):
return (yield ctx.schedule_activity("quickstart.greet", [name]))
async def main():
workflow_id = f"quickstart-python-greeter-{int(time.time())}"
async with Client(
"http://localhost:8080",
token="dev-token",
namespace="default",
) as client:
handle = await client.start_workflow(
workflow_type="quickstart.greeter",
task_queue="quickstart-python",
workflow_id=workflow_id,
input=["Python"],
)
worker = Worker(
client,
task_queue="quickstart-python",
workflows=[GreeterWorkflow],
activities=[greet],
)
await worker.run_until(workflow_id=workflow_id, timeout=30.0)
result = await handle.result(timeout=10.0)
execution = await handle.describe_run()
print(f"workflow_id={execution.workflow_id}")
print(f"status={execution.status}")
print(f"result={result}")
asyncio.run(main())
PY
python greeter.py
Expected result: status=completed and a result containing
Hello, Python!. The last two SDK calls read the selected run's result and
durable terminal state from the server.
Continue with the Python SDK guide.
Requirements: Rust 1.86 or newer. This example runs a native worker and client in one Tokio process.
- Install the SDK.
cargo new durable-workflow-rust-quickstart
cd durable-workflow-rust-quickstart
cargo add durable-workflow@=2.0.0
cargo add tokio --features macros,rt-multi-thread,time
- Create and run the worker and client. Expand the complete program; its final command compiles and runs it.
Complete runnable Rust source
cat > src/main.rs <<'RS'
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use durable_workflow::{json, Client, Result, Worker, WorkflowResultOptions};
#[tokio::main]
async fn main() -> Result<()> {
let client = Client::builder("http://localhost:8080")
.token(Some("dev-token".to_string()))
.namespace("default")
.build()?;
let task_queue = "quickstart-rust";
let mut worker = Worker::new(client.clone(), task_queue);
worker.register_activity("quickstart.greet", |_context, arguments| async move {
let name = arguments
.get(0)
.and_then(|value| value.as_str())
.unwrap_or("Rust");
Ok(json!({"greeting": format!("Hello, {name}!"), "language": "rust"}))
});
worker.register_workflow("quickstart.greeter", |context, input| async move {
let name = input.get(0).and_then(|value| value.as_str()).unwrap_or("Rust");
context.activity("quickstart.greet", json!([name])).await
});
worker.register().await?;
let workflow_id = format!("quickstart-rust-greeter-{}", unique_suffix());
let handle = client
.start_workflow(
"quickstart.greeter",
task_queue,
&workflow_id,
json!(["Rust"]),
)
.await?;
let watcher = handle.clone();
worker
.run_until(async move {
loop {
if watcher.describe().await.is_ok_and(|run| run.is_terminal()) {
break;
}
tokio::time::sleep(Duration::from_millis(500)).await;
}
})
.await?;
let result = handle.result(WorkflowResultOptions::default()).await?;
let execution = handle.describe_selected_run().await?;
println!("workflow_id={workflow_id}");
println!("status={}", execution.status.as_deref().unwrap_or("unknown"));
println!("result={result}");
Ok(())
}
fn unique_suffix() -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
}
RS
cargo run
Expected result: status=completed and a JSON result containing
"greeting":"Hello, Rust!". The example waits for its worker to finish the
run, then reads the selected run's durable status and decoded result.
Continue with the Rust SDK guide.
4. Clean up the local Server
Cloud users have no local Server to remove. For the self-hosted exercise:
docker rm -f durable-workflow-server
docker volume rm durable-workflow-quickstart
Separate path: embedded Laravel
Embedded Laravel is a separate first-party PHP deployment mode for
applications that want workflow state, queue execution, configuration, and
operator tooling inside their existing Laravel infrastructure. It installs
durable-workflow/workflow; it does not use the standalone server or
durable-workflow/sdk.
Start a fresh embedded application with the published package:
composer create-project laravel/laravel durable-workflow-laravel-quickstart
cd durable-workflow-laravel-quickstart
composer require durable-workflow/workflow:2.0.1
php artisan migrate
php artisan queue:work
When you want the operator UI inside that same Laravel application, add the qualified embedded Waterline Composer package:
composer require durable-workflow/waterline:2.0.0
php artisan waterline:install
This Composer package is not the install identity for the separately deployed self-hosted Waterline service. Embedded Laravel does not run Server or install one of the service-mode SDKs.
Continue with Embedded Installation to configure a
non-sync Laravel queue, then define
and start an embedded
workflow. Deployment Modes compares
this specialized route with the service-mode platform.
Next steps
- Use the Capability Index to check the supported surface for your selected runtime and SDK.
- Continue with the service-mode PHP SDK, Python SDK, or Rust SDK guide.
- Compare lifecycle, messages, schedules, visibility, and worker execution in Client and Worker Capabilities.
- Operate the matching runtime through Cloud Managed Runtime or the self-hosted Server, then add the CLI when shell automation is useful.
- Plan safe service-worker deployment with worker compatibility and routing and build-ID rollout.
For embedded Laravel authoring features such as timers, signals, queries, activities, and child workflows, use the separate Embedded documentation.
Release qualification is intentionally separate from this first-success guide. The Platform Conformance Suite contains the exact artifact matrix, public-source checks, full execution transcripts, wall-clock criteria, teardown, and machine-readable quickstart contract used for certification.