durable-workflow/sdk
A PHP SDK for building durable, fault-tolerant workflows. Define long-running processes as code, persist state, and recover from failures with retries and resumable execution—ideal for Laravel and PHP apps needing reliable background orchestration.
Start with the README and the organization-wide agent guide linked from agents/AGENTS.md — this package has very little public documentation, no dependents, and few stars, so the repository itself is your primary reference. Treat exact class names, env vars, and method signatures as “read the README first” items rather than assuming they match other workflow SDKs.
Minimal path:
composer require durable-workflow/sdk — pin to ^2.5.1 (see the numeric-name note below) and commit the lockfile. PHP 8.1–8.4 are the qualified runtimes..env — endpoint/address, namespace or task queue, and auth (API key for Cloud, or whatever the self-hosted Server requires). Copy the exact variable names from the README; don’t guess.AppServiceProvider (or a small package config file if the SDK ships one) so controllers, jobs, and artisan commands share one connection.If you are self-hosting, use a known-good pairing as your baseline: SDK 2.5.1 has been acceptance-tested against published Server 2.9.4. That is the fastest way to separate “my code is wrong” from “my client/server versions disagree.”
A good first use case is a multi-step business process that currently lives in a fragile chain of queued jobs: order fulfilment, onboarding, subscription billing, or document approval. Model the orchestration as a workflow and every side effect (DB write, HTTP call, email, payment) as an activity. That gives you retries, durable state, and a visible history without rewriting your domain logic.
Before committing, verify the SDK actually runs against your target server version and that the worker can boot inside your Laravel app. With this maturity level, a one-hour spike is worth more than reading docs.
Separate orchestration from side effects. Workflow code should only branch, await, and call activities. Activities wrap Eloquent, HTTP clients, mailers, and other Laravel services. This is the single most important pattern: it keeps workflows deterministic and replayable.
Dispatch from the edge of your app. Controllers, console commands, and existing queued jobs should call the SDK client to start a workflow, then return. Don’t run long orchestration inline in a request. Inject the client through the container rather than resolving it statically, so tests can swap it.
Use business-derived workflow IDs. Pass an ID such as order:1234 or subscription:abc:renewal:2026-11 when starting a workflow. This gives you idempotent starts, prevents duplicate executions on retries, and makes executions findable in the UI. Decide explicitly what should happen when an ID already exists. Prefixing with a noun also keeps IDs out of the numeric-string edge case described below.
Use durable timers instead of dispatch()->delay(). If the process needs to wait hours or days (trial expiry, reminder, escalation), use the workflow’s sleep/timer API. The state survives worker restarts and deploys; a delayed Laravel job does not give you the same history or cancellation semantics.
Model human-in-the-loop with signals or updates. Approval flows, manual review, and external callbacks map well to signals: the workflow awaits an approval signal, your controller or webhook sends it, and the workflow continues. Combine this with a timeout branch so stalled approvals don’t hang forever. Signal and update declarations are registered by name, so keep those names stable and non-numeric.
Compose with child workflows and parallel calls. Split large processes into child workflows for reuse and independent history. Run independent activities in parallel where the SDK exposes promises/futures, but keep fan-out bounded — 500 parallel HTTP calls from one workflow is usually a design smell.
Wrap activities around existing Laravel services. Constructor-inject repositories, gateways, and mailers into activity classes and let the container build them. Keep activity inputs/outputs to IDs and small DTOs; resolve models inside the activity from the database.
Deploy workers like queue workers, not web requests. Whether you use the managed worker startup path or your own artisan command, run it under Supervisor, systemd, or a Kubernetes Deployment with a separate pool per task queue or workflow family. Long-running PHP processes need memory limits, restart policies, and graceful shutdown on SIGTERM. Confirm in the startup logs that the worker registered the handlers you expect — that log line is your first diagnostic when a workflow sits in “started” forever.
Test at two levels. Unit-test workflow logic with mocked activity results or the SDK’s test environment, and run a small integration suite against a dockerized self-hosted server. The SDK maintains a replay corpus for determinism; lean on replay-style tests for any workflow you change in place. Assert on execution history/state rather than internal method calls. Also test the dispatch path from your controller to ensure the right workflow ID and arguments are sent.
0, 17, and 017 into the same identifier, and keyed registration metadata could be serialized as a JSON list instead of an object. The 2.5.1 patch preserves numeric-string workflow/activity names and string query/signal/update declarations. If you registered handlers with numeric names (or numeric-looking names like 017), upgrade and re-check registration in stale workers. Best practice regardless: prefix handler names descriptively (order-17, activity.017) so nothing depends on numeric coercion rules.now(), rand(), Str::uuid(), facades, or the database from workflow code. Use the SDK’s deterministic helpers or push the operation into an activity. This is the most common source of replay failures.memory_limit, and recycle workers periodically. Rebuild the container/opcache after deploys.config:cache and env vars. Ensure workers read the same cached config as the web tier; a worker started before a config change may behave differently from the app.WorkflowDispatcher contract, activity base classes) so you can swap implementations or fall back to Laravel queues if the package stalls. Pin the version, read release notes before upgrading, and keep critical paths covered by integration tests.How can I help you explore Laravel packages today?