Piece 3 of 5
Node Execution Result
The envelope every node execution returns — OutputData plus everything the engine needs to route, log, and report on that execution.
The Full Shape
// NodeExecutionResult.cs:82-108
public class NodeExecutionResult
{
public string OutputPortKey { get; set; } = "main";
public OutputPortUsageInfo? OutputPort;
public bool IsSuccess { get; set; } = true;
public bool IsSkipped { get; set; } = false;
public bool IsRetry { get; set; } = false;
public int DurationMs { get; set; } = 0;
public string? ErrorMessage { get; set; }
public Exception? Exception { get; set; }
public INodeResultBranches? OutputBranches { get; set; }
public Dictionary<string, object> OutputData { get; set; } = new();
public byte[]? OutputBinary { get; set; }
}
| Field | Purpose |
|---|---|
OutputPortKey | Which port the node exited from — "main", "error", a conditional branch name, or "waiting"/"pending" for HIL suspension. |
IsSuccess | Drives whether OnSuccess or OnError fires, and whether the node's state finalizes as Completed or Failed. |
IsSkipped / IsRetry | Flags for skip-logic and retry bookkeeping. |
DurationMs | Populated from the node's own perf timer, used in the Completed/Error stage report. |
ErrorMessage / Exception | Populated on failure — never forwarded raw into published events (see Metadata & Events for why). |
OutputBranches | A sibling wrapper around OutputData — see Output Branches. |
OutputData | The node's own output — see Output Data. |
OutputBinary | Raw binary pass-through (file bytes, images) alongside OutputData. |
OutputData and OutputBranches are siblings, not nested
This is the single most important structural fact in this whole guide. They are two independent properties on the same object. A JSON path that looks like
OutputData.OutputBranches... is not this class's real shape — it's a sign some other code serialized the whole NodeExecutionResult and mistakenly treated it as flat record data. See Pinned Data for exactly this happening.
Where It Gets Created and Reset
A fresh NodeExecutionResult is created at the very start of every single node execution — including every loop iteration — so nothing carries over accidentally between runs of the same node:
// BaseNodeExecutor.Orchestrate.cs:19-24 (Execute method)
executionContext.NodeResult = new NodeExecutionResult
{
OutputData = new Dictionary<string, object?>()
};
How the Result Flows Back Out
After ExecuteInternalAsync returns, the result propagates up through OnExecute → Execute → ProcessElementExecutor.ExecuteAsync → ProcessElementExecutionSequencer.ExecuteElementAsync, where its OutputData fields get written into scope (see Execution Memory & Scopes) and its state gets persisted (see Metadata & Events).