Node Data Model
How a node's data is structured, where it comes from, how a node changes it, and where it goes next — traced directly against the current ProcessEngine.Service source, not against documentation that may have drifted from it. Read this once and you should never need to guess about InputData, OutputData, OutputBranches, or the lifecycle events again.
The Five Pieces
Input Data
A flat bag of every variable visible to this node — not just "the previous node's output."
Output Data
What the node itself produces, seeded from Input Data before the executor even runs.
Node Execution Result
The envelope around Output Data — port key, success flag, timing, and error info.
Output Branches
A branch-aware copy of Output Data, built twice, purely for expression resolution.
Execution Memory & Scopes
The scope stack that turns one node's Output Data into the next node's Input Data.
Node Lifecycle Stages
The 10 stages every node passes through, and exactly what happens at each one.
Metadata & Events
What each lifecycle stage actually publishes today — and what it should.
Pinned Data
A real case study: how a subtle unwrap bug here caused a self-nesting JSON cycle.
The Shape, at a Glance
NodeExecutionResult
├── OutputData Dictionary<string, object> ← the executor's own output
├── OutputBranches INodeResultBranches ← SIBLING, not nested inside OutputData
│ ├── Success[] → INodeDataItem { Data } (Data is a COPY of OutputData's fields)
│ ├── Error[]
│ └── Conditional{}
├── OutputPortKey string e.g. "main", "error"
├── IsSuccess / IsSkipped / IsRetry
├── DurationMs
└── ErrorMessage / Exception
OutputData and OutputBranches are two separate properties on the same object — never one nested inside the other. Whenever you see a shape that looks self-nested (an OutputData field containing another OutputBranches), that's a sign something serialized a whole NodeExecutionResult and mistakenly treated it as one flat data record. See Pinned Data for the real bug this caused.
Read in This Order
Input Data → Output Data
Start with how a node receives data and how its own output gets seeded before its logic even runs.
Node Execution Result → Output Branches
Then see how that Output Data gets wrapped for the rest of the engine to consume.
Execution Memory → Lifecycle Stages
See how one node's result becomes the next node's input, and the exact stages a node passes through to get there.
Metadata & Events → Pinned Data
Finish with what actually gets published to the UI/logs today, and a real bug that ties every piece above together.