Documentation

The HIL Payload Build Process

flowchart TD S["Node executor returns\n'waiting' or 'pending' port key"] M["Collect NodeFieldManifest\nfrom executor settings class"] F["Filter to fields with\nHilPolicy.SendToHil = true"] L["Resolve Label and Description\nvia IHilLabelResolver\nAll runtime directives available"] A["Apply DisplayMode and InputMode\nto each field entry"] B["Build HilPayload\nList of HilFieldDescriptor objects"] P["Persist with SuspendedExecutionData\nStored alongside ExecutionMemory"] UI["Studio UI renders form\nfrom HilPayload"] S --> M --> F --> L --> A --> B --> P --> UI style S fill:#1e2640,stroke:#6c8cff,color:#e2e8f0 style L fill:#221c3e,stroke:#a78bfa,color:#e2e8f0 style B fill:#162d22,stroke:#34d399,color:#e2e8f0 style UI fill:#2d2616,stroke:#fbbf24,color:#e2e8f0

IHilLabelResolver

IHilLabelResolver is the interface responsible for evaluating the Label and Description strings declared in a field's HilPolicy. It receives the raw label string and the full expression context at the point of suspension, then returns the resolved string.

Because labels are resolved at suspension time — after all upstream nodes have run and all Tier 1 and Tier 2 expressions have been evaluated — they have access to the full set of runtime directives:

DirectiveIn label expressionsExample
@{input:key}Yes"Employee: @{input:firstName} @{input:lastName}"
@{output:nodeKey.field}Yes"Salary calculated by: @{output:calcNode.method}"
@{var:key}Yes"Threshold: @{var:approvalLimit}"
@{env:KEY}Yes"Environment: @{env:DEPLOY_ENV}"
@{context:field}Yes"Process ID: @{context:processId}"

Label expressions are always evaluated in template mode (see Expression Syntax). The result is always a string. Strict mode is not supported for labels.

Declaring HIL Labels in a Field Manifest

public class ApprovalNodeSettings : BaseNodeExecutorSettings, INodeFieldManifestSource
{
    public string EmployeeId { get; set; }
    public decimal SalaryAmount { get; set; }
    public string Department { get; set; }

    public NodeFieldManifest GetFieldManifest() => new NodeFieldManifest()
        .Add(nameof(EmployeeId), f => f
            .Expression(EvaluationStage.AtInputReady, EvaluatorKind.Template)
            .DataFlow(DataFlowRole.Input)
            .Hil(h => h
                .SendToHil(
                    displayMode: DisplayMode.ReadableContext,
                    inputMode:   InputMode.Locked,
                    label:       "Employee",
                    description: "@{output:fetchEmployee.fullName} — @{output:fetchEmployee.jobTitle}")))
        .Add(nameof(SalaryAmount), f => f
            .Expression(EvaluationStage.AtInputReady, EvaluatorKind.Template)
            .DataFlow(DataFlowRole.Input)
            .Hil(h => h
                .SendToHil(
                    displayMode: DisplayMode.ReadableContext,
                    inputMode:   InputMode.PrefilledEditable,
                    label:       "Proposed Salary",
                    description: "Current salary: @{output:fetchEmployee.currentSalary}")))
        .Add(nameof(Department), f => f
            .Expression(EvaluationStage.AtInputReady, EvaluatorKind.Template)
            .DataFlow(DataFlowRole.Input)
            .Hil(h => h
                .SendToHil(
                    displayMode: DisplayMode.ReadableContext,
                    inputMode:   InputMode.Locked,
                    label:       "Department",
                    description: "")));
}

HilFieldDescriptor — The Output Object

After label resolution, each field with SendToHil = true becomes a HilFieldDescriptor entry in the payload. This is what the Studio UI consumes to render the approval form.

PropertySourceDescription
FieldKeyField name from manifestIdentifies which config field this entry describes
LabelHilPolicy.Label after expression resolutionHuman-readable label shown in the form
DescriptionHilPolicy.Description after expression resolutionLonger contextual description shown under the label
CurrentValueResolved field value at time of suspensionThe value the node had when it suspended
DisplayModeHilPolicy.DisplayModeHow the value is rendered (visible / masked / concealed)
InputModeHilPolicy.InputModeWhether the human can edit the value
FieldTypeInferred from value typeUsed by the UI to render the appropriate input control

What Happens on Resume

When the human submits the HIL form, the submitted values are received by the ContinueAsync handler. For each field where InputMode is not Locked, the submitted value replaces the current field value in the suspended execution's config bag.

When the thread resumes execution, the node's config fields reflect the human's input. If the human changed SalaryAmount from 80,000 to 85,000, the executor will see 85,000 as its SalaryAmount setting when it runs after resumption.

flowchart LR SUSP["Thread suspended\nHIL payload persisted"] FORM["Studio renders\napproval form from payload"] HUMAN["Human reviews context\nedits editable fields\nsubmits decision"] MERGE["Submitted values merged\ninto suspended config bag"] RESUME["ContinueAsync called\nThread resumes from\nsuspended node's outputs"] SUSP --> FORM --> HUMAN --> MERGE --> RESUME style SUSP fill:#2d2616,stroke:#fbbf24,color:#e2e8f0 style HUMAN fill:#2d2616,stroke:#fbbf24,color:#e2e8f0 style RESUME fill:#162d22,stroke:#34d399,color:#e2e8f0

PreHilData and the Audit Trail

Before the HIL payload is built, the current state of all fields is snapshotted into PreHilData. This snapshot is persisted alongside SuspendedExecutionData and is never modified by the human's input.

After resumption, both PreHilData (the before state) and Hil (the human's submitted values) are available for comparison in audit trails, compliance checks, and post-hoc analysis. The executor can access both bags via the element context if it needs to act on the delta.

Labels are evaluated at suspension time, not design time Label expressions like "Approve salary increase for @{output:fetchEmployee.fullName}" are not validated until the node actually suspends at runtime. A typo in an element key will produce a label with unresolved placeholders, not a deployment-time error. Always test HIL nodes end-to-end to verify label rendering.