Flow & Symmetric Mapping
How every Request field maps to a Reply field · Action resolution · UI rendering · Node executor integration
Full HIL Flow
flowchart TD
WF[Workflow Engine] -->|Phase 1 Suspension| B[BaseHilNodeExecutor]
B -->|BuildPresentations| P[HilPresentationRequestEnvelope]
P -->|Dispatch simultaneously| C1[Web Channel]
P -->|Dispatch simultaneously| C2[Slack Channel]
P -->|Dispatch simultaneously| C3[Email Channel]
C1 -->|Renders Presentations| U[Actor]
C2 -->|Slack blocks| U
C3 -->|Email template| U
U -->|Clicks action button| R[HilPresentationReplyEnvelope]
R -->|POST to ResumeCallbackURL| E[Node Executor]
E -->|Routes on Decision| O1[approved port]
E -->|Routes on Decision| O2[rejected port]
E -->|Routes on Decision| O3[submitted port]
O1 --> WF2[Workflow continues]
O2 --> WF2
O3 --> WF2
style P fill:#2d2860,stroke:#7c6af7,color:#e6edf3
style R fill:#0e4f5c,stroke:#22d3ee,color:#e6edf3
style U fill:#1c2128,stroke:#3fb950,color:#e6edf3
Action Resolution — Per Step
flowchart LR
S[Start step N] --> Q{Presentation N\nActions non-empty?}
Q -->|Yes| PA[Use Presentation.Actions\nfor this step]
Q -->|No| EA[Use envelope-level\nActions as fallback]
PA --> R[Render buttons for step N]
EA --> R
R --> A[Actor clicks a button]
A --> D[Set Reply.Decision + Reply.ActionName\nSet DataSteps N .ActionName]
style PA fill:#2d2860,stroke:#7c6af7,color:#e6edf3
style EA fill:#1c2128,stroke:#30363d,color:#8b949e
style D fill:#0e4f5c,stroke:#22d3ee,color:#e6edf3
Request → Reply Field Mapping
Symmetric Mapping
// Session IDs — echoed unchanged
Request.Session.CorrelationID ──echoed──▶ Reply.CorrelationID
Request.Session.ReplyToID ──echoed──▶ Reply.ReplyToID
Request.Session.EngageSessionID ◄─────────▶ Reply.EngageSessionID
// Action routing
Request.Actions[N].ActionKey ──maps to─▶ Reply.ActionName // echoed back
Request.Actions[N].ActionKey (routing) ──maps to─▶ Reply.Decision // standard token
// Per-step action resolution (new in v1.3)
Request.Presentations[N].Actions (non-empty) ────────▶ buttons shown for step N
Request.Actions[] (fallback when step empty) ────────▶ buttons shown for step N
// Presentation ↔ DataSteps
Request.Presentations[N].Order ──maps to─▶ Reply.Payload.DataSteps[N].StepOrder
Request.Presentations[N].PresentationType ──maps to─▶ Reply.Payload.DataSteps[N].PresentationType
// Assignees ↔ Responders
Request.Assignees[N].ActorID ──maps to─▶ Reply.Responders[N].ActorID
// ReplyOptions control payload shape
Request.ReplyOptions.EnableSteppedDataReturn ────────▶ Reply.Payload.DataSteps[] populated?
Request.ReplyOptions.EnableFlatDataReturn ────────▶ Reply.Payload.Data{} populated?
// Resume endpoint
Request.Session.ResumeCallbackURL ◄────────── (Reply is POSTed here)
UI Rendering Engine Walkthrough
flowchart TD
A[Read HilType] --> B[Identify current user from Assignees]
B --> C{For each Presentation\nordered by Order}
C -->|form| D[Read FormID or FormSchemaJson\nApply PrePopulate defaults\nDisplay CustomData in context panel\nRender Atlas Form fields]
C -->|chat| E[Read PromptMessage\nDisplay CustomData context\nRender reply input field]
C -->|approval| F[Read SummaryText + ContextDocURL\nDisplay CustomData sidebar\nRender approve or reject panel]
C -->|info| G[Display CustomData read-only\nNo input collected]
D --> H[Resolve actions for this step]
E --> H
F --> H
G --> H
H --> I{Presentation.Actions\nnon-empty?}
I -->|Yes| J[Use Presentation.Actions]
I -->|No| K[Use envelope Actions]
J --> L[Render action buttons]
K --> L
L --> M[Actor completes step]
M --> N{More steps?}
N -->|Yes| C
N -->|No - final action clicked| O[Build HilPresentationReplyEnvelope\nSet Decision + ActionName\nPopulate DataSteps + Data\nEcho CorrelationID + ReplyToID]
O --> P[POST to Session.ResumeCallbackURL]
style A fill:#2d2860,stroke:#7c6af7,color:#e6edf3
style P fill:#0e4f5c,stroke:#22d3ee,color:#e6edf3
style J fill:#2d2860,stroke:#7c6af7,color:#e6edf3
style K fill:#1c2128,stroke:#30363d,color:#8b949e
Node Executor Integration
C# — BaseHilNodeExecutor hooks
// Phase 1 — each node provides Presentations[]; base wires Session + Assignees + Channels + Actions
protected abstract List<HilPresentation> BuildPresentations(
TSettings settings,
List<ResolvedActor> actors);
// Phase 2 — base deserializes approvalDecision into typed envelope
protected HilPresentationReplyEnvelope? ParseHilReply(
Dictionary<string, object?> inputData);
// Phase 2 — each node processes the typed reply (zero JSON parsing)
protected abstract Task<NodeExecutionResult> ProcessHilResponse(
HilPresentationReplyEnvelope reply,
TSettings settings,
ProcessElementExecutionContext context,
CancellationToken cancellationToken);
| Node | Reads from Reply |
|---|---|
FormNodeExecutor | reply.Payload.DataSteps[0].Data — form fields. reply.Payload.Data — flat bag. |
FormTriggerNodeExecutor | Same as FormNodeExecutor. |
ChatNodeExecutor | reply.Payload.ReplyText (typed), reply.ActionName for classify. |
ChatTriggerNodeExecutor | reply.Payload.ReplyText, reply.Responders[0].ActorID. |
ChatReceiveNodeExecutor | reply.Payload.DataSteps[0].Data["ReplyText"], channel markers. |
ApprovalNodeExecutor | reply.Decision for port routing, reply.Payload.Comment, reply.Payload.Reason. |