Documentation

What Triggers Suspension?

Any node executor can trigger a durable suspension by returning a NodeExecutionResult with an output port key of "waiting" or "pending".

When the thread orchestrator's output routing step sees one of these port keys, it does not route to downstream nodes. Instead it serializes state, stores it, sets a pause signal, and transitions the thread to Paused.

The Suspension Flow

flowchart TD A["Node executor\nreturns 'waiting' or 'pending' port key"] B["Output router detects suspension port\nHandleElementResultAsync checks key before normal routing"] C["Build SuspendedExecutionData\nSnapshot full ExecutionMemory + all IDs + timestamps"] D["Persist to database\nISuspendedExecutionRegistry.StoreAsync"] E["Set pause signal\nThread state transitions to Paused"] F["Workflow suspended\nServer is free to handle other requests"] G["External resolution\nApproval · Timer fires · Form submitted · Event arrives"] H["ContinueAsync called\nLoad suspended record · Restore ExecutionMemory"] I["Thread execution restarts\nFrom suspended node's downstream connections"] A-->B-->C-->D-->E-->F-->G-->H-->I style A fill:#1e2640,stroke:#6c8cff,color:#e2e8f0 style F fill:#2d2616,stroke:#fbbf24,color:#e2e8f0 style G fill:#2d2616,stroke:#fbbf24,color:#e2e8f0 style I fill:#162d22,stroke:#34d399,color:#e2e8f0

What Gets Saved

SuspendedExecutionData contains everything needed to resume:

FieldPurpose
ExecutionResIdUnique identifier for this execution — used to look it up on resume
ThreadVersionIdWhich thread version was executing
SuspendedNodeKeyThe ProcessElementKey of the node that triggered suspension
MemoryThe full ExecutionMemory — all variables, node outputs, loop state, scope stack
ExecutionIDThe thread execution's ID for correlation
ExecutorIDProcessThreadID and related IDs for reloading the thread definition
InputDataThe original trigger data
CompletedNodeCountProgress counter at the time of suspension
SuspendedAtTimestamp for SLA tracking and expiry logic
TenantIdMulti-tenant isolation
ParentProcessContextReference to the parent process for resuming the full hierarchy

Types of Suspension

Approval / HIL

The workflow presents data to a human actor and waits for approve/reject/modify input. Uses "waiting" port. The HIL payload is built from the node's NodeFieldManifest HilPolicy descriptors.

Delay / Timer

The workflow pauses for a configured duration. A PendingDelay record is scheduled. A background service fires the resume when the time arrives.

Form Fill

A URL to a rendered form is returned. The workflow pauses until the form is submitted. Uses "pending" port. Form data is merged into the input bag on resume.

Event Wait

The workflow waits for a specific external event matching a correlation key. When a matching event arrives (via API or Kafka), the correlation service triggers the resume.

HIL — Human in the Loop

HIL suspension is the most sophisticated type. When a node suspends for human review, it builds a HIL payload — a description of the fields the human should see and interact with. This payload is constructed from the NodeFieldManifest's HilPolicy descriptors:

For full HIL label and policy details, see the HIL Labels page in the Expression Engine documentation.

Durability requirement The SQL-backed ISuspendedExecutionRegistry ensures suspended executions survive server restarts. The in-memory registry is only appropriate for unit testing. Always ensure the SQL registry is registered in production.