Flow Studio
Triggering Suspension
A HIL executor signals suspension by returning NodeExecutionResult.Suspend(payload). The engine detects this result and hands off to HILSuspensionService — the executor does nothing else.
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
- Receives
NodeResultType.Suspendresult from the executor. - Calls
HILSuspensionService.SuspendAsync(executionContext, payload). - Suspension service serialises ExecutionMemory, writes to
Process_SuspendedExecutions. - Suspension service creates HIL task record in
Process_HilTasks. - 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.