Documentation

How Routing Works

Every NodeExecutionResult carries an OutputPortKey — a string that names the logical output the node is emitting from. Common port keys are "success" and "error", but nodes can define any ports they need.

The IExecutionRouter.GetDownstreamNodesForOutputPort method takes the current element definition, the port key, and the thread definition, then returns the list of downstream ProcessElementDefinition objects connected to that port. These are pushed onto the execution stack.

Routing Decision Flow

flowchart TD RESULT["NodeExecutionResult\nOutputPortKey = 'success' / 'error' / 'waiting' / ..."] CHECK1{"Loop break or\ncontinue signal?"} LOOP["Loop orchestrator takes over\nIteration logic runs"] CHECK2{"Abort signal\nset?"} ABORT["Thread transitions to Cancelled\nExecution stops"] CHECK3{"Is port 'waiting'\nor 'pending'?"} SUSPEND["Trigger durable suspension\nNo downstream routing"] ROUTER["IExecutionRouter.GetDownstreamNodesForOutputPort\nLook up edges from this port in thread definition"] CHECK4{"Downstream\nnodes found?"} PUSH["Push nodes onto execution stack\nor active parallel lane"] TERM["No nodes pushed\nStack will drain\nThread completes normally"] RESULT-->CHECK1 CHECK1-- Yes -->LOOP CHECK1-- No -->CHECK2 CHECK2-- Yes -->ABORT CHECK2-- No -->CHECK3 CHECK3-- Yes -->SUSPEND CHECK3-- No -->ROUTER-->CHECK4 CHECK4-- Yes -->PUSH CHECK4-- No -->TERM style RESULT fill:#1e2640,stroke:#6c8cff,color:#e2e8f0 style ABORT fill:#2d1616,stroke:#f87171,color:#e2e8f0 style SUSPEND fill:#2d2616,stroke:#fbbf24,color:#e2e8f0 style PUSH fill:#162d22,stroke:#34d399,color:#e2e8f0 style TERM fill:#162d22,stroke:#34d399,color:#e2e8f0

Standard Port Keys

Port KeyMeaningSpecial behaviour
"success"Normal completionStandard downstream routing
"error"Node reported failureRoutes to error-handler nodes if connected; otherwise thread may fail
"true" / "false"Boolean condition resultUsed by IF/condition nodes
"waiting"Node suspended for human inputTriggers durable suspension — no downstream routing
"pending"Node suspended for form/eventTriggers durable suspension — no downstream routing

Special Control Flow Signals

Some routing decisions bypass the port key entirely and use flags set directly on ExecutionMemory:

Loop Control

When a loop-break or loop-continue node executes, it sets Memory.LoopBreakSignal or Memory.LoopContinueSignal. The output routing step checks these flags before consulting the port key. When set, normal downstream routing is skipped entirely and the loop orchestrator handles the iteration logic.

Abort Signal

A WorkflowControl node can call Memory.SignalAbort(). The thread loop checks Memory.AbortSignal after each node. When set, the loop exits and the thread transitions to Cancelled.

Parallel Routing

When Memory.IsParallelExecutionActive is true, routing works differently: instead of pushing downstream nodes onto the main stack, they are pushed onto the active parallel lane via _parallelExecutionManager.PushNodeToActiveLane.

A parallel join node waits until all lanes have completed, then merges outputs and resumes normal sequential routing.

What Happens with No Connections

If a node's output port has no downstream connections, GetDownstreamNodesForOutputPort returns an empty list. Nothing is pushed onto the stack. The execution loop continues and eventually exhausts the stack, ending the thread normally with Completed.

This is the normal way a terminal node (like "Send Email" at the end of a flow) ends execution — not by throwing or returning a special state, but simply by having no outgoing edges.

Port keys are strings, not enums Node executors can define any string as a port key. The engine treats them as opaque labels. The Studio UI reads port definitions from the node's type schema to draw the connection handles. A port key in code must exactly match the port name defined in the node's schema for the edge to be created in the editor.