← Examples

Multi-agent workflow diagram

Visualise how multiple agents, tools, queues, and human review steps interact in an automated workflow. BPMN-style with start/end events, gateways, and intermediate signals.

workflow Agent configuration / orchestration runtime generateupdatevalidate

What this example shows

A BPMN-flavoured workflow that documents how three specialised agents and a human reviewer cooperate to triage incoming orders. The diagram makes the routing logic, the fall-back to humans, the approval gate, and the terminate path explicit - so a reader can audit the system’s behaviour without reading every prompt.

When to use it

When you operate a multi-agent system and need an honest picture of how the agents actually decide. Diagrams like this are useful for design review (does every input have a defined output?), for incident response (where did the order get stuck?), for onboarding new contributors (which agent does what?), and for compliance review (where does a human have to approve?).

What the agent does

A meta-agent reads the orchestration runtime’s configuration - the agent registry, the routing rules, the gateway conditions, the human-in-the-loop hooks - and translates them into a workflow scene. Each agent becomes a workflow.task. Gateways become workflow.gateway diamonds with the routing condition as the label. Human review steps stay visible as separate tasks because they’re behaviourally distinct from agent steps.

The same agent can re-run on every change to the orchestration config and apply incremental updates: a new agent appears as a new task; a routing rule change appears as a relabelled gateway edge; deprecated agents disappear with a deleteElement operation.

What the output includes

  • A start event (workflow.messageStart), end event (workflow.messageEnd), and an explicit terminate path (workflow.terminate) for rejected orders.
  • Three agent tasks (Classifier, Fast-path, Complex-case) plus one human task - the diagram makes the human-in-the-loop step visible by design.
  • Two gateway diamonds: one for routing simple vs complex orders, one for the human approval gate.
  • A workflow.subprocess for “Execute order” - the actual fulfilment is delegated to a different diagram, kept out of this view.
  • BPMN-correct edge styling: solid sequence-flow lines with triangle arrowheads, gateway labels external to the diamond.

Rendered diagram

Multi-agent workflow diagram — rendered diagram

Built by Zindex from the canonical scene below. Open in Playground to swap themes (clean / dark / blueprint / sketch), or POST the scene to /v1/scenes/render with format: "png" for a rasterised version.

Scene JSON

Raw

The canonical DSP scene used to render the diagram above. Drop into the Playground or POST to /v1/scenes/render to reproduce.

{
  "schemaVersion": "0.1",
  "diagramFamily": "workflow",
  "scene": {
    "id": "multi-agent-workflow",
    "title": "Multi-agent Order Triage",
    "units": "px",
    "canvas": {
      "width": 1300,
      "height": 420
    }
  },
  "layoutStrategy": {
    "algorithm": "hierarchical",
    "direction": "LR",
    "nodeSpacing": 35,
    "rankSpacing": 80
  },
  "elements": [
    {
      "id": "msgStart",
      "kind": "node",
      "nodeType": "workflow.messageStart",
      "shape": "ellipse",
      "label": "Order received"
    },
    {
      "id": "classifier",
      "kind": "node",
      "nodeType": "workflow.task",
      "shape": "roundedRect",
      "label": "Classifier agent"
    },
    {
      "id": "decRoute",
      "kind": "node",
      "nodeType": "workflow.gateway",
      "shape": "diamond",
      "label": "Route?"
    },
    {
      "id": "fast",
      "kind": "node",
      "nodeType": "workflow.task",
      "shape": "roundedRect",
      "label": "Fast-path agent"
    },
    {
      "id": "complex",
      "kind": "node",
      "nodeType": "workflow.task",
      "shape": "roundedRect",
      "label": "Complex-case agent"
    },
    {
      "id": "human",
      "kind": "node",
      "nodeType": "workflow.task",
      "shape": "roundedRect",
      "label": "Human reviewer"
    },
    {
      "id": "decReview",
      "kind": "node",
      "nodeType": "workflow.gateway",
      "shape": "diamond",
      "label": "Approved?"
    },
    {
      "id": "exec",
      "kind": "node",
      "nodeType": "workflow.subprocess",
      "shape": "roundedRect",
      "label": "Execute order"
    },
    {
      "id": "notify",
      "kind": "node",
      "nodeType": "workflow.task",
      "shape": "roundedRect",
      "label": "Notify customer"
    },
    {
      "id": "msgEnd",
      "kind": "node",
      "nodeType": "workflow.messageEnd",
      "shape": "ellipse",
      "label": "Order complete"
    },
    {
      "id": "rejected",
      "kind": "node",
      "nodeType": "workflow.terminate",
      "shape": "ellipse",
      "label": "Rejected"
    },
    {
      "id": "e1",
      "kind": "edge",
      "edgeType": "workflow.sequenceFlow",
      "from": {
        "elementId": "msgStart"
      },
      "to": {
        "elementId": "classifier"
      },
      "router": "orthogonal"
    },
    {
      "id": "e2",
      "kind": "edge",
      "edgeType": "workflow.sequenceFlow",
      "from": {
        "elementId": "classifier"
      },
      "to": {
        "elementId": "decRoute"
      },
      "router": "orthogonal"
    },
    {
      "id": "e3",
      "kind": "edge",
      "edgeType": "workflow.sequenceFlow",
      "from": {
        "elementId": "decRoute"
      },
      "to": {
        "elementId": "fast"
      },
      "router": "orthogonal",
      "label": "simple"
    },
    {
      "id": "e4",
      "kind": "edge",
      "edgeType": "workflow.sequenceFlow",
      "from": {
        "elementId": "decRoute"
      },
      "to": {
        "elementId": "complex"
      },
      "router": "orthogonal",
      "label": "complex"
    },
    {
      "id": "e5",
      "kind": "edge",
      "edgeType": "workflow.sequenceFlow",
      "from": {
        "elementId": "complex"
      },
      "to": {
        "elementId": "human"
      },
      "router": "orthogonal",
      "label": "needs review"
    },
    {
      "id": "e6",
      "kind": "edge",
      "edgeType": "workflow.sequenceFlow",
      "from": {
        "elementId": "human"
      },
      "to": {
        "elementId": "decReview"
      },
      "router": "orthogonal"
    },
    {
      "id": "e7",
      "kind": "edge",
      "edgeType": "workflow.sequenceFlow",
      "from": {
        "elementId": "decReview"
      },
      "to": {
        "elementId": "rejected"
      },
      "router": "orthogonal",
      "label": "no"
    },
    {
      "id": "e8",
      "kind": "edge",
      "edgeType": "workflow.sequenceFlow",
      "from": {
        "elementId": "decReview"
      },
      "to": {
        "elementId": "exec"
      },
      "router": "orthogonal",
      "label": "yes"
    },
    {
      "id": "e9",
      "kind": "edge",
      "edgeType": "workflow.sequenceFlow",
      "from": {
        "elementId": "fast"
      },
      "to": {
        "elementId": "exec"
      },
      "router": "orthogonal"
    },
    {
      "id": "e10",
      "kind": "edge",
      "edgeType": "workflow.sequenceFlow",
      "from": {
        "elementId": "exec"
      },
      "to": {
        "elementId": "notify"
      },
      "router": "orthogonal"
    },
    {
      "id": "e11",
      "kind": "edge",
      "edgeType": "workflow.sequenceFlow",
      "from": {
        "elementId": "notify"
      },
      "to": {
        "elementId": "msgEnd"
      },
      "router": "orthogonal"
    }
  ]
}

Agent workflow

Document a multi-agent triage workflow as an executable BPMN-style diagram. The diagram is generated from the agent-orchestration configuration (declarative - typically a YAML file describing classifiers, tools, gateways, and human-in-the-loop checkpoints) and stays in sync as the orchestration evolves.

Inputs

  • Agent orchestration configuration file (e.g. agents/triage.yaml describing the classifier, downstream agents, gateway conditions, and human-review steps)
  • Existing Zindex scene id
  • Zindex API key

Outputs

  • Updated persisted scene with one workflow node per orchestration step (start event, classifier task, gateways, downstream agents, human-reviewer task, terminate events)
  • Rendered BPMN-style SVG suitable for embedding in onboarding docs and runbooks
  • Revision history showing how the orchestration policy has evolved (added a fast-path gateway, raised the auto-approve threshold, added human review for complex cases)
  1. 01

    Fetch the persisted scene

    Workflow scenes are long-lived. Fetch the current revision before deriving anything new.

    dsp_get_scene GET /v1/scenes/${SCENE_ID}
  2. 02

    Parse the orchestration configuration

    Read agents/triage.yaml. Each entry describes one orchestration step: task type (`classifier`, `agent`, `human-review`, `gateway`, `terminate`), inputs, outputs, gateway conditions. The YAML is structured precisely so that one entry maps to one workflow element.

  3. 03

    Compute the operation diff

    For each orchestration step, ensure a node exists with the right `nodeType`: `workflow.messageStart` for the entrypoint, `workflow.task` for tasks, `workflow.gateway` (or one of the gateway* variants) for branching points, `workflow.terminate` for terminal failure paths, `workflow.messageEnd` for the success completion. For each transition, ensure an edge exists with `edgeType: workflow.sequenceFlow` and a label for any condition (e.g. 'simple', 'complex', 'needs review').

  4. 04

    Apply the operation batch

    One applyOps batch with errorPolicy=allOrNothing. The revisionMessage should describe the policy change: 'add fast-path gateway: simple cases skip human review' is meaningful months later, where 'orchestration update' is not.

    dsp_apply_ops POST /v1/scenes/${SCENE_ID}/ops
  5. 05

    Validate the workflow scene

    Confirm BPMN semantics: every gateway has at least two outgoing edges, every messageStart has at least one outgoing edge, every messageEnd has at least one incoming edge. The platform validates these automatically against the workflow diagram family.

    dsp_validate_scene POST /v1/scenes/validate
  6. 06

    Render the workflow diagram

    Render to SVG. The hierarchical LR layout naturally separates the start event, branching gateways, parallel paths, and terminate events - readable left-to-right as a process. Embed the rendered SVG in the agent runbook so on-call engineers can see what the orchestration is supposed to do without reading YAML.

    dsp_render_scene POST /v1/scenes/${SCENE_ID}/render
  7. 07

    Publish the diagram to the agent runbook

    Drop the rendered SVG into the runbook directory. When the agent system pages on-call (because a gateway threshold was crossed or human review queue is backed up), the runbook now includes the canonical orchestration diagram pinned to the deployed revision.

MCP recipe

For agents using Model Context Protocol. The tool sequence below matches the workflow steps; copy the prompt as a system message.

Tool sequence

  1. 01 dsp_get_scene Fetch the persisted scene
  2. 02 dsp_apply_ops Apply the operation batch
  3. 03 dsp_validate_scene Validate the workflow scene
  4. 04 dsp_render_scene Render the workflow diagram

Unique tools used: dsp_get_scene, dsp_apply_ops, dsp_validate_scene, dsp_render_scene.

Copyable agent prompt

Drop this verbatim into a system prompt for an MCP-connected agent. The Zindex MCP server (@zindex-ai/mcp, configured with a ZINDEX_API_KEY environment variable - setup guide) exposes the tools the prompt references.

You are an automated workflow-documentation agent. Your job is to keep a BPMN-style diagram of a multi-agent triage orchestration in sync with the orchestration configuration, so that on-call engineers, designers, and new hires always see what the system is supposed to do - independent of the YAML they would otherwise have to read.

The persisted Zindex scene id is `${SCENE_ID}`; it already exists. Each agent orchestration has one scene; revisions track policy changes ('add fast-path gateway', 'raise auto-approve threshold', 'add human review for complex cases'). Treat the scene as the canonical visual contract for the orchestration.

Workflow on every run (typically on every PR that touches `agents/triage.yaml` or its peers, plus a daily scheduled run as a safety net):

1. Read `agents/triage.yaml` (or whatever orchestration configuration file the project uses). Each entry describes one workflow step: type (`classifier`, `agent`, `human-review`, `gateway`, `terminate`), inputs, outputs, gateway conditions. The YAML is structured intentionally so that one entry maps to one workflow element - do not parse free-text comments; rely on the structured fields.

2. Call `dsp_get_scene({ sceneId: "${SCENE_ID}" })` to read the current revision and elements.

3. For each orchestration step, compute the corresponding workflow element. Map the step type to a `nodeType`: `workflow.messageStart` for the message-driven entrypoint, `workflow.task` for synchronous tasks, `workflow.subprocess` for calls into nested workflows, `workflow.gateway` (or one of the typed gateway variants when applicable) for branching, `workflow.terminate` for terminal failure paths, `workflow.messageEnd` for successful completion. Use the step's stable identifier (the YAML key) as the element id - never re-derive ids; renames must produce `updateNode`, not delete-and-create.

4. For each transition between steps, compute the corresponding edge with `edgeType: workflow.sequenceFlow`. Label the edge with any gateway condition ('simple', 'complex', 'yes', 'no'). Use stable edge ids built from `e_${from_id}_${to_id}` so re-running doesn't churn the graph.

5. Diff what you parsed against the persisted scene. New steps → `createNode`. Removed steps → `deleteElement`. Changed types → `updateNode` (the type might change when the policy decides a former task should become a gateway). Changed conditions → `updateEdge`.

6. Call `dsp_apply_ops` with one batch. `errorPolicy: "allOrNothing"`. The `revisionMessage` should describe the policy change: 'add fast-path gateway: simple cases skip human review' is meaningful months later in the audit trail, where 'orchestration update' is not.

7. Call `dsp_validate_scene`. Resolve workflow-family-specific issues: every gateway needs at least two outgoing edges, every messageStart needs at least one outgoing edge, every messageEnd needs at least one incoming edge. The platform validates these automatically against the `workflow` diagram family - if validation fails, the orchestration YAML is itself malformed and a human should review.

8. Call `dsp_render_scene({ format: "svg", theme: "clean" })`. Embed the rendered SVG in the agent runbook (`docs/runbooks/triage.md`) via a stable URL. When the orchestration pages on-call (a gateway threshold crossed, a human-review queue backed up), the runbook now includes the canonical diagram pinned to the deployed revision - so the responder sees what the system was *supposed* to do, then compares to what actually happened.

Hard rules: never hand-edit the rendered SVG; the rendered file is a throwaway projection of the persisted scene. Never regenerate the scene from scratch; always patch with stable ids - orchestration policy evolves over many small changes, and stable ids are what make the revision history a meaningful policy changelog. If validation fails, surface the structured diagnostic (`code`, `path`, `message`) on the PR; do not silently work around BPMN semantic violations.

HTTP API recipe

For agents/devs not using MCP. Set $ZINDEX_API_KEY in the Authorization header on authenticated calls. Stateless endpoints (/v1/scenes/render, /v1/scenes/validate, /v1/scenes/normalize) need no key. Full reference: API endpoints, OpenAPI spec.

  1. 01 GET /v1/scenes/${SCENE_ID}

    Workflow scenes are long-lived. Fetch the current revision before deriving anything new.

  2. 02 POST /v1/scenes/${SCENE_ID}/ops

    One applyOps batch with errorPolicy=allOrNothing. The revisionMessage should describe the policy change: 'add fast-path gateway: simple cases skip human review' is meaningful months later, where 'orchestration update' is not.

  3. 03 POST /v1/scenes/validate

    Confirm BPMN semantics: every gateway has at least two outgoing edges, every messageStart has at least one outgoing edge, every messageEnd has at least one incoming edge. The platform validates these automatically against the workflow diagram family.

  4. 04 POST /v1/scenes/${SCENE_ID}/render

    Render to SVG. The hierarchical LR layout naturally separates the start event, branching gateways, parallel paths, and terminate events - readable left-to-right as a process. Embed the rendered SVG in the agent runbook so on-call engineers can see what the orchestration is supposed to do without reading YAML.

Operations

Raw

The typed-operation envelope that builds this scene from empty. POST to /v1/scenes/:id/ops after creating a scene, or pass to dsp_apply_ops. Each op carries a stable id so subsequent runs can update the same elements instead of regenerating.

  • 22 operations
  • 11 createEdge, 11 createNode
{
  "schemaVersion": "0.1",
  "errorPolicy": "allOrNothing",
  "revisionMessage": "Initial multi-agent triage workflow: classifier, fast/complex/human paths",
  "ops": [
    {
      "op": "createNode",
      "id": "msgStart",
      "nodeType": "workflow.messageStart",
      "shape": "ellipse",
      "label": "Order received"
    },
    {
      "op": "createNode",
      "id": "classifier",
      "nodeType": "workflow.task",
      "shape": "roundedRect",
      "label": "Classifier agent"
    },
    {
      "op": "createNode",
      "id": "decRoute",
      "nodeType": "workflow.gateway",
      "shape": "diamond",
      "label": "Route?"
    },
    {
      "op": "createNode",
      "id": "fast",
      "nodeType": "workflow.task",
      "shape": "roundedRect",
      "label": "Fast-path agent"
    },
    {
      "op": "createNode",
      "id": "complex",
      "nodeType": "workflow.task",
      "shape": "roundedRect",
      "label": "Complex-case agent"
    },
    {
      "op": "createNode",
      "id": "human",
      "nodeType": "workflow.task",
      "shape": "roundedRect",
      "label": "Human reviewer"
    },
    {
      "op": "createNode",
      "id": "decReview",
      "nodeType": "workflow.gateway",
      "shape": "diamond",
      "label": "Approved?"
    },
    {
      "op": "createNode",
      "id": "exec",
      "nodeType": "workflow.subprocess",
      "shape": "roundedRect",
      "label": "Execute order"
    },
    {
      "op": "createNode",
      "id": "notify",
      "nodeType": "workflow.task",
      "shape": "roundedRect",
      "label": "Notify customer"
    },
    {
      "op": "createNode",
      "id": "msgEnd",
      "nodeType": "workflow.messageEnd",
      "shape": "ellipse",
      "label": "Order complete"
    },
    {
      "op": "createNode",
      "id": "rejected",
      "nodeType": "workflow.terminate",
      "shape": "ellipse",
      "label": "Rejected"
    },
    {
      "op": "createEdge",
      "id": "e1",
      "from": {
        "elementId": "msgStart"
      },
      "to": {
        "elementId": "classifier"
      },
      "edgeType": "workflow.sequenceFlow",
      "router": "orthogonal"
    },
    {
      "op": "createEdge",
      "id": "e2",
      "from": {
        "elementId": "classifier"
      },
      "to": {
        "elementId": "decRoute"
      },
      "edgeType": "workflow.sequenceFlow",
      "router": "orthogonal"
    },
    {
      "op": "createEdge",
      "id": "e3",
      "from": {
        "elementId": "decRoute"
      },
      "to": {
        "elementId": "fast"
      },
      "edgeType": "workflow.sequenceFlow",
      "label": "simple",
      "router": "orthogonal"
    },
    {
      "op": "createEdge",
      "id": "e4",
      "from": {
        "elementId": "decRoute"
      },
      "to": {
        "elementId": "complex"
      },
      "edgeType": "workflow.sequenceFlow",
      "label": "complex",
      "router": "orthogonal"
    },
    {
      "op": "createEdge",
      "id": "e5",
      "from": {
        "elementId": "complex"
      },
      "to": {
        "elementId": "human"
      },
      "edgeType": "workflow.sequenceFlow",
      "label": "needs review",
      "router": "orthogonal"
    },
    {
      "op": "createEdge",
      "id": "e6",
      "from": {
        "elementId": "human"
      },
      "to": {
        "elementId": "decReview"
      },
      "edgeType": "workflow.sequenceFlow",
      "router": "orthogonal"
    },
    {
      "op": "createEdge",
      "id": "e7",
      "from": {
        "elementId": "decReview"
      },
      "to": {
        "elementId": "rejected"
      },
      "edgeType": "workflow.sequenceFlow",
      "label": "no",
      "router": "orthogonal"
    },
    {
      "op": "createEdge",
      "id": "e8",
      "from": {
        "elementId": "decReview"
      },
      "to": {
        "elementId": "exec"
      },
      "edgeType": "workflow.sequenceFlow",
      "label": "yes",
      "router": "orthogonal"
    },
    {
      "op": "createEdge",
      "id": "e9",
      "from": {
        "elementId": "fast"
      },
      "to": {
        "elementId": "exec"
      },
      "edgeType": "workflow.sequenceFlow",
      "router": "orthogonal"
    },
    {
      "op": "createEdge",
      "id": "e10",
      "from": {
        "elementId": "exec"
      },
      "to": {
        "elementId": "notify"
      },
      "edgeType": "workflow.sequenceFlow",
      "router": "orthogonal"
    },
    {
      "op": "createEdge",
      "id": "e11",
      "from": {
        "elementId": "notify"
      },
      "to": {
        "elementId": "msgEnd"
      },
      "edgeType": "workflow.sequenceFlow",
      "router": "orthogonal"
    }
  ]
}

Validation

Valid

Captured response from POST /v1/scenes/validate. The platform runs 40+ semantic checks; see the full list in the validation rules reference.

  • 0 diagnostics

Scene validates with no diagnostics. Agents that produce scenes like this can ship the rendered SVG without a recovery loop.

Codes the platform can emit include TEXT_OVERFLOW, CANVAS_AUTO_EXTENDED, EDGE_LABEL_SUPPRESSED_REDUNDANT, EDGE_LABEL_SUPPRESSED_FANIN, EDGE_COLUMN_NOT_FOUND, LABEL_DUPLICATION_DETECTED, LAYOUT_ABSOLUTE_AT_ORIGIN, and MISSING_DIAGRAM_FAMILY. Each diagnostic carries a structured data field with element ids and context an agent can act on programmatically.

Revision diff

Raw

The structural diff between two revisions of the persisted scene — the response shape dsp_diff_scene returns. Stateful diagram evolution is Zindex's strongest differentiator: the same scene id evolves through immutable revisions, each diffable.

Evolution scenario

A policy change: complex cases that previously routed straight to human review now go through an auto_escalate task that batches similar cases together before paging a human. The classifier's routing decision (`decRoute`) gained a third branch. The revision message reads 'add auto-escalate path: complex → batch → human review'.

  • Revision 2324
  • +2 added
  • -0 removed
  • ~1 modified

+ Added

  • auto_escalate
  • e_complex_escalate

~ Modified

  • decRoute

Raw dsp_diff_scene response

{
  "schemaVersion": "1.0",
  "sceneId": "triage-orchestration",
  "fromRevision": 23,
  "toRevision": 24,
  "summary": {
    "added": 2,
    "removed": 0,
    "modified": 1
  },
  "added": [
    "auto_escalate",
    "e_complex_escalate"
  ],
  "removed": [],
  "modified": [
    "decRoute"
  ],
  "scenario": "A policy change: complex cases that previously routed straight to human review now go through an auto_escalate task that batches similar cases together before paging a human. The classifier's routing decision (`decRoute`) gained a third branch. The revision message reads 'add auto-escalate path: complex → batch → human review'."
}

Want to see this on your own scene? Run the CI recipe below — it calls dsp_diff_scene on every revision change and surfaces a real before / after on every PR.

CI/CD recipe

Pull request + scheduled Raw YAML

A complete, runnable GitHub Actions workflow for this example. Drop the YAML into .github/workflows/zindex-multi-agent-workflow.yml, add the listed secrets, and the agent runs unattended on every qualifying trigger. Re-comments idempotently using a hidden marker so the PR conversation stays clean across pushes.

Trigger

Runs on every PR that touches the orchestration YAML, plus a daily safety-net run at 06:00 UTC. The PR trigger surfaces orchestration policy changes during review; the daily run catches any drift in case a YAML edit landed without its CI run completing.

  • Schedule: 0 6 * * *
  • Path filters: agents/triage.yaml, agents/**/*.yaml

Required secrets

  • ZINDEX_API_KEY required Zindex API key with scene-write scope.
  • ZINDEX_SCENE_ID required Long-lived persisted scene id for the orchestration diagram. One scene per orchestration; never recreate.

Inputs

  • agents/triage.yaml - the orchestration configuration
  • scripts/parse-orchestration.mjs (you author this) - emits { steps: [{ id, type, label, transitions }] }
  • scripts/orchestration-to-ops.mjs (you author this) - maps step types to workflow.* nodeTypes; emits typed ops with stable ids

Outputs

  • out/diagram.svg - rendered BPMN-style workflow
  • out/diff.json - structural diff vs previous revision
  • out/validation.json - diagnostic capture (build fails if BPMN semantics are broken)
  • PR comment with policy changes summary
  • Workflow artifact 'orchestration-diagram-svg' (30-day retention)

GitHub Actions workflow

# Zindex - Multi-agent workflow diagram. Keeps a BPMN-style diagram of the
# agent orchestration policy in sync with the orchestration YAML on every PR
# that touches it (plus a daily safety-net run).
#
# Drop into .github/workflows/zindex-orchestration.yml.

name: Zindex - Multi-agent workflow

on:
  pull_request:
    paths:
      - "agents/triage.yaml"
      - "agents/**/*.yaml"
  schedule:
    - cron: "0 6 * * *"   # Daily 06:00 UTC safety net
  workflow_dispatch:

permissions:
  contents: read
  pull-requests: write

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
  ZINDEX_API_BASE: https://api.zindex.ai

jobs:
  sync-orchestration-diagram:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # 1. Parse the orchestration YAML. Each entry maps to a workflow
      #    element - author scripts/parse-orchestration.mjs against your
      #    project's YAML schema. The parser should emit
      #    { steps: [{ id, type, label, transitions }] } where `type` is one
      #    of classifier|agent|human-review|gateway|terminate.
      - name: Parse orchestration to steps.json
        run: |
          mkdir -p out
          node scripts/parse-orchestration.mjs > out/steps.json
          echo "::notice::Parsed $(jq '.steps | length' out/steps.json) steps"

      - name: Capture current revision
        id: prev_rev
        env:
          ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
          ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
        run: |
          echo "rev=$(curl -fsSL -H "Authorization: Bearer $ZINDEX_API_KEY" \
            "$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID" | jq -r '.revision')" >> "$GITHUB_OUTPUT"

      # 2. Map each step to a Zindex workflow element. Use stable ids (the
      #    YAML key) so a renamed step produces updateNode rather than
      #    delete + create - the latter would break sequenceFlow edges.
      - name: Compute applyOps batch
        run: node scripts/orchestration-to-ops.mjs out/steps.json > out/ops.json

      - name: Apply ops
        id: apply
        env:
          ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
          ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
        run: |
          RESP=$(curl -fsSL -X POST \
            -H "Authorization: Bearer $ZINDEX_API_KEY" \
            -H "Content-Type: application/json" \
            --data-binary @out/ops.json \
            "$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID/ops")
          echo "rev=$(echo "$RESP" | jq -r '.revision')" >> "$GITHUB_OUTPUT"
          echo "applied=$(echo "$RESP" | jq -r '.applied')" >> "$GITHUB_OUTPUT"

      - name: Validate workflow semantics
        env:
          ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
          ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
        run: |
          curl -fsSL -X POST \
            -H "Authorization: Bearer $ZINDEX_API_KEY" \
            -H "Content-Type: application/json" \
            --data-binary @<(curl -fsSL -H "Authorization: Bearer $ZINDEX_API_KEY" \
              "$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID" | jq '{schemaVersion: "0.1", diagramFamily, scene, layoutStrategy, elements}') \
            "$ZINDEX_API_BASE/v1/scenes/validate" \
            > out/validation.json
          if [ "$(jq -r '.ok' out/validation.json)" != "true" ]; then
            echo "::error::Validation failed - workflow YAML may have invalid BPMN semantics. See out/validation.json."
            jq -r '.diagnostics[] | "::error::\(.code) at \(.path // "scene"): \(.message)"' out/validation.json
            exit 1
          fi

      - name: Render
        env:
          ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
          ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
        run: |
          curl -fsSL -X POST \
            -H "Authorization: Bearer $ZINDEX_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{"format":"svg","theme":"clean"}' \
            "$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID/render" \
            | jq -r '.output.content' > out/diagram.svg

      - name: Diff revisions
        id: diff
        if: steps.apply.outputs.rev != steps.prev_rev.outputs.rev
        env:
          ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
          ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
          PREV: ${{ steps.prev_rev.outputs.rev }}
          NEW: ${{ steps.apply.outputs.rev }}
        run: |
          curl -fsSL -H "Authorization: Bearer $ZINDEX_API_KEY" \
            "$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID/diff?from=$PREV&to=$NEW" \
            > out/diff.json
          echo "added=$(jq -r '.summary.added' out/diff.json)" >> "$GITHUB_OUTPUT"
          echo "removed=$(jq -r '.summary.removed' out/diff.json)" >> "$GITHUB_OUTPUT"
          echo "modified=$(jq -r '.summary.modified' out/diff.json)" >> "$GITHUB_OUTPUT"

      - uses: actions/upload-artifact@v4
        id: upload
        with:
          name: orchestration-diagram-svg
          path: out/diagram.svg
          retention-days: 30

      - uses: peter-evans/find-comment@v3
        if: github.event_name == 'pull_request'
        id: find_comment
        with:
          issue-number: ${{ github.event.pull_request.number }}
          comment-author: "github-actions[bot]"
          body-includes: "<!-- zindex-bot:multi-agent-workflow -->"

      - uses: peter-evans/create-or-update-comment@v4
        if: github.event_name == 'pull_request'
        with:
          comment-id: ${{ steps.find_comment.outputs.comment-id }}
          issue-number: ${{ github.event.pull_request.number }}
          edit-mode: replace
          body: |
            <!-- zindex-bot:multi-agent-workflow -->
            ### Orchestration diagram updated · revision ${{ steps.apply.outputs.rev }}

            | | |
            |---|---|
            | Workflow changes | +${{ steps.diff.outputs.added || '0' }} / -${{ steps.diff.outputs.removed || '0' }} / ~${{ steps.diff.outputs.modified || '0' }} |
            | Revision | ${{ steps.prev_rev.outputs.rev }} → ${{ steps.apply.outputs.rev }} |

            <details><summary>Download rendered SVG</summary>

            ${{ steps.upload.outputs.artifact-url }}

            </details>

            <sub>Rendered by Zindex · scene `${{ secrets.ZINDEX_SCENE_ID }}`</sub>

PR comment template

The bot posts this comment on every triggering PR. The hidden marker <!-- zindex-bot:multi-agent-workflow --> lets peter-evans/create-or-update-comment find and overwrite the previous comment instead of appending a new one.

<!-- zindex-bot:multi-agent-workflow -->
### Orchestration diagram updated · revision ${NEW_REVISION}

| | |
|---|---|
| Workflow changes | +${ADDED} / -${REMOVED} / ~${MODIFIED} |
| Revision | ${PREV_REVISION} → ${NEW_REVISION} |

<details><summary>Download rendered SVG</summary>

${ARTIFACT_URL}

</details>

<sub>Rendered by Zindex · scene `${SCENE_ID}`</sub>

Agent resources

Machine-readable versions of this example. Agents should fetch these rather than scrape the rendered HTML.

  • multi-agent-workflow.scene.json Canonical DSP scene Open
  • multi-agent-workflow.ops.json Typed-operation envelope that builds the scene Open
  • multi-agent-workflow.workflow.json Structured agent workflow (goal, inputs, outputs, steps) Open
  • multi-agent-workflow.diff.json Sample dsp_diff_scene response (revision evolution) Open
  • multi-agent-workflow.github-actions.yml Runnable GitHub Actions workflow for the CI/CD recipe Open
  • multi-agent-workflow.svg Build-time rendered diagram Open
  • multi-agent-workflow.md Agent-readable markdown summary Open
  • /examples/index.json Manifest of all examples (cross-linked) Open