Lifecycle
Node Lifecycle Stages
Every node passes through the same 10 stages, in the same order, driven by BaseNodeExecutor.Orchestrate.cs. This is the backbone that everything else in this guide hangs off of.
The Stage Enum
// eNodeStage.cs
Unknown = 0
Initiated = 10
Entry = 100
EntryValidate = 200
PreValidationGuardRails = 250
PreProcess = 300
Process = 400
DoWork = 410 // internal sub-stage, only during Process (e.g. agent tool calls)
PostProcess = 500
PostValidationGuardRails = 550
Exit = 600
Error = 700
HIL = 800 // suspension-related, not part of the linear sequence
Suspended = 810
Completed = 10000
The Call Chain
Execute() // BaseNodeExecutor.Orchestrate.cs:15
├── resets NodeResult, OutputData = {}
├── OnExecute() // line 39, returns the stage sequence below
│ ├── report: Initiated // "Starting" — carries scrubbed InputData today
│ ├── OnEntry() // → InitializeOutputDataFromInput() runs HERE
│ ├── report: Entry
│ ├── OnEntryValidate()
│ ├── report: EntryValidate
│ ├── OnPreValidationGuardRails()
│ ├── report: PreValidationGuardRails (a "blocked" result short-circuits here)
│ ├── OnPreProcess()
│ ├── report: PreProcess (a non-null result short-circuits here)
│ ├── OnProcess() → OnProcessExecution() → **executor's ExecuteInternalAsync()**
│ ├── report: Process
│ ├── OnPostProcess()
│ ├── report: PostProcess
│ ├── OnPostValidationGuardRails() (a violation REPLACES the result here)
│ ├── report: PostValidationGuardRails
│ ├── OnExit()
│ └── report: Exit
├── OnSuccess() or OnError() // based on result.IsSuccess
└── report: Completed or Error // carries a safe scalar summary of the result
| Stage | What actually happens there |
|---|---|
Initiated | First report fired. Currently the only stage whose metadata includes inputData (scrubbed). See Metadata & Events. |
Entry | OnEntry() runs before this report fires — this is where InitializeOutputDataFromInput seeds OutputData (see Output Data), and where InputSnapshot gets scrubbed and stored on the runtime info. |
EntryValidate | Node-specific input validation hook. |
PreValidationGuardRails | Guard rail checks before the node's main logic. A "blocked" verdict short-circuits the whole node here — OnProcess never runs. |
PreProcess | Last hook before the executor's real logic. A non-null result here also short-circuits (used by some nodes for early-exit conditions). |
Process | Where ExecuteInternalAsync — the node-specific logic every node type implements — actually runs, via OnProcess → OnProcessExecution. |
PostProcess | Post-execution hook, runs regardless of success/failure. |
PostValidationGuardRails | Guard rail checks on the output. A violation here replaces the node's result with a GuardrailsViolation result — this used to be computed and silently discarded; it's now actually applied. |
Exit | Final hook before the result is returned upward. |
Completed / Error | Fired from Execute() itself (not from OnExecute) — the very last thing that happens, after OnSuccess/OnError have run. |
DoWork and HIL are not part of this linear chain
DoWork is emitted from inside a node's own logic during Process (e.g. an AI agent reporting "calling tool X") via ReportExecutionProgressDoWorkAsync, gated by obs.EnableNodeInternalEvents. HIL/Suspended fire when a node's result routes to the waiting/pending port, registering suspension infrastructure rather than continuing the linear sequence.
What This Means for Debugging
If you need to know exactly when OutputData first exists for a node, it's the moment Execute() resets it (before OnExecute even starts) — not at Entry. If you need to know when a node's real logic ran, that's Process, specifically inside OnProcessExecution calling the concrete executor's ExecuteInternalAsync.