Flow Studio
OnAfterExecuteAsync
Called after the executor returns a successful result. The full NodeExecutionResult, output data, and duration are available. The richest event for metrics collection, SLA tracking, and post-execution notifications.
What Is Available
| Data | Notes |
|---|---|
args.Result | The full NodeExecutionResult — includes Output, Status, PinnedData |
args.Result.Output | The executor's output object (deserialise to specific type) |
args.DurationMs | Total executor time in milliseconds (measured by the engine) |
args.NodeType | TypeCode string for filtering subscribers by node type |
args.Context | Snapshot — includes full execution memory at time of completion |
Metrics Collection Pattern
public async Task OnAfterExecuteAsync(NodeExecutionEventArgs args, CancellationToken ct)
{
// Record execution duration per node type
await _metrics.RecordAsync(new MetricPoint
{
Name = "node.execution.duration_ms",
Value = args.DurationMs ?? 0,
Labels = new Dictionary<string, string>
{
["node_type"] = args.NodeType,
["process_id"] = args.ProcessId,
["tenant_id"] = args.TenantId,
["result_status"]= args.Result!.Status.ToString()
}
}, ct);
// Increment success counter
_metrics.IncrementCounter("node.execution.success",
("node_type", args.NodeType),
("tenant_id", args.TenantId));
}
SLA Tracking Pattern
public async Task OnAfterExecuteAsync(NodeExecutionEventArgs args, CancellationToken ct)
{
var sla = await _slaRepo.GetSlaForNodeTypeAsync(args.NodeType, ct);
if (sla == null) return;
if (args.DurationMs > sla.MaxDurationMs)
{
await _alertService.SendSlaBreachAsync(new SlaBreachAlert
{
NodeId = args.NodeId,
NodeType = args.NodeType,
ExecutionId = args.ExecutionId,
ActualMs = args.DurationMs!.Value,
SlaMs = sla.MaxDurationMs,
BreachPercent = (double)args.DurationMs!.Value / sla.MaxDurationMs * 100
}, ct);
}
}
Accessing Output Data
The Result.Output property is typed as object. Cast or deserialise to access specific fields:
public async Task OnAfterExecuteAsync(NodeExecutionEventArgs args, CancellationToken ct)
{
if (args.NodeType != "HttpRequest") return;
// Cast to the known output type for this executor
if (args.Result!.Output is HttpRequestOutput httpOutput)
{
if (httpOutput.StatusCode >= 400)
{
await _alertService.SendAsync($"HTTP node returned {httpOutput.StatusCode}", ct);
}
}
}
Notification Pattern
public async Task OnAfterExecuteAsync(NodeExecutionEventArgs args, CancellationToken ct)
{
// Notify integration layer that a specific node completed
if (args.NodeType == "Approval" && args.Result!.Status == NodeResultStatus.Success)
{
await _eventBus.PublishAsync(new ApprovalNodeCompletedEvent
{
ExecutionId = args.ExecutionId,
NodeId = args.NodeId,
Decision = ((ApprovalOutput)args.Result.Output!).Decision
}, ct);
}
}
OnAfter only fires on Success: If the executor throws, OnAfterExecuteAsync is NOT called — OnErrorAsync fires instead. The two events are mutually exclusive.