Portal Community

The Suspend Result

// NodeExecutionResult has a static factory for suspension
public static NodeExecutionResult Suspend(SuspendPayload payload)
{
    return new NodeExecutionResult
    {
        ResultType = NodeResultType.Suspend,
        Payload    = payload
    };
}

SuspendPayload Schema

public class SuspendPayload
{
    public string         ExecutionResId  { get; init; }  // GUID — generated by HIL executor
    public string         SuspendedNodeId { get; init; }  // this node's ID
    public HilTaskType    TaskType        { get; init; }  // Approval | UserForm | Review
    public string         ActorId         { get; init; }  // resolved actor
    public string         ActorType       { get; init; }  // User | Group | Agent
    public DateTimeOffset? ExpiresAt      { get; init; }  // null if no timeout
    public object?        TaskPayload     { get; init; }  // form schema, task context
    public string?        EscalationActorId { get; init; }
    public string         TimeoutBehavior { get; init; }  // Escalate|AutoApprove|AutoReject|Fail
}

BaseHILExecutor Implementation

// How a concrete HIL executor builds and returns the Suspend result
public class ApprovalExecutor : BaseHILExecutor<ApprovalInput, ApprovalOutput>
{
    protected override SuspendPayload BuildSuspendPayload(
        NodeExecutionContext ctx, ApprovalInput input)
    {
        var timeoutDuration = input.TimeoutDuration != null
            ? XmlConvert.ToTimeSpan(input.TimeoutDuration)
            : (TimeSpan?)null;

        return new SuspendPayload
        {
            ExecutionResId    = Guid.NewGuid().ToString(),  // unique per suspension
            SuspendedNodeId   = ctx.NodeId,
            TaskType          = HilTaskType.Approval,
            ActorId           = ResolveActor(ctx, input),  // resolve static or dynamic
            ActorType         = input.ActorType,
            ExpiresAt         = timeoutDuration.HasValue
                                ? DateTimeOffset.UtcNow.Add(timeoutDuration.Value)
                                : null,
            EscalationActorId = input.EscalationActorId,
            TimeoutBehavior   = input.TimeoutBehavior,
            TaskPayload       = new
            {
                title       = input.Title,
                instruction = input.Instruction,
                strategy    = input.Strategy
            }
        };
    }
}

What the Engine Does on Suspend

  1. Receives NodeResultType.Suspend result from the executor.
  2. Calls HILSuspensionService.SuspendAsync(executionContext, payload).
  3. Suspension service serialises ExecutionMemory, writes to Process_SuspendedExecutions.
  4. Suspension service creates HIL task record in Process_HilTasks.
  5. Engine returns 202 Accepted to the caller — execution is now suspended.
No await after Suspend: Once a HIL executor returns Suspend, no further code runs in that executor. The BuildSuspendPayload method is the entire executor implementation. All post-suspension logic lives in the engine and HIL services.