Skip to content

Workflow Model

A workflow is a directed graph of nodes connected by edges. It defines process steps and the conditions that determine routing.

Workflow Definition

Field Type Description
id String Unique identifier
name String Display name
description String Optional description
version Integer Optional host-managed revision; instances are not automatically pinned to it
nodes List The nodes in the graph
edges List The edges connecting nodes

Workflow definitions are JSON-serializable. The consuming application stores them however it chooses.

See typed configuration and wire contract v1 for the shared schema, per-node TypeScript/Java APIs, host extension fields, defaults, and source-compatibility guidance.

Node Types

Every node has an id, type, name, and config (type-specific configuration). position (visual coordinates) is optional and nullable; the browser can lay out definitions that omit it.

Start

Entry point for the workflow. One per workflow.

  • Config: Defines expected inputs via an inputs array
  • Behavior: Validates the initial context against the input schema, then transitions to the next node
  • Edges: Supports multiple conditional outgoing edges evaluated against the initial context
{
  "inputs": [
    { "name": "cveId", "type": "string", "required": true, "description": "CVE to triage" },
    { "name": "severity", "type": "string", "required": false }
  ]
}

Action

Automated work delegated to a NodeExecutor provided by the host application.

  • Config: Must include an actionType field matching a registered executor
  • Behavior: Invokes the executor synchronously. COMPLETED output is checked/mapped into context; PENDING parks the action for host completion. FAILED enters error recovery.
{
  "actionType": "analyze-cve",
  "param1": "value1"
}

Human Task

Blocks until a human responds. The engine sets the instance to WAITING status.

  • Config: The engine interprets four keys:
    • title (String, optional) — an EL expression computing the task title, e.g. the subject of the item in a task inbox. Join text and values with the EL string concatenation operator += (e.g. "'Review ' += context.cveId"; note that + is numeric addition in EL). When the title is absent or blank, fails to evaluate, or yields null, the node name is used instead.
    • description (String) — instructions for the person completing the task
    • inputs (Map) — expression strings or JSON literals, resolved on entry and info reads (e.g. {"Credit Score": "context.creditScore"})
    • outputs (List of {name, type, required}) — defines the form schema for task completion

The validator emits MISSING_TASK_DESCRIPTION and MISSING_TASK_OUTPUTS warnings when these are absent, and an INVALID_TASK_TITLE_EXPRESSION warning when the title is not valid EL. - Behavior: Completes when the host calls completeNode with the response (completeCurrentNode for one parked branch). The host validates the submitted form values.

Receive Event

Blocks until a matching external event arrives.

  • Config: eventType (required) and match expressions (optional) for event correlation
  • Behavior: The host uses matchesEvent to correlate, then completeNode to deliver a matching event. Optional outputs expressions map selected event values into context.
  • Timeout (optional): timeout is a positive ISO 8601 duration (e.g. PT1H). A node with a timeout must have exactly one outgoing edge with isTimeout: true (like a BPMN boundary timer) plus at least one normal edge. The host schedules a timer from ReceiveEventInfo.timeout() and calls onReceiveEventTimeout when it fires; the engine then follows the timeout edge. See Event Correlation.

See Event Correlation for details.

Wait

Blocks for a configured duration. The engine sets the instance to WAITING status.

  • Config: duration (String) — ISO 8601, e.g. PT30M, PT2H, P1D. The validator emits MISSING_WAIT_DURATION if absent.
  • Behavior: The host reads getWaitInfo, schedules a timer, then calls completeNode when it expires.

End

Terminal state. One or more per workflow.

  • Config: Pass-through — typically carries outcome metadata (e.g. "outcome": "mitigated")
  • Behavior: Sets the instance to COMPLETED status

Edges

Edges connect nodes and control the flow of execution.

Field Type Description
id String Unique identifier
source String Source node ID
target String Target node ID
condition String Jakarta EL expression (optional)
priority int Evaluation order — lower numbers first
isDefault boolean Fallback when no conditions match
label String Display label (optional)
isTimeout boolean Receive-event timeout edge (optional, default false); never chosen by normal routing

Conditional Routing

When a node completes, the engine evaluates its outgoing edges:

  1. Edges are sorted by priority (ascending)
  2. Each edge's condition is evaluated against the workflow context using Jakarta EL
  3. The first edge whose condition returns true is followed
  4. If no condition matches, the default edge is followed
  5. If no edge matches and there is no default, the error handler is invoked

Condition expressions use context as the root variable:

context.result.status == 'affected'
context.score > 80
context.approved && context.reviewCount >= 2

Parallel Fork/Join

A node with two or more outgoing edges that are all unconditional (no condition and isDefault: false) is a fork: completing it activates every outgoing branch, rather than choosing one. This is the parallel counterpart to conditional routing, where exactly one edge is taken.

{ "id": "e-analyze", "source": "fetch-cve", "target": "analyze-impact", "priority": 0, "isDefault": false }
{ "id": "e-notify",  "source": "fetch-cve", "target": "notify-team",    "priority": 1, "isDefault": false }

The branches re-converge at a join — the first node reachable from every branch. A join behaves as an AND-join: it waits for all incoming parallel branches to arrive before it fires, and it fires exactly once. No special node type or config marks a join; it is identified statically from the graph shape.

Fork/join regions must be well-formed:

  • Every fork must have exactly one matching join (otherwise FORK_WITHOUT_JOIN).
  • A parallel branch must not reach an end node before joining (otherwise PARALLEL_BRANCH_REACHES_END).
  • A node's outgoing edges must be all unconditional (fork) or use conditions/a default (exclusive choice) — never a mix (otherwise MIXED_FORK_EDGES).

See Validation for the rules and the worked example for a complete fork/join workflow.

Cycles

Edges can loop back to earlier nodes — this is intentional for retry/re-review patterns:

Human Task (approve plan)
  → [approved] Action (implement)
  → [rejected] Action (revise plan) → Human Task (approve plan)  // loop back

Workflow Instance

A workflow instance is the execution's runtime state, held in a single JSON document. The host handles persistence.

Field Type Description
id String Instance identifier (UUID by default)
workflowId String Reference to the workflow definition
currentNodeId String | null Convenience cursor for one branch; null during parallel waits. Terminal snapshots may retain a final/failing cursor.
activeBranches Array Branch records { branchId, nodeId }. Interpret activity together with status; failed/cancelled snapshots may retain records. A sequential run uses root.
joinArrivals Object For each pending join node, the incoming edge ids that have already arrived and are waiting for the rest.
status Enum running, waiting, completed, failed, cancelled
context Map Initial data plus mapped completed and partial pending outputs
history List Record of visited nodes with timestamps and edge info
failureReason String Why the instance failed (null if not failed)
createdOn Instant When the instance was created
updatedOn Instant When the instance was last modified

At a parallel wait, currentNodeId is null; it is a convenience for the single-branch case, not a substitute for status and branch records. Each HistoryEntry has an optional branchId tagging its visit; a missing history branch ID denotes root. Entries also record, when the node is entered, the input values it received (declared workflow inputs for a start node, resolved inputs for action and human-task nodes) and, for human-task nodes, the computed task title; both are omitted when absent, so viewers can show per-visit values even after later nodes change the context. Java timestamps serialize as ISO strings with the Jackson Java Time module. Old instances missing branch collections normalize to empty collections in Java; that alone does not synthesize parked branches for safe resume. Hosts must migrate legacy runtime state and retain the correct definition. See current contracts.

Status Lifecycle

          ┌─────────┐
          │ RUNNING  │ ← startWorkflow / completeCurrentNode
          └────┬─────┘
               │
    ┌──────────┼──────────┐
    ▼          ▼          ▼
┌────────┐ ┌──────┐ ┌───────────┐
│WAITING │ │ END  │ │  ERROR    │
│        │ │      │ │           │
└───┬────┘ └──┬───┘ └─────┬─────┘
    │         │           │
    │    ┌────▼───┐  ┌────▼───┐
    │    │COMPLETED│  │ FAILED │
    │    └────────┘  └────────┘
    │
    ▼
┌──────────┐
│CANCELLED │ ← cancelWorkflow
└──────────┘