Execution & modes
Starting an execution
All three entry points go through the same manager
(ExecutionManagerContract, fronted by the Workflow facade):
use Qanna\WorkflowEngine\Facades\Workflow;
// Run now, in this process. Returns a WorkflowExecution.
$execution = Workflow::run('send-welcome-email', ['email' => 'ada@example.com']);
// Queue it. Returns nothing; a worker runs it later.
Workflow::dispatch('send-welcome-email', ['email' => 'ada@example.com']);
// Resume a suspended execution (see Suspend & resume).
Workflow::resume($executionId, ['approved' => true]);
run() also accepts $triggeredBy (a free-text label recorded on the execution,
default 'manual'), an ExecutionMode, and an optional $executionId.
Workflow::runDraft() takes the same arguments except $executionId, and runs
the unpublished draft instead — see Publishing & versions.
When an execution starts, the engine:
- Loads the workflow's published version (or its draft, for
runDraft()). A workflow that has never been published can't be run withrun(). - Fires the workflow's trigger against the payload. If the trigger returns
TriggerResult::ignore(), no execution is created andrun()returnsnull. - Captures a snapshot of the definition into a new execution record.
- Walks the node graph from the
from: 'trigger'edge until the workflow completes, fails, is stopped, or suspends.
Execution modes
| Mode | Behaviour |
|---|---|
ExecutionMode::Sync | Everything runs in the current process. A short Wait (within sync_wait_timeout) sleeps in-process and resumes immediately; a longer one suspends and returns a Suspended execution. This is the default for Workflow::run(). |
ExecutionMode::Async | The default for queued execution. Wait and child-workflow calls always go through the queue. |
ExecutionMode::Test | Runs the real engine but resolves everything inline and instantly — waits don't sleep, child workflows never touch a real queue. Used by Workflow::fake(). |
The WorkflowExecution record
Every run produces a WorkflowExecution Eloquent model (returned by run() /
resume(), and queryable later — see Storage):
$execution->id; // uuid
$execution->workflow_id;
$execution->workflow_version; // the published version that ran; null for a draft run
$execution->status; // ExecutionStatus enum
$execution->input; // the trigger payload
$execution->output; // final output (see below)
$execution->error_message; // set when status is Failed
$execution->logs; // Collection<ExecutionLogEntry>
$execution->meta; // includes 'triggered_by', 'duration_ms'
$execution->started_at; // Carbon, millisecond precision
$execution->finished_at;
$execution->durationMs();
$execution->isTerminal(); // Succeeded | Failed | Cancelled
$execution->timeline(); // one summary row per node, in traversal order
Status values
| Status | Meaning |
|---|---|
Running | In progress. |
Succeeded | Reached the end of the graph without error. |
Suspended | Paused, waiting to be resumed (a Wait, a child workflow, a webhook, or a manual resume). Not terminal. |
Failed | A node returned a failure or threw, or a Stop node terminated it as a failure. |
Cancelled | A Stop node ended it intentionally on success. |
Pending | Reserved; not currently produced by the engine. |
What output contains
By default the execution's output is the last node that ran. That is often not what you want once side-effecting nodes run after the "real" result was produced. Put a Set output node at the end of a flow to make the execution return exactly what you choose.
The execution log
Each node writes a running row when it starts and a terminal row
(success / error / failed / waiting / stopped) when it finishes, plus
retrying and warning rows where relevant. $execution->timeline() collapses
these to one row per node with summed durations. Logging can be turned off with
the enable_logger config key.
Read logs later through the execution's logs relation:
use Qanna\WorkflowEngine\Models\WorkflowExecution;
$execution = WorkflowExecution::with('logs')->find($executionId);
foreach ($execution->logs as $log) {
$log->node_id;
$log->message;
$log->context->status; // running, success, failed, …
$log->duration_ms;
}
Queued execution
Workflow::dispatch() queues the run. Resuming after a long wait queues a
delayed resume that runs once the wait elapses. Both need a running queue
worker. Failures inside a node surface as a Failed execution record, not a
thrown job exception.