Skip to main content
Version: 2.0 prerelease

Sample App

https://github.com/durable-workflow/sample-app

This is a sample Laravel 13 application built on Durable Workflow 2.0 (alpha) with example workflows that you can run inside a GitHub codespace.

The sample app is the recommended starting point for "how do I learn Durable Workflow?". It is the first place new features get end-to-end coverage, the first place bugs are reproduced against a realistic Laravel host, and the source the docs site mirrors when it shows what a pattern looks like in practice. If you have a real durable-workflow pattern you want to share, the Contribute a Sample guide walks through the submission flow.

Looking for the Laravel 12 / Durable Workflow 1.x version? It's preserved on the Laravel-12 branch. Older blog posts and tutorials that reference v1 patterns (e.g. Workflow\Workflow, yield activity(...), Workflow\Activity) target that branch.

Each entry in the gallery names the pattern surface the sample teaches, the workflow class that exercises it, the artisan command that runs it, and the Waterline screen that proves the run committed. The gallery mirrors the README "Sample Index" in the sample-app repository — when a sample lands or moves, the README and this gallery move together.

PatternWorkflow classCommandWaterline screen
Smallest deterministic v2 workflowApp\Workflows\Simple\SimpleWorkflowphp artisan app:workflowRun list → run detail with two activity events and a WorkflowExecutionCompleted event
Durable elapsed-time measurement without replay driftApp\Workflows\Elapsed\ElapsedTimeWorkflowphp artisan app:elapsedRun detail showing two MarkerRecorded events for sideEffect clock reads bracketing a TimerFired event
Coordination across Laravel app boundariesApp\Workflows\Microservice\MicroserviceWorkflowphp artisan app:microserviceRun detail showing activity events with task-queue routing across the app and microservice workers
Browser automation with captured artifactsApp\Workflows\Playwright\CheckConsoleErrorsWorkflowphp artisan app:playwrightRun detail showing the Playwright activity, the FFmpeg activity, and the cleanup activity in order
Webhook-started workflow with a signal waitApp\Workflows\Webhooks\WebhookWorkflowphp artisan app:webhookRun detail showing WorkflowExecutionStarted from the webhook ingress and a WorkflowExecutionSignaled event for ready
AI activity loop with durable retry/validationApp\Workflows\Prism\PrismWorkflowphp artisan app:prismRun detail showing repeated activity attempts and the ActivityTaskCompleted that satisfies the validator
Signal-driven AI agent with saga compensationApp\Workflows\Ai\AiWorkflowphp artisan app:aiRun detail showing a message-stream MarkerRecorded reference, an Update event, and the compensation activities recorded after a saga failure

The Waterline screen column names the events you should expect to see in a healthy run; if your local run is missing one, that gap is the fastest way to localize a worker-side or environment problem.

The pattern pages on this site link directly into the sample workflow that exercises each surface. Use this table to jump from a pattern page to the matching runnable workflow.

Pattern pageSample workflow
SagasApp\Workflows\Ai\AiWorkflow (php artisan app:ai)
SignalsApp\Workflows\Webhooks\WebhookWorkflow (php artisan app:webhook)
Message StreamsApp\Workflows\Ai\AiWorkflow (php artisan app:ai)
Child WorkflowsApp\Workflows\Microservice\MicroserviceWorkflow (php artisan app:microservice)
Side EffectsApp\Workflows\Elapsed\ElapsedTimeWorkflow (php artisan app:elapsed)
WebhooksApp\Workflows\Webhooks\WebhookWorkflow (php artisan app:webhook)
MCP Workflowsevery gallery entry, exposed through config/workflow_mcp.php

Step 1

Create a codespace from the main branch of this repo.

image

Step 2

Once the codespace has been created, wait for the codespace to build. This should take between 5 to 10 minutes.

Step 3

Once it is done. You will see the editor and the terminal at the bottom.

image

Step 4

Run composer install.

composer install

Step 5

Run the init command to setup the app, install extra dependencies and run the migrations.

php artisan app:init

The sample app's php artisan migrate path picks up the workflow and Waterline package migrations directly, so all tables and Waterline saved views are ready after the normal install.

Step 6

Start the queue worker. This will enable the processing of workflows and activities.

php artisan queue:work

When you want to prove the repair loop itself without waiting for a busy queue worker to hit another Looping cycle, run one explicit recovery sweep from a second terminal:

php artisan workflow:v2:repair-pass

That command uses the same scan and backoff policy as the worker loop. It is useful after fixing a local queue/backend issue, while validating a lost-task scenario in the sample app, or when a low-traffic codespace would otherwise take a while to hit another loop pass. Add --run-id=... to limit the sweep to one or more selected runs during an experiment, or --instance-id=... when the whole instance should stay in scope.

Step 7

Create a new terminal window.

image

Step 8

Start the example workflow inside the new terminal window.

php artisan app:workflow

Step 9

You can view the waterline dashboard at https://[your-codespace-name]-80.preview.app.github.dev/waterline/dashboard.

image

Waterline is the durable-state view. It answers whether a workflow started, which run is current, which typed history events were committed, which waits are open, and which operator actions are available. Worker-side telemetry is separate: poll latency, task duration, exporter setup, custom application metrics, and worker process errors come from the PHP worker logs or from the SDK metrics endpoint of an external worker.

SurfaceAnswersSample check
Waterline and history exportDurable workflow status, history, retries, waits, signals, updates, failures, and operator actionsOpen /waterline/dashboard, then export the selected run history
Worker logsPHP queue worker process errors and application log linesTail storage/logs/laravel.log while php artisan queue:work is running
SDK metricsExternal worker/client request counts, poll latency, and task durationScrape the SDK worker's Prometheus/OpenMetrics endpoint

For a Python worker, install the Prometheus extra and expose the worker metrics from that worker process:

pip install 'durable-workflow[prometheus]'
from prometheus_client import start_http_server

from durable_workflow import Client, PrometheusMetrics, Worker

metrics = PrometheusMetrics()
start_http_server(9102)

async with Client("http://localhost:8080", token="secret", metrics=metrics) as client:
worker = Worker(
client,
task_queue="default",
workflows=[GreeterWorkflow],
activities=[greet],
metrics=metrics,
)
await worker.run()

Replace GreeterWorkflow and greet with the workflow and activity handlers registered by that worker.

Scrape :9102/metrics for durable_workflow_worker_* and durable_workflow_client_* series. Those metrics explain worker runtime performance; Waterline remains the source of truth for the committed workflow history.

Waterline's detail screen includes an "Export History" action for the selected run. When the detail screen is showing the instance's current run, that button uses the instance-scoped current-run export route; historical run detail keeps using the explicit /runs/{runId}/history-export path. You can also export the same replay/debug bundle from the sample app terminal:

php artisan workflow:v2:history-export {workflow-instance-id} --run-id={workflow-run-id} --output=storage/app/workflow-history/example.json --pretty

The export includes a SHA-256 integrity checksum. Set DW_V2_HISTORY_EXPORT_SIGNING_KEY and DW_V2_HISTORY_EXPORT_SIGNING_KEY_ID in the app environment when another system needs to verify the exported bundle with an HMAC signature.

The exported selected_run block includes waits_projection_source, timeline_projection_source, timers_projection_source, and lineage_projection_source. The exported links block also includes projection_source, and the links.parents / links.children sections come from the selected run's typed lineage history first, so child-workflow and continue-as-new relationships remain visible in the bundle even if mutable link rows have drifted during a local experiment. When a lineage row is surviving only through older mutable compatibility data, the bundle now marks that entry with history_authority = mutable_open_fallback and diagnostic_only = true instead of silently rehydrating extra link metadata during export.

AI Workflow Message Streams

Repeated AI or human-input workflows should use the first-class v2 message stream facade as their authoring pattern:

$reply = $this->inbox('ai.assistant')->receiveOne();

$this->outbox('ai.assistant')->sendReference(
targetInstanceId: $this->workflowId(),
payloadReference: $storedReplyReference,
correlationId: $requestId,
);

Keep app-owned payload storage for large request/response bodies, then pass the stored reference through the stream. Do not teach new sample workflows to write workflow_messages, MessageStreamCursor, or MessageService calls directly. See Message Streams for the stable v2 inbox/outbox contract.

Step 10

Run the workflow and activity tests.

vendor/bin/phpunit

That's it! You can now create and test workflows.

AI Client MCP Server

The sample app also exposes a Laravel MCP server at /mcp/workflows. This is the reference AI-client surface for Durable Workflow v2: it gives agents structured workflow discovery, start, status, output, recent typed history, and failure facts without requiring them to scrape Waterline.

For the detailed MCP endpoint and tool contract, see MCP Workflow Surface. For the broader AI-assisted development contract, including v2 LLM manifests, CLI exit codes, Waterline exports, and SDK references, see AI-assisted development.

The MCP server is registered from routes/ai.php by the Laravel MCP package. The exposed workflow keys live in config/workflow_mcp.php; each entry can include the workflow class plus discovery metadata such as a description, credential requirements, and expected arguments.

Default tools:

ToolPurpose
list_workflowsLists configured workflow keys, credential requirements, v2 status values, and optionally recent runs.
start_workflowStarts a configured v2 workflow and returns workflow_id, run_id, status, business key, and command outcome.
get_workflow_resultPolls the current or selected run and returns status, output, visibility metadata, and latest failure summary.
get_workflow_historyReturns a bounded tail of typed v2 history events and latest durable failures for debugging.

A typical agent loop is:

{"tool": "list_workflows", "arguments": {"show_recent": true, "limit": 5}}
{"tool": "start_workflow", "arguments": {"workflow": "simple", "business_key": "demo-001"}}
{"tool": "get_workflow_result", "arguments": {"workflow_id": "<workflow_id>"}}
{"tool": "get_workflow_history", "arguments": {"run_id": "<run_id>", "limit": 25}}

Use simple or elapsed for no-credential smoke tests. The prism workflow is intentionally exposed as an AI example, but it requires OPENAI_API_KEY before a worker can complete it.