Flow Studio
Reading Upstream Output
Two methods provide access to upstream node output inside an executor: GetNodeOutput<T>(nodeId) for accessing any node by ID, and GetPreviousNodeOutput<T>() as a shortcut for the immediately preceding node in the graph.
GetNodeOutput — Access by nodeId
// Reading typed output from a specific upstream node
public override async Task<NodeExecutionResult> ExecuteAsync(
NodeExecutionContext ctx,
CancellationToken ct)
{
// Read from a node named "parse-json" in the workflow
var parsed = ctx.ExecutionMemory.GetNodeOutput<ParsedDocument>("parse-json");
if (parsed is null)
{
ctx.Observability.Logger.LogWarning("parse-json node output is null — node may not have run");
return NodeExecutionResult.Failure("Required upstream data not available");
}
// Use the parsed document
var summary = await _summarizer.SummarizeAsync(parsed.Content, ct);
return NodeExecutionResult.Success(summary);
}
GetPreviousNodeOutput — Immediate Upstream Shortcut
GetPreviousNodeOutput<T>() returns the output of the node that connects directly into this node's input port. It is equivalent to calling GetNodeOutput<T>(previousNodeId), but the caller does not need to know the upstream node's ID — the engine resolves it from the graph topology.
// The previous node shortcut — most useful for linear pipelines
public override async Task<NodeExecutionResult> ExecuteAsync(
NodeExecutionContext ctx,
CancellationToken ct)
{
// Reads output from whichever node's output port connects into this node's input port
var input = ctx.ExecutionMemory.GetPreviousNodeOutput<string>();
if (input is null)
return NodeExecutionResult.Failure("No input received");
return NodeExecutionResult.Success(input.ToUpperInvariant());
}
GetPreviousNodeOutput — When to Use Each
| Method | Use When | Risk |
|---|---|---|
GetNodeOutput<T>(nodeId) | Node reads from a non-adjacent upstream node, or when node identity matters | Breaks silently if nodeId changes in the workflow |
GetPreviousNodeOutput<T>() | Node always reads from its immediate upstream predecessor (linear pipeline) | Ambiguous if node has multiple incoming edges — engine picks the primary port |
Multiple Inputs — Fan-In Pattern
When a node has multiple incoming edges (fan-in), use named GetNodeOutput calls to read each upstream node independently. GetPreviousNodeOutput only reads one — the one connected to the primary input port.
// MergeNode.cs — reads from two named upstream nodes
public override async Task<NodeExecutionResult> ExecuteAsync(
NodeExecutionContext ctx,
CancellationToken ct)
{
var leftResult = ctx.ExecutionMemory.GetNodeOutput<DataSet>("left-branch-node");
var rightResult = ctx.ExecutionMemory.GetNodeOutput<DataSet>("right-branch-node");
var merged = Merge(leftResult, rightResult);
return NodeExecutionResult.Success(merged);
}
Null Safety Patterns
// Pattern 1 — fail fast on missing required input
var input = ctx.ExecutionMemory.GetNodeOutput<Order>("order-fetch");
if (input is null) return NodeExecutionResult.Failure("order-fetch output missing");
// Pattern 2 — fallback to default if input is optional
var config = ctx.ExecutionMemory.GetNodeOutput<ProcessConfig>("config-node")
?? ProcessConfig.Default;
// Pattern 3 — check and log
var data = ctx.ExecutionMemory.GetNodeOutput<ApiResult>("api-call-node");
if (data is null)
{
ctx.Observability.Logger.LogWarning("api-call-node output missing — using empty result");
data = ApiResult.Empty;
}
GetNodeOutput always returns null for nodes that have not yet run. The execution engine runs nodes in topological order. If you call
GetNodeOutput("node-b") from within node-a, and node-b is downstream of node-a, the result is always null — node-b cannot have run before node-a in a standard graph without cycles.