Portal Community

NodeExecutionResult — The Return Type

Every node executor returns a NodeExecutionResult from its ExecuteAsync method. This result carries both the routing decision (which port fires) and the data payload. The three static factory methods are:

// Happy path — node succeeded, returns data via main port
NodeExecutionResult.Success(object? output, object? pinnedData = null, string portKey = "main")

// Node failed — routes to error port, exception is captured
NodeExecutionResult.Fail(Exception exception)

// Node intentionally produced nothing — null in memory, no routing
NodeExecutionResult.Skip()

What Gets Stored

When Success(output) is called, the engine serializes the output to JSON and stores it at:

ExecutionMemory.nodeOutputs[nodeId] = output

The output parameter is typed as object? — it can be any JSON-serializable type: a plain class, an anonymous object, a dictionary, an array, or even a scalar string or number.

Common Output Shapes

// Returning a typed DTO
return NodeExecutionResult.Success(new InvoiceResult
{
    InvoiceId = invoice.Id,
    Total = invoice.Total,
    Status = invoice.Status
});

// Returning an anonymous object
return NodeExecutionResult.Success(new
{
    userId = user.Id,
    email = user.Email,
    roles = user.Roles
});

// Returning a list
return NodeExecutionResult.Success(new
{
    items = queryResults,
    rowCount = queryResults.Count
});

// Returning a scalar
return NodeExecutionResult.Success(new { value = 42 });

Pinned Data vs. Live Output

NodeExecutionResult.Success accepts an optional pinnedData parameter. Pinned data is a snapshot stored alongside the live output and displayed in the Node Inspector during execution replay. It does not affect expression evaluation — only output is accessible via $output.{nodeId}.

return NodeExecutionResult.Success(
    output: new { orderId = order.Id, status = "created" },
    pinnedData: new { rawResponse = httpResponse.Body, elapsedMs = sw.ElapsedMilliseconds }
);
Pinned data use case: Store debugging context (HTTP response body, timing, intermediate calculation steps) in pinned data so engineers can inspect it without exposing it to downstream nodes.

Custom Port Key

By default, Success() fires the "main" port. To route to a custom port, pass the portKey parameter:

// Route to "approved" port
return NodeExecutionResult.Success(new { decision = "approved" }, portKey: "approved");

// Route to "rejected" port
return NodeExecutionResult.Success(new { decision = "rejected", reason = "score too low" }, portKey: "rejected");

Data Persistence

Node outputs are persisted to the database when the execution checkpoints (at each node boundary). This means output is available even after a server restart or during HIL suspension — the full ExecutionMemory is rehydrated from the database when execution resumes.

Size consideration: Keep node outputs reasonably sized. Very large outputs (e.g., full file contents or thousands of rows) should be stored in blob storage and a reference returned as output. The engine does not enforce a hard limit, but oversized memory degrades performance.