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.

When an execution starts, the engine:

  1. Fires the workflow's trigger against the payload. If the trigger returns TriggerResult::ignore(), no execution is created and run() returns null.
  2. Captures a snapshot of the definition into a new execution record.
  3. 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 (returned by run() / resume(), and retrievable later from the execution repository). It is an immutable value object:

$execution->id; // uuid
$execution->workflowId;
$execution->workflowVersion;
$execution->status; // ExecutionStatus enum
$execution->input; // the trigger payload
$execution->output; // final output (see below)
$execution->errorMessage; // set when status is Failed
$execution->logs; // Collection<ExecutionLogEntry>
$execution->meta; // includes 'triggered_by', 'duration_ms'
$execution->startedAt;
$execution->finishedAt;
$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 repository:

use Qanna\WorkflowEngine\Storage\Contracts\ExecutionRepositoryContract;

$repo = app(ExecutionRepositoryContract::class);
$repo->find($executionId);
$repo->listForWorkflow('send-welcome-email', limit: 50);
$repo->listByStatus(ExecutionStatus::Failed);
$repo->logsForExecution($executionId);
$repo->purgeOlderThan(now()->subMonths(3));

Queued execution

Workflow::dispatch() pushes an ExecuteWorkflowJob. Resuming after a long wait pushes a ResumeExecutionJob (delayed until the wait elapses). Both need a running queue worker. Failures inside a job surface as a Failed execution record, not a thrown job exception.