Architecture

Flow is two systems working together. Flow Studio is a 7-layer React/TypeScript frontend. Flow Engine is an ASP.NET Core orchestration engine. Here is how they are built.

Flow Studio — 7-Layer Architecture

Each layer can only depend on the layer below it. No upward dependencies. This makes every package independently testable and reusable.

Layer 6
Main Application
apps/studio
Vite entry point. Mounts WorkflowApp. Dev server on port 5173. 896 KB bundle.
Layer 5
React Components
flow-studio-designer
100+ components: WorkflowApp, WorkflowEditor, Canvas, 20+ Node types, Toolbars, Modals, Dashboards. Hooks: useExecutionSignalR, useKeyboardShortcuts. 682 KB.
Layer 4
Core Framework
flow-studio-core
EventBus (pub/sub), Translators (UI ↔ Backend), DataTransformers, Utilities, Mock data. ~50 KB.
Layer 3
Business Logic
@flow-studio/services
WorkflowService, NodeFactoryService, backendToUiMapper, FormBuilderService, LayoutService, ProjectInitializationService. 22 KB.
Layer 2
State Management
@flow-studio/store
6 Zustand stores: workflowStore (nodes, edges, undo/redo, dirty flag), designerModeStore, uiStore, authStore, executionHistoryStore, settingsStore. 49 KB.
Layer 1
API Integration
@flow-studio/api
40 API client classes, baseApiClient, DTOs, 70+ endpoint routes, response mappers. 78 KB.
Layer 0
Type Definitions
@flow-studio/types
Pure TypeScript interfaces — Workflow, Node, Edge, Group, Execution, UI types. Zero dependencies. Foundation for all other packages.

Flow Engine — ProcessEngine Internals

Built on SOLID principles and Domain-Driven Design. The engine runs 60+ NodeExecutor types in a stack-based execution model with full persistence and real-time event streaming.

ProcessEngineService

The public API. Accepts ExecuteProcessRequest and returns immediately with an ExecutionID. Actual execution runs in a background service scope — fire-and-forget. Clients receive progress via SignalR.

  • ExecuteProcessAsync
  • PauseProcessExecutionAsync
  • ResumeProcessExecutionAsync
  • CancelProcessExecutionAsync
  • GetProcessExecutionStatusAsync

OrchestrationProcessor

The core execution engine, split across 8 partial classes for maintainability. Manages the full lifecycle: process → thread → element execution with branching, routing, and event publishing.

  • ExecuteProcess (all threads)
  • ExecuteThread (individual workflows)
  • ExecuteElement (individual nodes)
  • Suspend / Continuation
  • Control (Pause / Resume / Cancel)

Execution Stack (LIFO)

Nodes are pushed onto a stack and popped for execution. After each node, the routing algorithm evaluates connection conditions and pushes the next node(s). Supports parallel branching by pushing multiple nodes.

  • Stack-based LIFO model
  • Conditional routing per connection
  • Parallel branch push
  • Port-based output routing

Four-Level Context Hierarchy

Every execution carries a nested context: AppExecutionContext → ProcessExecutionContext → ProcessThreadExecutionContext → ProcessElementExecutionContext. Each level carries its own data, ID, and state.

  • AppExecutionContext
  • ProcessExecutionContext
  • ProcessThreadExecutionContext
  • ProcessElementExecutionContext

NodeExecutor Pattern

All 60+ executors extend BaseNodeExecutor and follow a 3-part pattern: Validate (preconditions) → ExecuteInternalAsync (main logic) → Finalize (cleanup). Any exception returns a failed NodeExecutionResult with the error port.

  • Validate(context)
  • ExecuteInternalAsync(context)
  • Finalize(context, result)
  • OutputPort routing result

Data Persistence Layer

Every execution, thread, and node result is persisted. Six core tables capture the full audit trail from start to finish.

  • ProcessExecution
  • ProcessThreadExecution
  • ProcessElementExecution
  • ExecutionMemory
  • ApprovalRequest
  • SuspendedExecution

9-State Execution Model

Every workflow and every node moves through a defined set of states. Nothing is ambiguous.

0 — Idle 1 — Queued 2 — Running 3 — Paused 4 — Waiting (Approval / Timer) 5 — Completed 6 — Completed with Warnings 7 — Failed 8 — Cancelled 9 — Timed Out

Execution Memory — 5 Variable Scopes

Variables are scoped to the right level. Node output data is merged into the appropriate scope and passed forward automatically.

Scope Lifetime Use for
GlobalScope Entire workflow execution Data shared across all threads and nodes — e.g. input parameters, final results
ThreadScope One ProcessThread Data local to a workflow thread — isolated from parallel branches
NodeScope One node execution Temporary working data for a single node — cleared after the node finishes
LoopScope One loop iteration Loop counter, current item — reset on each iteration
TryCatchScope One try/catch block Error information, rollback data — available in catch and finally blocks

15+ NodeCapabilities

Capabilities extend what a node can do beyond its base execution logic. Each node declares which capabilities it needs. The engine validates availability before execution.

ActorInLoop
Human approvals, task assignments, voting strategies (All / AnyOne / NofM)
Forms
Form rendering and data capture via Atlas Forms integration
Messaging
Email, SMS, Slack, Teams notifications — pub/sub channels
Identity
User/role validation and auth via Passport
Entity
Database persistence via Data Ocean entities
Datasource
Data queries across registered datasource providers
Rules
Business rule evaluation and condition checking
Webhooks
Inbound, outbound, and callback endpoint management
Services
External REST API service integration
Widgets
UI components — charts, tables, metrics panels
Process
Sub-workflow invocation and nested process management
MCP
Model Context Protocol — standardised AI tool integration
DIDComm
Decentralized identity communication for cross-org workflows
Execution
Parallel and batch execution coordination
Discovery
Capability enumeration and node self-description