Flow Studio
nodeOutputs Map
The nodeOutputs dictionary is the primary data bus between nodes. After each node executes, BaseNodeExecutor writes the node's output into this map under the node's ID. Downstream nodes read from it using GetNodeOutput<T>(nodeId).
How Output Gets Written
Executors never call SetNodeOutput directly. They return a NodeExecutionResult, and BaseNodeExecutor writes the output to memory after the executor completes.
// BaseNodeExecutor.cs (simplified write path)
var result = await ExecuteAsync(ctx, ct);
if (result.IsSuccess)
{
// Write output to the shared nodeOutputs map
ctx.ExecutionMemory.SetNodeOutput(ctx.NodeId, result.Output);
// Also persist pinnedData if non-null (separate concern — see Guide30)
if (result.PinnedData is not null)
await _pinnedDataService.SaveAsync(ctx.ProcessId, ctx.NodeId, result.PinnedData, ct);
}
Key and Value Semantics
| Property | Detail |
|---|---|
| Key | The nodeId string — e.g. "node-abc". Set by the workflow designer in Flow Studio. |
| Value type | object? — any JSON-serializable value. Typically an anonymous object, POCO, primitive, or null. |
| Overwrite | Last write wins. If a node runs twice (e.g., in a retry loop), each execution overwrites the previous output. |
| Missing key | GetNodeOutput returns null if the nodeId is not in the map — safe to handle with null-check. |
Type Safety
// Option 1 — untyped (use when type is unknown)
var raw = ctx.ExecutionMemory.GetNodeOutput("data-transform-node");
// Option 2 — typed cast (most common)
var result = ctx.ExecutionMemory.GetNodeOutput<TransformResult>("data-transform-node");
// Returns null if key missing OR if the stored value is not castable to TransformResult
// Option 3 — primitive types use the untyped overload
var count = (int?)ctx.ExecutionMemory.GetNodeOutput("counter-node") ?? 0;
What Can Be Stored
| Value Type | Supported? | Notes |
|---|---|---|
| Anonymous object | Yes | Common for short-lived inter-node data |
| POCO class | Yes | Prefer for typed downstream reads |
string, int, bool, decimal | Yes | Use untyped GetNodeOutput and cast |
List<T> or T[] | Yes | Arrays and lists are fully supported |
null | Yes | Written to map as null — GetNodeOutput will return null |
| Non-serializable (streams, sockets) | No | Will fail during HIL serialization (see Guide31/06) |
Naming Convention for nodeId
nodeIds are set by the workflow designer in Flow Studio when placing a node on the canvas. By default they are machine-generated (e.g., node-1, node-2), but designers should rename nodes to meaningful IDs when cross-node data access is needed.
// Good — executor reads from a meaningfully named node
var customerData = ctx.ExecutionMemory.GetNodeOutput<Customer>("fetch-customer");
// Fragile — if the nodeId changes in the workflow, this breaks
var customerData = ctx.ExecutionMemory.GetNodeOutput<Customer>("node-3");
Document your nodeId dependencies. If your executor reads from a specific upstream node by ID, document this in the executor's XML summary or README so workflow designers know which node must be named that way. Breaking the nodeId contract is a silent runtime error — the executor gets null with no error.