| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
This Laravel 13 application demonstrates Durable Workflow 2.0 through two first-class deployment paths: Service mode with a standalone Server and first-party language workers, or an engine embedded in Laravel. Both paths run in a GitHub Codespace on the supported 2.0 prerelease train. Stable Durable Workflow 2.0 has not been released yet.
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.
Create a Codespace from the main branch of this repository.
Wait while Codespaces pulls the prepared Sample App development image and installs the repository's Composer and npm dependencies. PHP and Composer, Python with an isolated SDK environment, Rust and Cargo, Docker Compose, dw, Node, and Chromium are already in the image. Post-create does not run apt, rustup, compile dw, or rebuild a language toolchain.
When setup finishes, choose either deployment path:
| Path | Best fit | Runtime |
|---|---|---|
| Service mode | One approachable run showing the complete first-party language story | PHP workflow + Python activity + Rust activity + standalone Server |
| Embedded Laravel | A Laravel application that owns workflow execution and storage | Laravel app + queue worker |
Service mode is the first-party PHP, Python, and Rust story. After Codespaces reports that setup is complete, run its featured PolyglotWorkflow sample:
scripts/polyglot.shThis one command resolves the current installable Server, PHP SDK, Python SDK, and Rust SDK artifacts; builds their worker images; waits for all three workers; and starts PolyglotWorkflow. The PHP-authored workflow routes an order calculation to the Python activity queue, sends that calculation to the Rust receipt activity queue, and combines both results. Its output identifies the PHP workflow runtime, both activity runtimes, all three task queues, the current artifact versions, and the completed receipt.
Docker and the complete PHP/Python/Rust toolchain are included in the prepared Codespaces image. The command does not require a package-install step or any tool outside the repository and its Compose stack. Repeat it for another run; the isolated sample-app-polyglot-demo project remains available between runs. The exhaustive directional codec, replay, signal, query, and Waterline checks remain in the polyglot matrix guide.
Ask an agent to create and run a workflow and activity, or use the same memorable service-mode authoring interface for every first-party SDK yourself:
scripts/playground php
scripts/playground python
scripts/playground rustEach choice creates editable workflow and activity source under .playground/<language>, outside the repository's conformance workers, and preserves files you already own. It resolves the repository's one qualified published-artifact tuple and starts isolated Server and Waterline state. Before execution it prints the effective workflow type, activity type, task queue, worker command, start command, and expected result from playground/contract.json. The client starts only after the matching worker registration produces a positive Worker ready checkpoint.
Success is reported after the SDK receives the expected result and dw confirms completed status and activity history. Waterline must select that exact workflow and run before the terminal prints its exact run URL. The same journeys run during development-image qualification on AMD64 and ARM64.
Pass --source to scaffold or run a different caller-owned directory:
scripts/playground python --source "$HOME/my-durable-python-worker"The default source and evidence paths are ignored by Git. The development image contains an isolated Python SDK environment, the PHP SDK package, and the Rust SDK dependency and development build caches, so first use does not rebuild an SDK graph. Remove the isolated Server, database, Redis, and Waterline state with scripts/playground down <language>.
Local published Server remains the default and requires no managed-service access. To run the same authored journey against an existing managed runtime, provide separate worker and client credentials through DURABLE_WORKFLOW_WORKER_TOKEN and DURABLE_WORKFLOW_CLIENT_TOKEN, then make the runtime contract explicit on the same language command:
language=rust # Choose php, python, or rust.
scripts/playground "$language" --runtime managed \
--runtime-url "https://runtime.example/namespaces/example" \
--namespace "example" \
--task-queue "sample-app-playground-example"The runner keeps the two credentials in their respective worker and client processes. It waits until the managed runtime advertises the current invocation's generated worker identity, workflow type, activity type, and exact queue before starting the client. The completed result must contain the caller input after it crosses both the workflow and activity boundaries; the final success record names the runtime, namespace, queue, registered types, and expected result shape without printing credential values. Managed evidence records explicitly identify Waterline proof as omitted because this mode does not provision a Waterline observer. Runtime enrollment and credential creation remain the managed service's setup responsibility.
The default php journey uses the framework-neutral durable-workflow/sdk through its Laravel bridge. The caller-owned activity receives configuration and a PSR logger from Laravel's container, and the live worker uses the bridge's role-scoped client and service configuration with the generated invocation identity. The scaffold's focused SDK test fake uses WorkerFactory. The journey runs that local test before registering the live worker, so moving from the embedded engine to service mode retains dependency injection, application configuration, logging, and testability.
For a framework-free PHP process, explicitly scaffold the installed SDK package's own examples instead of copying another implementation into Sample App:
scripts/playground scaffold php --standalone --source "$HOME/durable-php-worker"The installed package owns bootstrap.php, worker.php, and client.php. This scaffold-only variation does not replace the executed playground journey; use the shared language command above for either the local or managed runtime proof.
For framework integration work, an optional narrower variation uses the standalone Server with Laravel's SDK bridge plus PHP and Python activities:
scripts/service-mode.shIt remains documented as an advanced Laravel integration variation, not a separate deployment path.
Codespaces setup has already created the environment, generated the application key, migrated the database, and verified MySQL, Redis, and Playwright. Start Laravel's web, queue, log, and asset processes:
composer run devIn a second terminal, start the example workflow:
php artisan app:workflowOpen Waterline at the forwarded port 18080 URL under /waterline. The workflow result also appears in the terminal. Run the workflow and activity tests with:
php artisan testThe embedded stack uses the Codespace's sample-app Compose state. Workflow instances receive generated identities, so the command is safe to run again.
Check the two observability surfaces separately:
| Surface | Use it for | Where to look |
|---|---|---|
| Waterline and the workflow database | Durable workflow truth: run status, typed history, signals, updates, timers, retries, failures, and operator actions. | The run URL printed by application journeys, or /waterline when the selected stack includes it |
| Worker logs and SDK metrics | Runtime behavior: poll latency, task duration, exporter wiring, custom application metrics, and worker-side errors before they become durable failures. | Laravel logs for PHP workers; SDK metrics endpoints for external workers |
Waterline proves that the durable run exists and shows what the engine or standalone Server committed. Worker metrics remain a separate runtime surface. Minimal Python worker Prometheus wiring uses the compatibility-qualified SDK from the public quickstart contract:
curl -fsSL https://durable-workflow.com/install-sdk.sh | \
DURABLE_WORKFLOW_PYTHON_EXTRAS=prometheus sh -s -- pythonfrom 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. Use Waterline for the matching workflow history and status.
The default devcontainer Compose file pulls ghcr.io/durable-workflow/sample-app-devcontainer:main. The same image is available from Docker Hub as durableworkflow/sample-app-devcontainer:main; select it without changing the Compose file by setting SAMPLE_APP_DEVCONTAINER_IMAGE before opening the devcontainer. Both channels support Linux AMD64 and ARM64.
main is the moving channel built from protected main pushes and the weekly refresh. The Chromium revision follows the exact Playwright version in package-lock.json, which is covered by the weekly dependency update path. Every publication is retained under an immutable sha-<source-revision>-run-<workflow-run>-<attempt> tag in both registries. Published indexes include OCI source/revision labels, BuildKit provenance, and an SPDX SBOM. The moving tags advance only after both registry copies and both architectures pass the unauthenticated Compose qualification.
The prepared image also runs the OpenSSH server expected by Codespaces tooling, so remote shell and creation-log access do not require a per-Codespace feature install.
There is deliberately no local Dockerfile fallback in the Codespaces Compose topology. A pull or qualification failure stops setup instead of reconstructing the old operating-system environment. To qualify a published image manually and write phase timings to a JSON file, run:
scripts/ci/qualify-devcontainer-image.sh \
ghcr.io/durable-workflow/sample-app-devcontainer:main \
linux/amd64 \
devcontainer-qualification-timing.jsonThe qualification pulls the selected image, starts the same MySQL/Redis and Laravel/microservice topology with --no-build, installs only repository dependencies, launches Chromium as the non-root laravel user, verifies the SSH endpoint, checks the application health endpoint and mounted-checkout editability, and records fresh and warm startup timings.
Prefer a local workstation over Codespaces? The repository ships a docker-compose.yml that builds and runs the app, worker, MySQL, and Redis on any host with Docker Engine and Docker Compose v2 installed.
# 1. Clone and enter the repo
git clone https://github.com/durable-workflow/sample-app.git
cd sample-app
# 2. (Optional) expose the app on a non-default port
export APP_PORT=18080
# 3. Build and start the stack. --wait blocks until health checks pass.
docker compose up -d --build --wait app worker
# 4. Run migrations against the shared sample database.
docker compose exec -T app php artisan migrate:fresh --force
# 5. Run the simplest deterministic sample end-to-end.
docker compose exec -T app php artisan app:workflowOnce the stack is up, Waterline is at http://localhost:${APP_PORT:-8000}/waterline/dashboard and the MCP server is at http://localhost:${APP_PORT:-8000}/mcp/workflows.
For a release-style proof from a clean checkout, use the combined entry point instead of the manual build, migration, and sample commands above. It builds the resolved artifact tuple once, runs deterministic smoke, and continues through the provider-free conformance matrix on the same healthy stack and schema. AI surfaces are recorded as intentional skips, so this command needs no provider credential:
scripts/compose-smoke-conformance.shThe standalone full-conformance entry point remains self-contained for callers that do not need the deterministic preflight. AI-backed surfaces are disabled by default, so this form cannot consume a provider credential discovered in the shell or an ancestor dotenv file:
scripts/compose-conformance.shThe harness emits a JSON document with the sample-app commit, artifact versions, timestamp, per-surface outcome, focused findings, setup measurements, and any skipped surfaces. Setup measurements include whether the run started with a clean or warm image cache, setup duration, peak Docker disk growth, build invocation count, and whether a prepared stack was reused. Run the combined entry point once without its app image and again with the resulting cache to capture comparable clean-cache and warm-cache measurements. It runs the documented artisan samples, browser checks for the app and Waterline, the MCP workflow API, an API documentation check that compares the README's documented MCP tools and workflow keys with the live endpoint, a Waterline/manual observation check using workflow:v2:history-export, local sandbox lifecycle variants, sandbox recovery injection, and, in explicit provider mode, the Prism/AI samples. The Prism check uses OPENAI_API_KEY for the live model-backed AI surface. The travel-agent success and failure-injection checks reuse one deterministic booking plan so the run proves signals, durable assistant messages, booking activities, and compensation without spending extra model calls on each failure variant. The polyglot harness builds the exact released Rust SDK from crates.io and executes Rust-authored workflows and activities across the PHP, Python, and Rust runtime matrix. Its report distinguishes registered Rust execution from the release-cohort version pin. SAMPLE_APP_CONFORMANCE_SKIP_AI=1 is the safe release and automation mode. It passes --skip-ai to app:conformance, keeps OPENAI_API_KEY out of Compose exec arguments, and records every AI-backed surface as explicitly skipped while the deterministic and scripted agent-operability surfaces still run. Intentional AI skips are allowed automatically in this mode; combining them with --strict is rejected as a contradictory coverage request.
Provider-backed conformance requires an explicit opt-in, even when a credential is already present. Its release proof is:
export OPENAI_API_KEY=your-provider-key
SAMPLE_APP_CONFORMANCE_SKIP_AI=0 \
scripts/compose-smoke-conformance.sh --strictSet SAMPLE_APP_CONFORMANCE_ENV_FILE only on that opt-in path when the key lives in a dotenv file outside the repository; the wrapper then checks local workspace-level dotenv files without printing credential values. Provider mode requires strict coverage by default, and --strict makes that requirement explicit. Without AI credentials, the run stays non-passing and names the live Prism surface as uncovered. Set DURABLE_SERVER_IMAGE, DURABLE_WORKFLOW_CLI_VERSION, DURABLE_WORKFLOW_PYTHON_SDK_VERSION, DURABLE_WORKFLOW_RUST_SDK_VERSION, DURABLE_WORKFLOW_PHP_SDK_VERSION, DURABLE_WORKFLOW_WORKFLOW_VERSION, and DURABLE_WORKFLOW_WATERLINE_VERSION to override the published artifact set. The PHP SDK variable selects the framework-neutral durable-workflow/sdk package used by polyglot/; the Workflow variable selects the separate durable-workflow/workflow engine used by this Laravel application. By default, the wrapper calls scripts/resolve-current-artifacts.sh, which resolves one 2.0 prerelease channel from the public docs release-audit manifest. Beta tuples remain synchronized, while release-candidate tuples may contain component-specific increments as long as every component stays in the rc channel.
| Prerelease channel | Component-version policy |
|---|---|
| beta | synchronized |
| rc | component-specific |
| mixed | rejected |
The resolver emits the accepted tuple as shell assignments and preserves explicit overrides. The wrapper rebuilds the app and worker containers with the resolved PHP SDK, Workflow, and Waterline pins before running the harness, so the recorded versions come from installed packages rather than the committed fallback lock. The polyglot Rust image likewise applies the resolved SDK version to its build-local manifest, leaving the committed Cargo manifest and lock as the pinned fallback. The standalone PHP stack independently installs and executes the resolved PHP SDK pin. Set DURABLE_WORKFLOW_ARTIFACT_SOURCE=pinned for a reproducible run against the committed sample-app fallback tuple instead. Set DURABLE_WORKFLOW_ARTIFACT_TUPLE_FILE=/path/to/tuple.json when a local run should use a previously captured public tuple manifest. The wrapper passes the host checkout SHA into the app container as SAMPLE_APP_COMMIT; set that variable explicitly when running from a source archive or another environment without Git metadata. The same value is forwarded as a Docker build and runtime variable so source-free containers can report the sample-app revision without reading a local .git checkout. The wrapper also copies the JSON metadata back to storage/app/sample-app-conformance-metadata.json; set SAMPLE_APP_CONFORMANCE_METADATA_PATH to choose a different host-side path. Pass that file as DW_AGENT_OPERABILITY_SAMPLE_APP_METADATA_PATH when validating the agent-operability executable-loop contract against the current artifact tuple. The app service has the browser-safe sample-app network alias, and the wrapper uses http://sample-app:8000 inside the Compose network so browser activities running in the worker container can reach the app without an HTTPS upgrade. Set SAMPLE_APP_CONFORMANCE_URL when running against a different network address. The wrapper derives one coverage policy from the AI mode: provider-free mode allows its intentional AI skips, while provider mode is strict by default. Set SAMPLE_APP_CONFORMANCE_ALLOW_SKIPS=1 only for exploratory provider-mode runs that should return zero while naming missing provider-backed evidence. scripts/compose-smoke.sh starts with the bounded deterministic preflight: it runs the deterministic samples and exits after printing the blocked step, container status, and recent app/worker logs on failure. By default, a passing preflight continues into the broader public sample-app conformance surface so a release/conformance caller does not accidentally record deterministic smoke as full coverage. The handoff records the prepared app and worker containers; the full wrapper reuses them only when their health, artifact tuple, credentials, installed packages, and migrated schema still match. Otherwise it falls back to its self-contained rebuild and schema reset. Set SAMPLE_APP_SMOKE_ONLY=1 when a caller intentionally wants only the deterministic path. Set SAMPLE_APP_CONFORMANCE_AFTER_SMOKE=0 to disable the chained full surface for exploratory local runs, or run scripts/compose-conformance.sh --strict directly with SAMPLE_APP_CONFORMANCE_SKIP_AI=0 when a strict provider run does not need the deterministic preflight.
Tear the stack down with docker compose down -v --remove-orphans when finished. The deterministic Docker path is exercised on every push through the smoke GitHub Actions workflow, and the full harness is available for release and conformance checks that have the required credentials.
Use this index when you want a specific Durable Workflow pattern instead of another happy-path snippet.
| Goal | Workflow | Command | MCP key |
|---|---|---|---|
| Learn the smallest v2 workflow/activity shape | App\Workflows\Simple\SimpleWorkflow | php artisan app:workflow | simple |
| Measure durable elapsed time without replay drift | App\Workflows\Elapsed\ElapsedTimeWorkflow | php artisan app:elapsed | elapsed |
| Coordinate work across Laravel app boundaries | App\Workflows\Microservice\MicroserviceWorkflow | php artisan app:microservice | microservice |
| Run browser automation and collect generated artifacts | App\Workflows\Playwright\CheckConsoleErrorsWorkflow | php artisan app:playwright https://example.com | playwright |
| Start from an external webhook and wait for a signal | App\Workflows\Webhooks\WebhookWorkflow | php artisan app:webhook | webhook |
| Wrap an AI activity loop in durable retry/validation | App\Workflows\Prism\PrismWorkflow | php artisan app:prism | prism |
| Build a signal-driven AI agent with compensation | App\Workflows\Ai\AiWorkflow | php artisan app:ai | ai |
| Orchestrate an ephemeral agent sandbox with durable lifecycle | DurableWorkflow\AI\Workflows\SandboxAgentWorkflow | php artisan app:sandbox | sandbox |
| Run one PHP workflow that combines Python and Rust activity results | App\Workflows\Polyglot\PolyglotWorkflow | scripts/polyglot.sh | polyglot |
| Exercise machine-readable failure diagnosis and repair refusal | App\Workflows\Diagnostics\DiagnosticFailureWorkflow | /mcp/workflows start_workflow with workflow=diagnostic_failure | diagnostic_failure |
Porting a workflow from the v1 generator API to the v2 Fiber API is mechanical. The v1 sources live on the Laravel-12 branch; use it as a side-by-side reference while you migrate.
Workflow shape:
Activities:
Signals, updates, webhooks:
Compensation closures:
Stub usage:
The App\Workflows\Simple\SimpleWorkflow, App\Workflows\Webhooks\WebhookWorkflow, and App\Workflows\Ai\AiWorkflow samples in this repo are the canonical references for the basic shape, webhook entry, and signal/update agent patterns respectively.
Use message streams when a workflow needs to publish or consume repeated messages without writing Durable Workflow storage rows directly. The v2 authoring API is exposed through Workflow::inbox(), Workflow::outbox(), and Workflow::messages(); those facades own workflow_messages rows and stream cursor advancement for the workflow run.
App\Workflows\Ai\AiWorkflow is the reference sample. It stores large assistant payloads in the app-owned ai_workflow_messages table, then publishes only a durable reference on the ai.assistant stream:
$this->outbox(self::ASSISTANT_STREAM)
->sendReference(
$this->workflowId(),
$reference,
correlationId: $reference,
idempotencyKey: $reference,
metadata: ['role' => 'assistant'],
);The receive update consumes the next assistant reply through the matching inbox stream:
$streamMessage = $this->inbox(self::ASSISTANT_STREAM)
->receiveOne();receiveOne() consumes the message and advances the durable stream cursor, so repeated receives deliver new replies instead of replaying old ones. Keep app tables as payload/reference stores; let Durable Workflow own workflow_messages and stream cursor advancement through the facade.
Long-running coding agents need an ephemeral workspace, but lifecycle and recovery infrastructure should not be copied into each application. This app consumes durable-workflow/ai; the package owns the versioned provider contract, activities, DurableWorkflow\AI\Workflows\SandboxAgentWorkflow, E2B and local adapters, stable operation IDs, post-snapshot reconstruction, leases, and cleanup. The Sample App retains only its command, configuration example, and end-to-end demonstration.
config/durable-workflow-ai.php selects the provider. The default local subprocess provider is development/test-only, runs with the worker's privileges, and is not a security isolation boundary. The E2B adapter uses the documented HTTP API. This sample does not expose E2B suspend/resume because paused sandboxes have no provider TTL; it must not be enabled without an independent durable cleanup deadline. Both built-in providers explicitly declare at-least-once tool effects; a lost acknowledgement can repeat a mutating call.
Run the sample with:
php artisan app:sandbox # local subprocess provider
php artisan app:sandbox --snapshot-every=2 # snapshot every 2 tool calls
php artisan app:sandbox --snapshot-every=2 --inject-loss-after=2 # inject local loss after the checkpoint
DURABLE_AI_SANDBOX_DRIVER=e2b E2B_API_KEY=… php artisan app:sandboxSee docs/sandbox-orchestration.md for the integration walkthrough and links to the package's delivery contract and provider-author guide.
The repository ships a runnable polyglot demonstration in polyglot/. It brings up the standalone Durable Workflow server with framework-neutral PHP workers from the published durable-workflow/sdk package, Python workers, and crates.io-installed Rust workers side by side. The root Laravel example remains a separate embedded mode backed by durable-workflow/workflow. Nine workflow/activity runtime cells run end to end:
The cross-language scenarios are wire-level tests: the workflow runtime and activity runtime register separately, and each scheduled activity crosses the language boundary on the wire — not just inside one process. The smoke runs in CI on every pull request via .github/workflows/polyglot-validation.yml, so a regression in either direction is caught before release rather than in the field.
The codec round-trip rules — which payload values cross the language boundary cleanly and which need explicit adapters — are documented in the workflow package at docs/architecture/polyglot-codec-roundtrip.md. Operators of polyglot fleets should treat the "requires an explicit adapter" set as a workflow-author contract: the SDKs fail closed at the boundary rather than guess at a serialisation.
Durable Workflow v2 replays workflow code to rebuild local state from committed history. Keep workflow methods deterministic: call activities for side effects, use sideEffect() for values such as timestamps or random IDs, and wait for outside input through signals, updates, timers, or message streams.
Do this when a workflow needs the current time:
use function Workflow\V2\sideEffect;
$startedAt = sideEffect(fn () => now()->getTimestamp());Don't do this inside workflow code:
$startedAt = now();The direct now() call looks harmless, but replay can run the method again later and produce a different value than the one that originally drove branching, timeouts, or output. Prefer portable Avro Value types inside sideEffect() callbacks — integer timestamps, ISO-8601 strings, UUIDs — so the recorded value has the same meaning on replay. Convert domain objects such as Carbon instances through an explicit adapter instead of relying on PHP object serialization. The ElapsedTimeWorkflow sample keeps clock reads behind sideEffect() as integer timestamps, and the SimpleWorkflow, PrismWorkflow, and AiWorkflow samples keep external work inside activities for the same reason.
In addition to the basic example workflow, you can try these other workflows included in this sample app:
php artisan app:elapsed – Demonstrates how to correctly track start and end times to measure execution duration.
php artisan app:microservice – A fully working example of a workflow that spans multiple Laravel applications using a shared database and queue.
php artisan app:playwright – Runs a Playwright automation against https://example.com, captures a WebM video, encodes it to MP4 using FFmpeg, and then cleans up the WebM file. Pass a URL to check another page, for example php artisan app:playwright http://localhost:8000/waterline/dashboard.
php artisan app:webhook – Showcases how to use the built-in webhook system for triggering workflows externally.
php artisan app:prism - Uses Prism to build a durable AI agent loop. It asks an LLM to generate user profiles and hobbies, validates the result, and retries until the data meets business rules.
php artisan app:ai - NEW! Uses Laravel AI SDK to build a durable travel agent. The agent asks questions and books hotels, flights, and rental cars. If a booking error occurs, the workflow ensures prior bookings are canceled; an inactivity timeout closes the conversation without rolling back successful interactive bookings. For repeatable checks, pass one or more --message="..." options and optionally --inactivity-timeout=5; use --inject-failure=hotel, --inject-failure=flight, or --inject-failure=car to exercise compensation. --booking-plan-json='{"text":"...","bookings":[...]}' lets deterministic scripted checks run a single planned turn while still exercising the workflow, booking activities, and compensation.
php artisan app:sandbox - Package integration demo for durable-workflow/ai. The command dispatches a short tool sequence through the reusable sandbox workflow. Use --snapshot-every=2 --inject-loss-after=2 to inject one local lifecycle loss outside the tool journal and exercise recovery, or set DURABLE_AI_SANDBOX_DRIVER=e2b plus E2B_API_KEY to use E2B Cloud. The local subprocess provider is development/test-only and is not a security isolation boundary; E2B suspend/resume is unavailable until paused resources have an independent durable cleanup deadline.
Try them out to see workflows in action across different use cases!
This sample app includes an MCP (Model Context Protocol) server that allows AI clients (ChatGPT, Claude, Cursor, etc.) to start and monitor Durable Workflow v2 workflows. Treat it as the agent-operable companion to Waterline: humans can inspect /waterline/dashboard, while AI clients receive structured workflow IDs, run IDs, statuses, recent typed history, and failure summaries.
The MCP server is named Durable Workflow.
It is not a separate daemon in this repo. The server is exposed by the Laravel application itself, so once the app is running, the MCP route is live as part of the normal HTTP server.
The MCP server is available at: /mcp/workflows
To make the MCP server available locally:
If you prefer Docker, run docker compose up --build, then run docker compose exec app php artisan migrate --force once the containers are healthy. After migrations complete, connect to http://localhost:8000/mcp/workflows.
| Tool | Description |
|---|---|
| list_workflows | Discover configured workflow keys, credential requirements, status values, and recent v2 runs |
| start_workflow | Start a configured v2 workflow asynchronously and get a workflow instance ID plus run ID |
| get_workflow_result | Check workflow status, output, visibility metadata, and latest failure summary |
| get_workflow_history | Inspect a bounded slice of typed v2 history events and latest durable failures |
| diagnose_workflow | Summarize health facts, root-cause classification, remediation, latest failure evidence, and safe next actions for stuck or failed runs |
| repair_workflow | Request the built-in v2 repair command and receive a structured accepted, refused, or not-needed mutation result |
Available workflows are defined in config/workflow_mcp.php. By default, every workflow in the sample index is exposed:
To add more workflows, update the config file:
'workflows' => [
'simple' => [
'class' => App\Workflows\Simple\SimpleWorkflow::class,
'description' => 'Small deterministic workflow.',
'pattern' => 'deterministic activity chain',
'command' => 'php artisan app:workflow',
'requires' => [],
'arguments' => [],
],
'my_workflow' => [
'class' => App\Workflows\MyWorkflow::class,
'description' => 'What an agent should know before starting it.',
'requires' => ['EXTERNAL_API_KEY'],
'arguments' => [
['name' => 'customer_id', 'type' => 'string'],
],
],
],Class-string mappings are still accepted for small local experiments, but the array form gives agents safer discovery metadata.
An AI client would typically:
Use the structured templates under Issues so reproducers and sample requests land with the metadata maintainers need:
Bugs in the workflow engine itself or the standalone Durable Workflow server belong on the workflow and server repos respectively; the issue chooser links those out.
Have a Durable Workflow pattern you want to share? Read CONTRIBUTING.md for the full contract — workflow class layout, artisan command name, MCP entry, test, README index row, and the docs-site gallery and pattern-page cross-link that ship in the same change. The Contribute a Sample page on the docs site is the canonical version of the same guide.
Maintainers tagging an upstream release should read docs/release-notes-feature-contract.md first; it names the bar a sample must meet to be cited in upstream release notes and the checklist that runs before a release tag lands.
This is a public repository. Do not add private tracker names, workspace-only absolute paths, or loop/lane 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.
| Back | FazBrowse Home | New Git URL |