Portal Community

Why Subscribers Exist

When you need the same behaviour applied to every node execution — write an audit record, increment a counter, send a Slack alert on failure — the wrong place to implement it is inside each executor. That spreads identical logic across dozens of classes and makes it impossible to change consistently.

INodeEventSubscriber solves this with the observer pattern. Subscribers are registered once in DI and the engine calls them automatically at four points in the lifecycle of every node execution, regardless of which executor is running.

Before Execution

Called before the executor runs. Use for audit start records, pre-execution validation checks, or request enrichment.

After Execution

Called after a successful result. Full output is available. Use for metrics recording, downstream notifications, SLA tracking.

On Error

Called when the executor throws. The exception and retry context are available. Use for alerting, error catalogue logging.

On Skipped

Called when a node is skipped (conditional routing). Use for completeness auditing — every node's outcome is recorded.

Subscriber vs. GuardRail Engine

A common question: when do I use a subscriber vs. a GuardRail engine? The distinction is clear:

ConcernUse SubscriberUse GuardRail Engine
Audit loggingYesNo
Metrics / telemetryYesNo
Block execution based on policyNoYes
Rate limitingNoYes
Post-execution notificationsYesNo
PII content filteringNoYes
Important: Subscribers are not designed to block execution. While throwing in OnBeforeExecuteAsync will abort the node, that pattern is reserved for GuardRail engines. Use subscribers for observation, not enforcement.

The Four Lifecycle Events

1

OnBeforeExecuteAsync

Engine is about to call the executor. Context snapshot and input data are available.

2

OnAfterExecuteAsync

Executor returned successfully. Full NodeExecutionResult including output is available.

3

OnErrorAsync

Executor threw an exception. The exception, retry count, and context are available.

4

OnSkippedAsync

Node was skipped by the router (conditional branching). Skip reason is provided.

Execution Order

When multiple subscribers are registered, all are called for each event in the order they were registered in DI. There is no short-circuit — if subscriber A throws during OnAfterExecuteAsync, subscriber B is still called. Subscriber exceptions are caught and logged but do not affect workflow execution.

Performance note: Subscriber methods run on the same thread as the executor and add to total node execution time. Keep subscriber logic light. For heavy operations (writing to a remote audit store), use fire-and-forget inside the subscriber rather than awaiting network calls synchronously.

Common Use Cases

Use CaseEvents UsedTypical Implementation
Centralised audit logBefore + After + Error + SkippedWrite one row per lifecycle event to audit_node_executions
Duration metricsBefore (record start) + After (compute delta)Store start time in a dictionary keyed by executionId+nodeId
Failure alertingOnErrorEnqueue a message to the alerting queue
Execution tracingAll fourAttach structured log entries to the OpenTelemetry span
SLA trackingBefore + AfterCompare elapsed time against node SLA configuration