Part 1 — Runtime Components
Table of Contents
Architecture
The system uses a two-layer gateway architecture where the client never communicates directly with the workflow engine. All requests pass through FormManager, which either handles them directly or proxies them to WorkflowManager.
┌──────────┐ ┌──────────────┐ ┌───────────────────┐
│ Client │ ───▸ │ FormManager │ ───▸ │ WorkflowManager │
│ (UI) │ │ (FM Gateway) │ │ (WFM Engine) │
└──────────┘ └──────────────┘ └───────────────────┘
This separation ensures that the UI layer is decoupled from the core workflow engine. FM acts as a consistent entry point, handling concerns like correlation headers, request timeouts, and client-specific response wrapping before forwarding workflow operations to WFM.
Service Roles
FormManager (FM)
FormManager is the gateway and proxy layer that sits between the Studio UI and the WorkflowManager. It has two distinct responsibilities:
Direct endpoints: FM owns user management (CRUD operations on workflow users), model management (upsert/fetch workflow definitions), health checks, UI settings delivery, and cache eviction. These endpoints are handled entirely by FM without contacting WFM.
Proxy endpoints: For all workflow-related operations (context loading, task actions, file operations, notifications, queries, etc.), FM acts as a transparent proxy. It forwards the client request to WFM, injects correlation headers for distributed tracing, enforces request timeouts, and wraps the response in the standard FM response envelope before returning it to the client.
WorkflowManager (WFM)
WorkflowManager is the core workflow engine service. It is responsible for:
- Process lifecycle: Creating, executing, and completing workflow process instances.
- Task management: Assigning tasks to users/swimlanes, locking/unlocking tasks, completing tasks and advancing the process.
- Context state: Maintaining the
dataInstance(form data),processInstance(process metadata, tasks, notes, files), and activity logs for each process. - Flow function execution: Running server-side flow functions (business logic) defined in the workflow model — including task functions, file operations, switch evaluations, and named functions.
- Notifications: Generating and storing notification records when process state changes (created, updated, completed, finished).
- Batch jobs: Processing scheduled and queued batch operations.
Auto-Injected Fields
The FM client automatically enriches every outgoing request before it reaches the server:
| Field | Source | Injected Into | Description |
|---|---|---|---|
organizationId | Current session | Request body | The organization scope for multi-tenant isolation. |
appId | Current session | Request body | The application identifier scoping all data and models. |
user | Selected user in UI | Request header | The userId of the user performing the action. |
These fields do not need to be manually provided by the caller — the FM client handles injection transparently.
Common Request Headers
| Header | Type | Required | Description |
|---|---|---|---|
user | string | Yes (most endpoints) | The userId of the authenticated user performing the action. Auto-injected by the FM client from the currently selected user in the UI. |
lpLogClientId | string | No | A client-generated identifier used for log filtering and distributed tracing. Enables per-session log streaming in the Azure service logs endpoint. |
additionalheaders | string | No | URL-encoded JSON string containing additional context headers. These are forwarded to workflow flow functions during execution, allowing the caller to pass custom metadata into the flow runtime. |
studiotoken | string | Conditional | Authentication token required specifically for the Azure service log streaming endpoint (/azure/serviceLog/:service). |
if-none-match | string | No | Standard HTTP ETag header used by GET /model/getModel/:id for conditional caching. If the value matches the current model's ETag, a 304 Not Modified is returned. |
Response Envelope
All responses received by the FM client are wrapped in a standard envelope:
interface IFormManagerBaseResponse<T> {
status: "success" | "customerror" | "permissionError" | "error";
data: T;
error?: { message: string; title?: string };
}
| Field | Type | Description |
|---|---|---|
status | string | Determines how the client interprets the response. "success" indicates a normal result. "customerror" indicates a domain/business rule violation. "permissionError" indicates an authorization failure. "error" indicates an unexpected server failure. |
data | T | The actual response payload. When status is not "success", this may contain a stack trace string for debugging. |
error | object | Present when status is not "success". Contains a human-readable message and an optional title for display in the UI. |
Error Structure
When the server returns an error, the response body conforms to the ICustomError structure:
interface ICustomError {
statusCode: number;
type: "business" | "technical";
category: "warning" | "error";
message: string;
code: string;
summary?: string;
parameters?: Record<string, string>;
detail?: string;
stack?: string;
}
| Field | Description |
|---|---|
statusCode | HTTP status code (e.g., 400, 404, 500). |
type | "business" means a domain rule was violated (e.g., duplicate user, missing required field). "technical" means an infrastructure or system failure. |
category | "warning" for non-blocking issues, "error" for blocking failures. |
message | Detailed error message describing what went wrong. |
code | Machine-readable error code (e.g., "wm3120", "fm3027"). Prefixed with wm for WFM errors and fm for FM errors. |
summary | Short user-facing title for the error, suitable for UI display. |
parameters | Additional key-value context about the error (e.g., the offending field name). |
detail | Extended error details, often including process/instance identifiers for debugging. |
stack | Stack trace, included only in development/debug environments. |