Documentation

What Is an Element?

In the database, a ProcessElement is a row that says: "there is a node of type X, connected to connector Y, configured with JSON Z, at position K in thread T."

At runtime, the element is represented by a ProcessElementExecutionContext — a rich object that holds everything needed to execute that specific node instance. The component that executes an element is the ProcessElementExecutor — a scoped service that does pre-execution work then hands off to the node executor.

Element Execution Context

PropertyWhen populatedWhat it holds
ElementDefinitionBefore execution startsThe static definition: type name, timeout, config JSON, connections
ConnectorIDFrom the definitionWhich credential/service binding this node uses
ResolvedConfigAfter Tier 1 config resolutionThe merged 3-layer configuration ready for the executor
InputDataSet by the orchestrator before element dispatchOutput from upstream nodes mapped into this node's input bag
OutputDataAfter executor completesThis node's output, stored on the context for tracing
ExecutorResolved by INodeExecutorFactoryThe concrete executor instance for this node type
SatelliteNodesFrom the definitionAttached helper sub-nodes for this element
ParentThreadContextAt context creationReference to the thread — gives access to ExecutionMemory

The Pinned Data Short-Circuit

Before any config resolution or executor dispatch, the element executor checks whether the element definition has PinnedData attached. When present, the executor skips all resolution, validation, and business logic — it immediately returns the pinned output as if the node had executed normally. This enables workflow debugging without running real integrations.

PinnedData is a Studio feature Only the workflow studio UI sets PinnedData. It should never be present in production deployments. The check is O(1) — if no pinned data exists, execution continues with zero additional cost.

The Pre-Execution Pipeline

After the pinned data check, PrepareExecutionAsync runs three stages in sequence before the executor is dispatched.

flowchart LR START(["Element received from thread"]) CHECK{"PinnedData\nattached?"} PIN(["Return pinned output\nSkip all execution"]) subgraph T1["Tier 1 · Config Resolution"] direction TB M["3-Layer Merge\nExtension → Connector → Element"] AT["AtConfigLoad Expressions\n@{'{'}env:·{'}'} @{'{'}secret:·{'}'}"] M --> AT end subgraph T2["Tier 2 · Input Expressions"] direction TB IR["AtInputReady Expressions\n@{'{'}input:·{'}'} @{'{'}output:·{'}'} @{'{'}var:·{'}'}"] end subgraph V["Validation"] direction TB VL["Executor found?\nRequired fields present?\nConnector data valid?"] end EXEC(["Dispatch to Node Executor"]) START --> CHECK CHECK -- Yes --> PIN CHECK -- No --> T1 --> T2 --> V --> EXEC style T1 fill:#181d33,stroke:#6c8cff style T2 fill:#181d33,stroke:#a78bfa style V fill:#181d33,stroke:#34d399 style PIN fill:#2d1616,stroke:#f87171,color:#e2e8f0 style EXEC fill:#162d22,stroke:#34d399,color:#c8ffd8 style START fill:#162040,stroke:#6c8cff,color:#c8d8ff

Tier 1 — 3-Layer Config Resolution + AtConfigLoad Expressions

The 3-layer configuration merge happens here:

After merging, only fields with ExpressionPolicy.EvaluationStage == AtConfigLoad are sent through the expression engine at this stage. Fields like @{env:DATABASE_URL} or @{secret:ApiKey} do not depend on runtime input data — evaluating them early keeps the executor clean.

Tier 2 — AtInputReady Expressions

By the time PrepareExecutionAsync is called, the thread orchestrator has already populated elementContext.InputData with the mapped outputs from upstream nodes. This makes @{input:...} and @{output:nodeKey.fieldName} directives resolvable.

Why separate from Tier 1? Tier 1 happens before the input bag exists. If you try to resolve @{input:employeeId} at config-load time, the input bag is empty. Tier 2 defers exactly those fields until the input is ready. If no fields have AtInputReady stage, this entire tier is skipped with no overhead.

Validation

The element is validated before dispatch. Checks:

If validation fails, a NodeExecutionResult with IsSuccess = false is returned immediately. The executor is never called.

Executor Dispatch & Timeout

After preparation, the element executor calls executor.ExecuteAsync(nodeExecutionContext, cancellationToken). A timeout is enforced via a linked CancellationTokenSource with the element's configured TimeoutSeconds (default 300s / 5 minutes).

TimeoutBehaviorWhat happens
Error (default)Route to the node's error output port. Downstream error-handling nodes run.
SkipTreat as if the node succeeded. Route to success port. Execution continues.
CancelStop the entire thread immediately. State → Cancelled.
RetryRetry the node once via the retry policy. If that also fails, transition to Failed.
The element layer is invisible to executor developers Node executor implementors never interact with the element layer directly. By the time OnEntry fires in the executor lifecycle, config is resolved, expressions are evaluated, and InputData is populated. The executor always starts with a clean, ready-to-use context.