Suspension Policies
A SuspensionPolicy is a node-level policy that controls how long the node
waits, when it times out, how it escalates, and how it reminds actors — for nodes that
suspend workflow execution and wait for human response.
SuspensionPolicy is set on the manifest itself
(not on individual field descriptors) and applies to the entire node's wait behaviour.
Structure
public sealed class SuspensionPolicy
{
public int TimeoutSeconds { get; init; } = 0;
public string TimeoutPortKey { get; init; } = string.Empty;
public TimeoutBehavior TimeoutBehavior { get; init; } = TimeoutBehavior.AbsoluteDeadline;
public int[] ReminderIntervalSeconds { get; init; } = Array.Empty<int>();
public int SlaThresholdSeconds { get; init; } = 0;
public bool EmitSlaBreachEvent { get; init; } = false;
public bool AllowAdminForceComplete { get; init; } = false;
public bool CapturePreSuspensionSnapshot { get; init; } = false;
}
TimeoutSeconds
Maximum seconds the node may remain suspended before the timeout action triggers.
0 means no timeout (waits indefinitely).
| Duration | Seconds |
|---|---|
| 1 hour | 3600 |
| 12 hours | 43200 |
| 24 hours | 86400 |
| 48 hours | 172800 |
| 7 days | 604800 |
TimeoutPortKey
The output port the node routes to when the timeout fires. Must be defined in the node's port configuration.
// Output ports
"approved" → approval accepted path
"rejected" → approval rejected path
"expired" → timeout / escalation path ← TimeoutPortKey points here
Common values: "expired", "timeout", "escalated".
TimeoutBehavior
AbsoluteDeadline
The timeout clock starts at the moment of suspension. The node has exactly TimeoutSeconds from suspension regardless of SLA configuration.
Use when: There is a hard business deadline that counts from task creation.
// Timeline
T+0h Node suspends ← Clock starts
T+24h SLA breach event (if slaThresholdSeconds = 86400)
T+48h Timeout fires ← TimeoutPortKey route
AfterSlaThreshold
The timeout clock starts after the SLA threshold has been breached. The actor has a grace period equal to SlaThresholdSeconds before the timeout countdown begins.
Use when: There is a soft SLA commitment and a hard cutoff only after the SLA is missed.
// Timeline (SlaThreshold = 12h, TimeoutSeconds = 24h)
T+0h Node suspends
T+12h SLA breach event ← Countdown starts here
T+36h Timeout fires ← T+12h + 24h
ReminderIntervalSeconds
Array of intervals (in seconds) at which reminder notifications are sent to the assigned actor.
Intervals are relative to when the countdown starts (depends on TimeoutBehavior).
Set to an empty array for no reminders.
"reminderIntervalSeconds": [86400, 43200, 3600]
// Reminders at 24h, 12h, and 1h before timeout (AbsoluteDeadline)
"reminderIntervalSeconds": [43200, 7200]
// Reminders at 12h and 2h after SLA breach (AfterSlaThreshold)
SlaThresholdSeconds
Seconds after which the suspension is an SLA breach. Triggers EmitSlaBreachEvent and starts the AfterSlaThreshold countdown. Set to 0 for no SLA monitoring.
SLA vs Timeout: SLA = the commitment ("should be done in X hours"). Timeout = the hard cutoff ("if not done, we move on"). Typically SlaThresholdSeconds < TimeoutSeconds.
EmitSlaBreachEvent
When true, emits a domain event when SlaThresholdSeconds is exceeded. Consumed by monitoring dashboards, SLA compliance reports, and automatic escalation workflows.
AllowAdminForceComplete
| Set to true | Set to false |
|---|---|
| Approval nodes — admin override is an acceptable escape hatch when a vote is stalled | Data collection forms — actual human data is required; admin cannot substitute the submission |
CapturePreSuspensionSnapshot
When true, captures a complete snapshot of workflow state immediately before
suspension — for audit trail, replay capability, and debugging. Disable for high-volume
workflows where storage cost matters.
Complete Examples
High-stakes approval — hard 48h deadline
suspensionPolicy: new SuspensionPolicy
{
TimeoutSeconds = 172800, // 48 hours
TimeoutPortKey = "expired",
TimeoutBehavior = TimeoutBehavior.AbsoluteDeadline,
ReminderIntervalSeconds = new[] { 86400, 43200, 3600 }, // 24h, 12h, 1h before expiry
SlaThresholdSeconds = 86400, // 24h SLA
EmitSlaBreachEvent = true,
AllowAdminForceComplete = true,
CapturePreSuspensionSnapshot = true
}
Data collection form — SLA + grace period
suspensionPolicy: new SuspensionPolicy
{
TimeoutSeconds = 86400, // 24h after SLA breach
TimeoutPortKey = "expired",
TimeoutBehavior = TimeoutBehavior.AfterSlaThreshold,
ReminderIntervalSeconds = new[] { 43200, 7200 }, // 12h and 2h after breach
SlaThresholdSeconds = 43200, // 12h SLA
EmitSlaBreachEvent = true,
AllowAdminForceComplete = false, // actual form data required
CapturePreSuspensionSnapshot = true
}
No suspension (action node)
NodeExecutorManifest.From(
ProcessElementTypeCode,
fields: new[] { ... },
suspensionPolicy: null // executes immediately, no waiting
)
Suspension Infrastructure Services
| Service | Role |
|---|---|
SuspensionPolicyOrchestrator | Reads the policy and registers all timers/events at suspension time |
ISuspensionTimerService | Manages timeout and reminder timers |
ISuspensionReminderService | Delivers reminder notifications to actors |
ISuspensionSlaMonitor | Tracks SLA thresholds and emits breach events |
The node executor does not call these directly — it calls SuspendAsync() on BaseNodeExecutor, which triggers the orchestrator.