Skip to content

Parallel Fork/Join Example

This worked example triages a CVE by running two independent steps in parallel — assessing impact and notifying the security team — then joining to produce a single report. It exercises a fork, an AND-join, and branch-aware simulation.

The workflow

fetch-cve fans out to analyze-impact and notify-team (both edges are unconditional, so they run in parallel). Both branches converge on create-report, which waits for both before it runs.

{
  "id": "cve-analyze-and-notify",
  "name": "CVE Analyze and Notify",
  "description": "Assess impact and notify the security team in parallel, then report.",
  "version": 1,
  "nodes": [
    {
      "id": "start", "type": "start", "name": "Start",
      "config": { "inputs": [{ "name": "cveId", "type": "string", "required": true }] },
      "position": { "x": 40, "y": 200 }
    },
    {
      "id": "fetch-cve", "type": "action", "name": "Fetch CVE Details",
      "config": {
        "actionType": "fetch-cve",
        "inputs": { "CVE ID": "context.cveId" },
        "outputs": [{ "name": "cve", "type": "object", "required": true }]
      },
      "position": { "x": 260, "y": 200 }
    },
    {
      "id": "analyze-impact", "type": "action", "name": "Analyze Impact",
      "config": {
        "actionType": "analyze-impact",
        "inputs": { "CVE": "context.cve" },
        "outputs": [{ "name": "severity", "type": "string", "required": true }]
      },
      "position": { "x": 500, "y": 100 }
    },
    {
      "id": "notify-team", "type": "action", "name": "Notify Security Team",
      "config": {
        "actionType": "send-notification",
        "inputs": { "CVE ID": "context.cveId" },
        "outputs": [{ "name": "notifiedAt", "type": "string", "required": true }]
      },
      "position": { "x": 500, "y": 300 }
    },
    {
      "id": "create-report", "type": "action", "name": "Create Report",
      "config": {
        "actionType": "create-report",
        "inputs": { "CVE ID": "context.cveId", "Severity": "context.severity" },
        "outputs": [{ "name": "reportUrl", "type": "string", "required": true }]
      },
      "position": { "x": 760, "y": 200 }
    },
    {
      "id": "end", "type": "end", "name": "Published",
      "config": { "outcome": "published" },
      "position": { "x": 1000, "y": 200 }
    }
  ],
  "edges": [
    { "id": "e1", "source": "start", "target": "fetch-cve", "priority": 0, "isDefault": false },
    { "id": "e2", "source": "fetch-cve", "target": "analyze-impact", "priority": 0, "isDefault": false },
    { "id": "e3", "source": "fetch-cve", "target": "notify-team", "priority": 1, "isDefault": false },
    { "id": "e4", "source": "analyze-impact", "target": "create-report", "priority": 0, "isDefault": false },
    { "id": "e5", "source": "notify-team", "target": "create-report", "priority": 0, "isDefault": false },
    { "id": "e6", "source": "create-report", "target": "end", "priority": 0, "isDefault": false }
  ]
}

Why this is a valid fork/join

  • fetch-cve is a fork: it has two outgoing edges (e2, e3), both unconditional (isDefault: false, no condition). Entering fetch-cve activates both branches.
  • create-report is the join: it is the first node reachable from both branches and has two incoming edges (e4, e5). As an AND-join it runs only after both analyze-impact and notify-team complete.
  • The region is balanced: neither branch reaches end before create-report, so it validates with no FORK_WITHOUT_JOIN or PARALLEL_BRANCH_REACHES_END problems.

Running it

Starting the workflow activates both branches after fetch-cve. The instance's activeBranches holds one entry per running branch; currentNodeId is null while both run. create-report fires once both arrive; its two incoming edge ids accumulate in joinArrivals until the second lands. See Engine Usage for the resume-by-node API.

Visualizing and simulating it

The Workflow Viewer highlights both active nodes at once and animates both branch arrivals. In the Visual Editor, fetch-cve carries a Fork badge and create-report a Join badge, and simulation lets you resume each blocked branch one at a time.