Flow Studio
Server-Side Nodes
Long-running daemon nodes — implementing IServerNode and IHostedService to maintain persistent state, loaded models, and open connections across all workflow calls.
Execution Model
A server-side node is a singleton IHostedService that registers itself with the ServerNodeRegistry during StartAsync. It remains in memory for the lifetime of the process. When a workflow reaches a ServerNodeCallNode, the engine resolves the target server node from the registry and dispatches a request to it — no DI scope is opened and no instance is created.
Application Startup
├── IHostedService.StartAsync()
│ ├── Load model / initialise connection pool
│ └── ServerNodeRegistry.Register(nodeId, this)
│
│ [always-on, waiting for requests]
│
Workflow Execution (per call)
├── ServerNodeCallExecutor.ExecuteAsync(ctx)
│ ├── ServerNodeRegistry.Resolve(nodeId)
│ └── await serverNode.HandleRequestAsync(request, ct)
│
└── Returns NodeExecutionResult to engine
Key Characteristics
| Characteristic | Detail |
|---|---|
| DI lifetime | Singleton — one instance for the process lifetime |
| State | Shared across all workflow executions that call this node |
| Concurrency | Must be internally thread-safe — multiple workflow executions may call concurrently |
| Startup cost | Paid once at process start — amortised over all subsequent calls |
| Testing | Requires integration tests — startup lifecycle must be tested separately from per-call tests |
| Failure isolation | A server node crash can affect the host process; implement defensive error handling |
Why Server Nodes Exist
Some AI and infrastructure capabilities have startup costs that make per-request instantiation impractical:
- AI inference: Loading a local LLM or embedding model can take 5–30 seconds and consume several GB of memory. This must happen once at startup, not per workflow call.
- Database CDC consumers: A Change Data Capture listener maintains a persistent connection and cursor position. It cannot be created fresh per call.
- WebSocket servers: A WebSocket connection must remain open across multiple message exchanges. A scoped executor cannot hold an open socket.
- Connection pools: Some specialised drivers do not support
IHttpClientFactory-style pooling. A server node manages the pool and hands out connections on each request.
Concurrency is your responsibility: Because a server node is singleton and shared across concurrent workflow calls, you must use thread-safe data structures (
ConcurrentDictionary, SemaphoreSlim, Channel<T>) to manage shared state. Unprotected shared mutable state will produce subtle, hard-to-reproduce bugs under load.