Portal Community

The Three Layers

Every HIL channel implementation is built from three independent layers that never need to know about each other:

Layer 1 — What to show

Declared by the node type in its NodeFieldManifest. The HilPolicy on each field descriptor states whether the field is sent to HIL, how it's displayed, and whether the human can edit it. This is channel-agnostic.

Layer 2 — How to show it

Implemented by the channel node executor. Reads the resolved HIL fields, formats them for Slack / WhatsApp / email / etc., embeds the ExecutionResId, sends the message, and returns the "waiting" port.

Layer 3 — How to resume

Always the same: the channel's inbound webhook handler extracts the ExecutionResId and the human's response, then calls ContinueAsync. The engine does the rest.

End-to-End Flow

1

Node executor reads the NodeFieldManifest

The executor calls IHilLabelResolver.ResolveAllAsync to get all fields where HilPolicy.SendToHil == true. Each resolved field carries its label, description, current value, DisplayMode, and InputMode.

DisplayMode values: ReadableContext (show value), ReadableMasked (show as ****), Concealed (hide entirely).
InputMode values: Locked (read-only), EditableOptional, RequiredFromHuman, PrefilledEditable.

2

Executor formats the payload for the target channel

The resolved fields are translated into whatever format the channel needs — Slack Block Kit blocks, a WhatsApp interactive message, an HTML email, etc. Fields with InputMode = Locked become read-only display items. Fields with editable input modes become input controls (buttons, text inputs).

3

ExecutionResId is embedded in the outgoing message

The ExecutionResId is the correlation key. It is embedded in the outgoing message so it comes back with the human's response. How it's embedded depends on the channel — see each channel's page for details.

// Available from the execution context
string resId = elementContext.ExecutionContext.ExecutionResId;
4

Node returns the "waiting" port — engine suspends

The executor returns a NodeExecutionResult with OutputPortKey = "waiting". The engine serializes the full ExecutionMemory (variables, node outputs, scope stack, loop state) into a SuspendedExecutionData record and persists it to SQL. The in-process thread is torn down. The server is free.

return NodeExecutionResult.Suspend("waiting");
5

Human receives and responds via the channel

The workflow is fully paused in the database. The human sees the message, reviews the fields, and responds — clicking a button, filling a form, replying to a message. This can happen seconds or days later. Server restarts don't matter.

6

Webhook handler receives the response

The channel fires an inbound event to your registered webhook endpoint. Your handler extracts the ExecutionResId from the payload and maps the human's response to a Dictionary<string, object> keyed by field name.

7

ContinueAsync resumes the workflow

The handler calls IWorkflowContinuationOrchestrator.ContinueAsync. The engine loads SuspendedExecutionData from SQL, restores ExecutionMemory, merges the response data into the input bag, and restarts the execution loop from the node's downstream connections.

await _continuationOrchestrator.ContinueAsync(
    executionResId: resId,
    responseData:   humanResponse,
    cancellationToken: ct);

SuspendedExecutionData — What Gets Saved

When the engine suspends, it persists everything needed for a full resume. As a channel developer, the only field you need to care about is ExecutionResId.

FieldWhat it contains
ExecutionResIdThe unique correlation key — embed this in every outgoing HIL message
MemoryFull ExecutionMemory: all variables, node outputs, scope stack, loop stack
SuspendedNodeKeyThe ProcessElementKey of the node that triggered suspension
ThreadVersionIdWhich thread definition was running
InputDataOriginal trigger data
SuspendedAtTimestamp for SLA tracking and expiry
TenantIdMulti-tenant isolation
ParentProcessContextReference to parent process for full hierarchy resume

NodeFieldManifest — HilPolicy Reference

The HilPolicy on each NodeFieldDescriptor controls the field's behaviour in HIL interactions:

PropertyTypeMeaning
SendToHilboolIf false, this field is invisible to HIL entirely — never include it in your message
DisplayModeHilDisplayModeReadableContext: show value · ReadableMasked: show as **** · Concealed: do not render
InputModeHilInputModeLocked: display only · EditableOptional: can change · RequiredFromHuman: must provide · PrefilledEditable: pre-filled but editable
Labelstring?Human-readable field label
Descriptionstring?Additional context shown to the human
LabelExpressionPolicyExpressionPolicy?When non-null, label/description are dynamic expressions resolved at runtime via IHilLabelResolver

Correlation Key Strategies

Different channels have different constraints on how much data can travel with an interaction event. Choose the strategy that fits:

StrategyHow it worksBest for
Direct embed The ExecutionResId (a GUID string) is placed directly in the interaction payload field (e.g. Slack action_id, Telegram callback_data) Slack, Telegram, Discord — payloads support long strings
Metadata embed The ExecutionResId is stored in a dedicated metadata/private-metadata field separate from the visible interaction Slack (modal/view metadata), Teams (Adaptive Card data)
Correlation table A short token (8–12 chars) is stored in a HilCorrelation table mapping token → ExecutionResId. The short token travels with the message. WhatsApp (256-char payload limit), SMS, any channel with tight payload limits
Signed URL The ExecutionResId is embedded in a signed URL in the message. The human clicks the link, which hits a BizFirst endpoint. Email, any channel where you can include links
Session context The active HIL execution for a given phone/user ID is tracked in a table. Inbound reply is correlated by sender identity. SMS (reply-based), WhatsApp text replies
The golden rule Whatever strategy you use, at the moment the human responds your webhook handler must be able to recover the ExecutionResId. Everything else is implementation detail.