The Node Executor
The Node Executor is where business logic lives. Every node type — HTTP request,
approval gate, LLM prompt, data transform, email sender — implements this layer.
All executors share a structured 8-stage lifecycle enforced by BaseNodeExecutor.
Architecture: BaseNodeExecutor
All node executors extend BaseNodeExecutor, an abstract class that defines
the execution lifecycle via a strict hook-based pattern. The entry point is
ExecuteAsync, which calls OnExecute, which runs all 8 stages
in order. Executor implementations override only the stages they need.
This design enforces a universal structure across all 60+ node types. When you read a node executor, you know exactly where to look for each kind of logic.
The 8-Stage Lifecycle
OnEntry
Initialize state. Read config, set defaults, prepare any internal state needed for the node run.
OnEntryValidate
Load and validate the config bag. Verify required fields exist. Fail fast with a clear error if something is missing.
OnPreValidationGuardRails
Pre-execution safety checks. Rate limiting, security gates, permission checks. Can short-circuit execution.
OnPreProcess
Optional pre-processing. Map inputs, transform data, acquire resources. Returns a result to short-circuit if needed.
OnProcess
The core business logic. API call, LLM prompt, database query, or transform. Returns the result.
OnPostProcess
Post-processing. Transform output data, publish events, update state.
OnPostValidationGuardRails
Post-execution safety checks. Validate the result, apply output policies, run compliance checks.
OnExit
Cleanup. Release resources, log final telemetry, close connections. Always runs even if earlier stages had issues.
Stage Progress Reporting
After every stage, ReportNodeProgressByStage is called with an eNodeStage
enum value. This fires a progress event that the SignalR event producer picks up and
broadcasts to connected Studio clients in real time — this is why the workflow UI can show
a node animating through its stages as it executes.
| eNodeStage | When fired |
|---|---|
Initiated | Start of OnExecute, before OnEntry |
Entry | After OnEntry |
EntryValidate | After OnEntryValidate |
PreValidationGuardRails | After OnPreValidationGuardRails |
PreProcess | After OnPreProcess |
Process | After OnProcess |
PostProcess | After OnPostProcess |
PostValidationGuardRails | After OnPostValidationGuardRails |
Exit | After OnExit |
Completed | If result.IsSuccess == true |
Error | If result.IsSuccess == false |
How to Write a Node Executor
Implement a class that extends BaseNodeExecutor. At minimum, override
OnEntryValidate (to load your config) and OnProcess
(to run your logic). Everything else is optional.
public class MyNodeExecutor : BaseNodeExecutor
{
private MyNodeSettings _settings;
// Stage 2 — load and validate config
protected override async Task OnEntryValidate(
NodeExecutionContext ctx, CancellationToken ct)
{
_settings = await LoadAndValidateConfigAsync<MyNodeSettings>(ctx, ct);
}
// Stage 5 — core business logic
protected override async Task<NodeExecutionResult> OnProcess(
NodeExecutionContext ctx, CancellationToken ct)
{
// ctx.ElementExecutionContext.InputData — upstream outputs
// _settings — fully resolved, expression-evaluated config
var result = await MyServiceCall(_settings.ApiUrl, ct);
return NodeExecutionResult.Success(
outputPortKey: "success",
outputData: new Dictionary<string, object> { ["result"] = result });
}
}
The NodeExecutionContext
The executor receives a NodeExecutionContext which carries:
ElementExecutionContext— full element context with resolved config, input/output bags, connector data.RuntimeInfo— result accumulator used by the base class to store intermediate results.Processor— reference to theOrchestrationProcessor, used by control-flow nodes (loop, fork, suspend) that need to signal the orchestrator.CurrentContext— the full execution hierarchy context for event broadcasting.
NodeFieldManifest in Executors
Executor settings classes can optionally implement INodeFieldManifestSource
to declare per-field policies. This tells the expression pipeline which fields should
be evaluated at which stage, how they participate in data flow, and how they appear
in HIL forms. Without a manifest, all fields default to ExpressionPolicy.Default
(template evaluation at AtConfigLoad stage).
For details on the manifest system, see the Node Field Policies page.