Documentation

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

flowchart TD START(["ExecuteAsync"]) OE["OnEntry\nInitialize state · Read config · Set defaults"] OEV["OnEntryValidate\nLoad settings · Validate required fields"] OPG["OnPreValidationGuardRails\nRate limits · Security gates · Permission checks"] OPP["OnPreProcess\nTransform inputs · Acquire resources"] OP["OnProcess\nBusiness logic · API calls · AI · Data transforms"] OPoP["OnPostProcess\nTransform output · Publish events · Update state"] OPoG["OnPostValidationGuardRails\nOutput validation · Compliance checks"] OX["OnExit\nCleanup · Telemetry · Close connections"] CHK{"IsSuccess?"} OS["OnSuccess\nStage: Completed"] OF["OnError\nStage: Error"] END(["NodeExecutionResult"]) START-->OE-->OEV-->OPG-->OPP-->OP-->OPoP-->OPoG-->OX-->CHK CHK-- Yes -->OS-->END CHK-- No -->OF-->END style START fill:#162040,stroke:#6c8cff,color:#c8d8ff style OP fill:#162d22,stroke:#34d399,color:#e2e8f0 style CHK fill:#2d2616,stroke:#fbbf24,color:#e2e8f0 style OS fill:#162d22,stroke:#34d399,color:#e2e8f0 style OF fill:#2d1616,stroke:#f87171,color:#e2e8f0 style END fill:#162040,stroke:#6c8cff,color:#c8d8ff
Stage 1

OnEntry

Initialize state. Read config, set defaults, prepare any internal state needed for the node run.

Stage 2

OnEntryValidate

Load and validate the config bag. Verify required fields exist. Fail fast with a clear error if something is missing.

Stage 3

OnPreValidationGuardRails

Pre-execution safety checks. Rate limiting, security gates, permission checks. Can short-circuit execution.

Stage 4

OnPreProcess

Optional pre-processing. Map inputs, transform data, acquire resources. Returns a result to short-circuit if needed.

Stage 5

OnProcess

The core business logic. API call, LLM prompt, database query, or transform. Returns the result.

Stage 6

OnPostProcess

Post-processing. Transform output data, publish events, update state.

Stage 7

OnPostValidationGuardRails

Post-execution safety checks. Validate the result, apply output policies, run compliance checks.

Stage 8

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.

eNodeStageWhen fired
InitiatedStart of OnExecute, before OnEntry
EntryAfter OnEntry
EntryValidateAfter OnEntryValidate
PreValidationGuardRailsAfter OnPreValidationGuardRails
PreProcessAfter OnPreProcess
ProcessAfter OnProcess
PostProcessAfter OnPostProcess
PostValidationGuardRailsAfter OnPostValidationGuardRails
ExitAfter OnExit
CompletedIf result.IsSuccess == true
ErrorIf 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 });
    }
}
Config is already resolved when OnEntryValidate runs By the time any stage fires, the Element layer has already resolved the 3-layer config merge, evaluated Tier 1 and Tier 2 expressions, and validated the element. Your executor always starts with clean, usable data.

The NodeExecutionContext

The executor receives a NodeExecutionContext which carries:

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.