This directory is the runnable polyglot demonstration that ships with the
sample app. It proves the Durable Workflow control plane is language-neutral
by running a conformance smoke around four runtime scenarios against one
standalone server, with real workers in different languages registered on
coordinated task queues. The smoke drives workflow start, signal, query, and
result retrieval through the published dw CLI and checks the same run through
Waterline.
The main sample app (docker-compose.yml at the repository root) is the
single-language, in-process Laravel demo. This directory is a separate
demonstration — its own docker-compose.yml, its own services, its own
smoke — so the simple Laravel-only path stays simple.
Four scenarios run end to end:
| Scenario | Workflow language | Activity language | Source |
|---|---|---|---|
| Python authoring | Python (sdk-python) |
Python | python_workflow/workflow.py |
| PHP authoring | PHP (durable-workflow/workflow) |
PHP | app/Workflows/Polyglot/PhpSameLanguageWorkflow.php + app/Console/Commands/PolyglotWorker.php |
| Cross-language activity | PHP (durable-workflow/workflow) |
Python | app/Workflows/Polyglot/PhpToPythonWorkflow.php + python_worker/activities.py |
| Reverse cross-language activity | Python (sdk-python) |
PHP (durable-workflow/workflow) |
python_workflow/workflow.py + app/Console/Commands/PolyglotWorker.php |
The PHP-authored scenario is the wire-level cross-language test:
php-workflow-workeris a real Laravel + Composer-installeddurable-workflow/workflowcontainer that registerspolyglot.php-to-python.PhpToPythonWorkflowon thepolyglot-php-to-pythontask queue. Its workflow source is the same file the main sample app's MCP listing surfaces.python-activity-workeris a Python container that registerspolyglot.php-to-python.reverseandpolyglot.php-to-python.tallyon the same task queue.- Each run schedules a real activity dispatch — workflow code is in PHP, activity code is in Python — so the Avro envelope crosses the language boundary on the wire, not just inside one process.
The Python-authored same-language scenario is the language-symmetric reference:
python-workflow-workeris a long-running Pythondurable-workflowworker that registers thepolyglot.python.greeterworkflow plus itspolyglot.python.greetandpolyglot.python.summariseactivities on thepolyglot-pythontask queue.- The smoke driver acts purely as a client: it waits for the Python worker to register, starts a workflow, and asserts the result. The workflow itself executes inside the running container, so the docker-compose stack is the actual unit under test.
The PHP-authored same-language scenario is the PHP reference:
php-same-workflow-workerregisterspolyglot.php.greeteron thepolyglot-phptask queue.php-same-activity-workerregisterspolyglot.php.markerandpolyglot.php.describeon the same task queue.- The smoke asserts that workflow and activity tasks are both handled by PHP workers through the standalone worker-plane protocol.
The Python-to-PHP scenario is the reverse wire-level cross-language test:
- The same
python-workflow-workerregisterspolyglot.python-to-php.greeteron thepolyglot-pythontask queue. php-activity-workeris a separate Laravel + Composer-installeddurable-workflow/workflowcontainer that registerspolyglot.python-to-php.markerandpolyglot.python-to-php.describeon thepolyglot-python-to-phptask queue.- The smoke asserts that the Python workflow result includes the PHP runtime marker returned by those activities.
The smoke also exercises the conformance surfaces that sit around those four runtime scenarios:
- workflow start and result retrieval through the published
dwCLI; - signal and query handling through the published
dwCLI for PHP-authored and Python-authored workflows; - bidirectional type round-trips for strings with non-ASCII text, ints, floats, booleans, nulls, mixed lists, nested maps, timestamps, and binary values represented by the published JSON-native codec as explicit base64 objects;
- typed activity error round-trips from Python activity to PHP workflow and PHP activity to Python workflow;
- Waterline event typing, payload rendering, and worker attribution for same-language and mixed-language runs.
The smoke emits a run metadata JSON document after all required surfaces run. That document includes exact public artifact pins for the server image, CLI, Python SDK, PHP SDK, and Waterline, plus pass/fail status per surface.
The codec contract that determines which payload values cross the language boundary cleanly is documented in the workflow package: Polyglot Codec Round-Trip Contract.
polyglot/
├── docker-compose.yml full stack (server + workers + smoke)
├── python_workflow/
│ ├── workflow.py Python-authored workflow + activities
│ └── Dockerfile Python image
├── python_worker/
│ ├── activities.py Python activities consumed by the PHP workflow
│ ├── Dockerfile Python image (also baked-in smoke driver)
│ └── scripts/
│ ├── smoke.sh shell entrypoint for the full smoke
│ ├── polyglot_smoke.py drives all scenarios and emits metadata
│ ├── php_same_language_smoke.py PHP-authoring sanity driver
│ ├── python_workflow_smoke.py Python-authoring smoke driver
│ ├── php_to_python_smoke.py PHP→Python smoke driver
│ └── python_to_php_smoke.py Python→PHP smoke driver
├── php_worker/
│ └── Dockerfile Real PHP image (Laravel + durable-workflow PHP SDK)
└── README.md this file
The smoke driver scripts live under python_worker/scripts/ because the
smoke service in docker-compose.yml reuses the python_worker image
build context, and the Dockerfile bakes the scripts/ tree into the
image at /app/scripts/. Editing those files there is the only way to
change what the smoke service runs — there is no bind mount.
The PHP-authored polyglot workflow lives with the rest of the sample
app's PHP code at app/Workflows/Polyglot/PhpToPythonWorkflow.php so it
is autoloaded by Laravel and discoverable through the same MCP listing
machinery as the other samples. The polyglot PHP worker container
reuses the sample-app Laravel image and runs the
php artisan app:polyglot-worker command. In --mode=workflow it
registers itself against the standalone server and pumps workflow tasks
through a Fiber-based replay of the same class file. In
--mode=activity it registers PHP activities consumed by the
Python-authored workflow.
while IFS= read -r assignment; do export "$assignment"; done < <(scripts/resolve-current-artifacts.sh)
cd polyglot
docker compose up -d --build --wait \
server python-activity-worker php-same-workflow-worker php-same-activity-worker \
php-workflow-worker php-activity-worker python-workflow-worker waterline
docker compose run --rm --build smoke
docker compose down -vThe smoke service runs /app/scripts/smoke.sh (baked in from
python_worker/scripts/smoke.sh), which:
- waits for the Python and PHP workers to register on their coordinated task queues;
- uses
dw workflow:start --wait --jsonto run the Python-authored, PHP-authored, PHP-to-Python, and Python-to-PHP workflow scenarios; - uses
dw workflow:start,dw workflow:query,dw workflow:signal, anddw workflow:describeto verify signal/query parity through the published CLI for both Python-authored and PHP-authored workflows; - runs the bidirectional type round-trip and typed-error matrices through the same published CLI entrypoint;
- reads Waterline JSON endpoints for the mixed-language and same-language runs and compares event typing, payload rendering, and worker attribution;
- emits one machine-readable conformance metadata document with artifact pins and pass/fail status for every required surface.
The final stdout block is the run metadata document. It records the public artifact pins used by the run and the surface matrix for the CLI, runtime, codec, typed-error, signal/query, and Waterline checks. Every required surface must pass before the smoke exits successfully.
Removing php-workflow-worker from the up line is the regression
test for "this stack is actually polyglot": the smoke fails fast with
"no PHP worker registered on task queue" instead of silently passing.
Removing php-activity-worker fails the matching Python-to-PHP check
with "no php worker registered on task queue".
Removing python-workflow-worker fails the matching "no Python worker
registered on task queue" check on the symmetric side.
The .github/workflows/polyglot-validation.yml GitHub Actions job
runs the same docker compose run --rm --build smoke on every push and
pull request, so the checked-out smoke driver scripts are rebuilt into
the image before they execute. A regression in either direction is
caught here, not in the field.
All scenarios use the avro codec by default — that is the v2
default. Values that round-trip cleanly (JSON-native scalars, lists,
maps) flow without adapter code; values that need explicit codec
negotiation (PHP BackedEnum, Python dataclasses, Decimal,
datetime) are listed in the codec round-trip contract linked above.
The smoke fixtures stay inside the clean round-trip set by design so
the demo runs without per-language adapter shims.
The polyglot compose stack starts a Waterline service against the same
standalone server database. The smoke reads Waterline's JSON endpoints for
same-language and mixed-language runs and verifies that workflow arguments,
outputs, event typing, and worker attribution render with the same fidelity
across runtime combinations. Waterline reads each row's payload_codec column
rather than sniffing blob shape, so a run authored in Python decodes to the
same JSON structure a PHP run does.