Skip to main content

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:

  1. Loads the workflow's published version (or its draft, for runDraft()). A workflow that has never been published can't be run with run().
  2. Fires the workflow's trigger against the payload. If the trigger returns TriggerResult::ignore(), no execution is created and run() returns null.
  3. Captures a snapshot of the definition into a new execution record.
  4. Walks the node graph from the from: 'trigger' edge until the workflow completes, fails, is stopped, or suspends.

Execution modes​

ModeBehaviour
ExecutionMode::SyncEverything 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::AsyncThe default for queued execution. Wait and child-workflow calls always go through the queue.
ExecutionMode::TestRuns 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​

StatusMeaning
RunningIn progress.
SucceededReached the end of the graph without error.
SuspendedPaused, waiting to be resumed (a Wait, a child workflow, a webhook, or a manual resume). Not terminal.
FailedA node returned a failure or threw, or a Stop node terminated it as a failure.
CancelledA Stop node ended it intentionally on success.
PendingReserved; 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.