Skip to content

Example 13: Multi-Backend Roles

What It Is

Multi-Backend Roles let one application DAG send different placements to different execution backends. The Cartographer assigns stream processing to cpu and summary generation to io while preserving the same JSON-LD graph.

The role names are deployment labels, not new workflow primitives. The graph stays portable because it asks for cpu and io; the host decides whether those roles mean browser workers, Node worker threads, forked processes, or in-process execution.

How It Works

Each placement declares only a logical role name. The host decides what backend satisfies that role: a browser worker pool, Node worker threads, forked processes, or in-process execution. The JSON-LD graph remains portable because topology references roles, not concrete transports.

Runtime behavior

Run the browser demo from the docs dev server:

bash
pnpm run docs:dev

Then open The Cartographer, click Run, and watch the DAG pane. The graph expands the same registered DAGs shown above:

  • process-stream fans out through stream-event on the cpu role.
  • summarize-insights invokes insights-summary on the io role.
  • The parent DAG stays a JSON-LD graph of placements, routes, and container role names.

Diagrams, Examples, and Outputs

The diagrams are generated from the Cartographer worker DAGs the browser demo executes, so role labels and embedded body DAGs stay visible beside their JSON-LD.

DAG registration and diagram

The in-browser Cartographer demo is the executable example for multi-backend role binding. The same JSON-LD assembly expresses both:

  • process-stream is a ScatterNode delegated to container role cpu.
  • summarize-insights is an EmbeddedDAGNode delegated to container role io.

The browser runner binds both roles to real WebWorkerContainer pools. The DAG does not change when a role is in-process, in a worker, or supplied by a plugin registry; the canonical assembly remains JSON-LD produced by the builder.

Top-level browser DAG

cartographerWorkersDAG is the DAG rendered and executed by the Cartographer page. The Mermaid diagram is generated from the JSON-LD below it, so the container-role labels are visible in the same shape the dispatcher executes.

Cartographer workers DAG

6 placements
DAG JSON-LD registered with the dispatcher
{
  "@context": {
    "@version": 1.1,
    "name": {
      "@id": "https://noocodec.dev/ontology/dag/name"
    },
    "version": {
      "@id": "https://noocodec.dev/ontology/dag/version"
    },
    "entrypoints": {
      "@id": "https://noocodec.dev/ontology/dag/entrypoints",
      "@container": "@index"
    },
    "nodes": {
      "@id": "https://noocodec.dev/ontology/dag/nodes",
      "@container": "@set"
    },
    "outputs": {
      "@id": "https://noocodec.dev/ontology/dag/outputs"
    },
    "node": {
      "@id": "https://noocodec.dev/ontology/dag/node"
    },
    "dag": {
      "@id": "https://noocodec.dev/ontology/dag/dag"
    },
    "body": {
      "@id": "https://noocodec.dev/ontology/dag/body"
    },
    "source": {
      "@id": "https://noocodec.dev/ontology/dag/source"
    },
    "sources": {
      "@id": "https://noocodec.dev/ontology/dag/sources",
      "@container": "@index"
    },
    "itemKey": {
      "@id": "https://noocodec.dev/ontology/dag/itemKey"
    },
    "execution": {
      "@id": "https://noocodec.dev/ontology/dag/execution"
    },
    "concurrency": {
      "@id": "https://noocodec.dev/ontology/dag/concurrency"
    },
    "throttle": {
      "@id": "https://noocodec.dev/ontology/dag/throttle"
    },
    "reservoir": {
      "@id": "https://noocodec.dev/ontology/dag/reservoir"
    },
    "gather": {
      "@id": "https://noocodec.dev/ontology/dag/gather"
    },
    "dagReference": {
      "@id": "https://noocodec.dev/ontology/dag/dagReference",
      "@type": "@id"
    },
    "DagReference": {
      "@id": "https://noocodec.dev/ontology/dag/DagReference"
    },
    "from": {
      "@id": "https://noocodec.dev/ontology/dag/from"
    },
    "path": {
      "@id": "https://noocodec.dev/ontology/dag/path"
    },
    "candidates": {
      "@id": "https://noocodec.dev/ontology/dag/candidates",
      "@container": "@set"
    },
    "candidateDag": {
      "@id": "https://noocodec.dev/ontology/dag/candidateDag",
      "@type": "@id"
    },
    "selectedDag": {
      "@id": "https://noocodec.dev/ontology/dag/selectedDag",
      "@type": "@id"
    },
    "resultField": {
      "@id": "https://noocodec.dev/ontology/dag/resultField"
    },
    "policy": {
      "@id": "https://noocodec.dev/ontology/dag/policy"
    },
    "reducer": {
      "@id": "https://noocodec.dev/ontology/dag/reducer"
    },
    "outcome": {
      "@id": "https://noocodec.dev/ontology/dag/outcome"
    },
    "phase": {
      "@id": "https://noocodec.dev/ontology/dag/phase"
    },
    "stateMapping": {
      "@id": "https://noocodec.dev/ontology/dag/stateMapping"
    },
    "container": {
      "@id": "https://noocodec.dev/ontology/dag/container"
    },
    "DAG": {
      "@id": "https://noocodec.dev/ontology/dag/DAG"
    },
    "Placement": {
      "@id": "https://noocodec.dev/ontology/dag/Placement"
    },
    "SingleNode": {
      "@id": "https://noocodec.dev/ontology/dag/SingleNode"
    },
    "ScatterNode": {
      "@id": "https://noocodec.dev/ontology/dag/ScatterNode"
    },
    "EmbeddedDAGNode": {
      "@id": "https://noocodec.dev/ontology/dag/EmbeddedDAGNode"
    },
    "GatherNode": {
      "@id": "https://noocodec.dev/ontology/dag/GatherNode"
    },
    "TerminalNode": {
      "@id": "https://noocodec.dev/ontology/dag/TerminalNode"
    },
    "PhaseNode": {
      "@id": "https://noocodec.dev/ontology/dag/PhaseNode"
    }
  },
  "@id": "urn:noocodec:dag:cartographer",
  "@type": "DAG",
  "name": "dag:cartographer",
  "version": "1.0",
  "entrypoints": {
    "position-ping": "urn:noocodec:dag:cartographer/node/intake-gather",
    "facility-scan": "urn:noocodec:dag:cartographer/node/intake-gather",
    "sensor-reading": "urn:noocodec:dag:cartographer/node/intake-gather",
    "customs-event": "urn:noocodec:dag:cartographer/node/intake-gather",
    "delivery-confirmation": "urn:noocodec:dag:cartographer/node/intake-gather"
  },
  "nodes": [
    {
      "@id": "urn:noocodec:dag:cartographer/node/intake-gather",
      "@type": "GatherNode",
      "name": "dag:cartographer/node/intake-gather",
      "sources": {
        "urn:noocodec:dag:cartographer/entrypoint/position-ping": {},
        "urn:noocodec:dag:cartographer/entrypoint/facility-scan": {},
        "urn:noocodec:dag:cartographer/entrypoint/sensor-reading": {},
        "urn:noocodec:dag:cartographer/entrypoint/customs-event": {},
        "urn:noocodec:dag:cartographer/entrypoint/delivery-confirmation": {}
      },
      "gather": {
        "strategy": "source-intake"
      },
      "outputs": {
        "success": "urn:noocodec:dag:cartographer/node/process-stream",
        "error": "urn:noocodec:dag:cartographer/node/process-stream",
        "empty": "urn:noocodec:dag:cartographer/node/failed"
      }
    },
    {
      "@id": "urn:noocodec:dag:cartographer/node/process-stream",
      "@type": "ScatterNode",
      "name": "dag:cartographer/node/process-stream",
      "source": "sources",
      "body": {
        "dag": "urn:noocodec:dag:stream-event"
      },
      "outputs": {
        "all-success": "urn:noocodec:dag:cartographer/node/fold-insights",
        "partial": "urn:noocodec:dag:cartographer/node/fold-insights",
        "all-error": "urn:noocodec:dag:cartographer/node/fold-insights",
        "empty": "urn:noocodec:dag:cartographer/node/summarize-insights"
      },
      "itemKey": "source-payload",
      "reducer": "aggregate",
      "container": "cpu",
      "execution": {
        "mode": "reservoir",
        "concurrency": 16,
        "reservoir": {
          "keyField": "eventType",
          "capacity": 1000
        }
      }
    },
    {
      "@id": "urn:noocodec:dag:cartographer/node/fold-insights",
      "@type": "GatherNode",
      "name": "dag:cartographer/node/fold-insights",
      "sources": {
        "urn:noocodec:dag:cartographer/node/process-stream": {}
      },
      "gather": {
        "strategy": "insights-fold"
      },
      "outputs": {
        "success": "urn:noocodec:dag:cartographer/node/summarize-insights",
        "error": "urn:noocodec:dag:cartographer/node/summarize-insights",
        "empty": "urn:noocodec:dag:cartographer/node/summarize-insights"
      }
    },
    {
      "@id": "urn:noocodec:dag:cartographer/node/summarize-insights",
      "@type": "EmbeddedDAGNode",
      "name": "dag:cartographer/node/summarize-insights",
      "outputs": {
        "success": "urn:noocodec:dag:cartographer/node/done",
        "error": "urn:noocodec:dag:cartographer/node/failed"
      },
      "dag": "urn:noocodec:dag:insights-summary",
      "container": "io"
    },
    {
      "@id": "urn:noocodec:dag:cartographer/node/done",
      "@type": "TerminalNode",
      "name": "dag:cartographer/node/done",
      "outcome": "completed"
    },
    {
      "@id": "urn:noocodec:dag:cartographer/node/failed",
      "@type": "TerminalNode",
      "name": "dag:cartographer/node/failed",
      "outcome": "failed"
    }
  ]
}
Mermaid generated from the same DAG
Mermaid source
%%{init: {"flowchart":{"nodeSpacing":92,"rankSpacing":104,"padding":28}}}%%
flowchart TB
  %% dag:cartographer (v1.0)
  entry_position-ping(["position-ping"])
  entry_position-ping --> urn_noocodec_dag_cartographer/node/intake-gather
  entry_facility-scan(["facility-scan"])
  entry_facility-scan --> urn_noocodec_dag_cartographer/node/intake-gather
  entry_sensor-reading(["sensor-reading"])
  entry_sensor-reading --> urn_noocodec_dag_cartographer/node/intake-gather
  entry_customs-event(["customs-event"])
  entry_customs-event --> urn_noocodec_dag_cartographer/node/intake-gather
  entry_delivery-confirmation(["delivery-confirmation"])
  entry_delivery-confirmation --> urn_noocodec_dag_cartographer/node/intake-gather
  urn_noocodec_dag_cartographer/node/intake-gather{"dag_cartographer/node/intake-gather"}
  urn_noocodec_dag_cartographer/node/intake-gather -->|success| urn_noocodec_dag_cartographer/node/process-stream
  urn_noocodec_dag_cartographer/node/intake-gather -->|error| urn_noocodec_dag_cartographer/node/process-stream
  urn_noocodec_dag_cartographer/node/intake-gather -->|empty| urn_noocodec_dag_cartographer/node/failed
  urn_noocodec_dag_cartographer/node/process-stream[/"dag:cartographer/node/process-stream ▣ eventType ×1000"/]
  urn_noocodec_dag_cartographer/node/process-stream -->|all-success| urn_noocodec_dag_cartographer/node/fold-insights
  urn_noocodec_dag_cartographer/node/process-stream -->|partial| urn_noocodec_dag_cartographer/node/fold-insights
  urn_noocodec_dag_cartographer/node/process-stream -->|all-error| urn_noocodec_dag_cartographer/node/fold-insights
  urn_noocodec_dag_cartographer/node/process-stream -->|empty| urn_noocodec_dag_cartographer/node/summarize-insights
  urn_noocodec_dag_cartographer/node/fold-insights{"dag_cartographer/node/fold-insights"}
  urn_noocodec_dag_cartographer/node/fold-insights -->|success| urn_noocodec_dag_cartographer/node/summarize-insights
  urn_noocodec_dag_cartographer/node/fold-insights -->|error| urn_noocodec_dag_cartographer/node/summarize-insights
  urn_noocodec_dag_cartographer/node/fold-insights -->|empty| urn_noocodec_dag_cartographer/node/summarize-insights
  urn_noocodec_dag_cartographer/node/summarize-insights[["dag:cartographer/node/summarize-insights"]]
  urn_noocodec_dag_cartographer/node/summarize-insights -->|success| urn_noocodec_dag_cartographer/node/done
  urn_noocodec_dag_cartographer/node/summarize-insights -->|error| urn_noocodec_dag_cartographer/node/failed
  urn_noocodec_dag_cartographer/node/done((("dag:cartographer/node/done")))
  urn_noocodec_dag_cartographer/node/failed>"dag:cartographer/node/failed"]
  classDef contained-cpu fill:#b45309,stroke:#d97706,color:#eef3f7
  class urn_noocodec_dag_cartographer/node/process-stream contained-cpu
  classDef contained-io fill:#7c3aed,stroke:#8b5cf6,color:#eef3f7
  class urn_noocodec_dag_cartographer/node/summarize-insights contained-io
  classDef reservoir fill:#1e3a5f,stroke:#3b82f6,color:#bfdbfe
  class urn_noocodec_dag_cartographer/node/process-stream reservoir
ts
/** Default reservoir capacity for the process-stream scatter in the workers DAG. */
export const DEFAULT_RESERVOIR_CAPACITY = 1000;

/**
 * CartographerWorkersDag: static factory for the cartographer-workers DAG and
 * its associated dispatcher bundle. Consumers call CartographerWorkersDag.build()
 * (DAG only) or CartographerWorkersDag.bundle() (full dispatcher bundle) with an
 * optional reservoir capacity override.
 *
 * DAG topology — identical to cartographerDAG with containerized boundaries:
 *   - container: 'cpu' so each stream-event body runs inside a
 *     WorkerThreadContainer/WebWorkerContainer rather than in-process.
 *   - container: 'io' so the final summary runs through the same embedded-DAG
 *     interface used by plugins and nested flows.
 *   - reservoir.capacity is parameterised; callers pass their UI-controlled
 *     batch size rather than relying on the compile-time default.
 *
 *   5 data-type entrypoints → gather('intake-gather', source-intake)
 *     → scatter('process-stream', 'sources', { dag: 'stream-event' },
 *               concurrency: 16, container: 'cpu', reservoir: { capacity })
 *     → gather('fold-insights', strategy: insights-fold)
 *     → embed('summarize-insights', 'insights-summary', container: 'io')
 *     → done
 */
export class CartographerWorkersDag {
  private constructor() { /* static-only */ }

  /**
   * Build the cartographer-workers DAG with the given reservoir capacity.
   * CLI, smoke tests, and dag-validate consumers use cartographerWorkersDAG
   * (the pre-built constant); the browser demo calls this with a UI-controlled value.
   */
  static build(capacity: number = DEFAULT_RESERVOIR_CAPACITY): DAGType {
    return new DAGBuilder(CARTOGRAPHER_DAG_IRI, '1.0')

      .gather(
        CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'intake-gather'),
        CARTOGRAPHER_IRIS.intakeSources(CARTOGRAPHER_DAG_IRI),
        { 'strategy': 'source-intake' },
        {
          'success': CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'process-stream'),
          'error':   CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'process-stream'),
          'empty':   CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'failed'),
        },
      )

      .scatter(
        CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'process-stream'),
        'sources',
        { 'dag': STREAM_EVENT_DAG_IRI },
        {
          'all-success': CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'fold-insights'),
          'partial':     CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'fold-insights'),
          'all-error':   CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'fold-insights'),
          'empty':       CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'summarize-insights'),
        },
        {
          'itemKey':     'source-payload',
          'container':   'cpu',
          'execution': { 'mode': 'reservoir', 'concurrency': 16, 'reservoir': { 'keyField': 'eventType', 'capacity': capacity } },
        },
      )
      .gather(CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'fold-insights'), {
        [CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'process-stream')]: {},
      }, { 'strategy': 'insights-fold' }, {
        'success': CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'summarize-insights'),
        'error':   CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'summarize-insights'),
        'empty':   CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'summarize-insights'),
      })

      .embed<CartographerState, CartographerState>(CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'summarize-insights'), INSIGHTS_SUMMARY_DAG_IRI, {
        'success': CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'done'),
        'error':   CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'failed'),
      }, {
        'container': 'io',
      })

      .terminal(CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'done'), { outcome: 'completed' })
      .terminal(CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'failed'), { outcome: 'failed' })

      .entrypoints({
        'position-ping':          CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'intake-gather'),
        'facility-scan':          CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'intake-gather'),
        'sensor-reading':         CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'intake-gather'),
        'customs-event':          CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'intake-gather'),
        'delivery-confirmation':  CARTOGRAPHER_IRIS.placementIri(CARTOGRAPHER_DAG_IRI, 'intake-gather'),
      })

      .build();
  }

  /**
   * Build the workers bundle with a configurable reservoir capacity. The returned
   * bundle is identical to cartographerWorkersBundle except that its cartographer
   * DAG is built with CartographerWorkersDag.build(capacity) so the process-stream
   * scatter uses the caller-supplied batch size.
   *
   * Used by the browser demo to wire UI-controlled knobs into each run() without
   * mutating the shared default-capacity constants.
   */
  static bundle(
    capacity: number = DEFAULT_RESERVOIR_CAPACITY,
  ): DispatcherBundleType<CartographerState> {
    return {
      'nodes': [
        ...cartographerWorkerRuntimeBundle.nodes,
      ],
      'dags': [
        ...cartographerWorkerRuntimeBundle.dags,
        CartographerWorkersDag.build(capacity),
      ],
    };
  }
}

/**
 * cartographerWorkersDAG: pre-built workers DAG at DEFAULT_RESERVOIR_CAPACITY.
 * CLI, smoke tests, and dag-validate consumers use this constant; the browser
 * demo uses CartographerWorkersDag.build(capacity) with a UI-controlled value.
 */
export const cartographerWorkersDAG: DAGType = CartographerWorkersDag.build();

cpu body DAG

The cpu role runs the stream-event body for every source payload. This is not a synthetic worker sample; it is the live Cartographer decode and routing pipeline.

stream-event body DAG

9 placements
DAG JSON-LD registered with the dispatcher
{
  "@context": {
    "@version": 1.1,
    "name": {
      "@id": "https://noocodec.dev/ontology/dag/name"
    },
    "version": {
      "@id": "https://noocodec.dev/ontology/dag/version"
    },
    "entrypoints": {
      "@id": "https://noocodec.dev/ontology/dag/entrypoints",
      "@container": "@index"
    },
    "nodes": {
      "@id": "https://noocodec.dev/ontology/dag/nodes",
      "@container": "@set"
    },
    "outputs": {
      "@id": "https://noocodec.dev/ontology/dag/outputs"
    },
    "node": {
      "@id": "https://noocodec.dev/ontology/dag/node"
    },
    "dag": {
      "@id": "https://noocodec.dev/ontology/dag/dag"
    },
    "body": {
      "@id": "https://noocodec.dev/ontology/dag/body"
    },
    "source": {
      "@id": "https://noocodec.dev/ontology/dag/source"
    },
    "sources": {
      "@id": "https://noocodec.dev/ontology/dag/sources",
      "@container": "@index"
    },
    "itemKey": {
      "@id": "https://noocodec.dev/ontology/dag/itemKey"
    },
    "execution": {
      "@id": "https://noocodec.dev/ontology/dag/execution"
    },
    "concurrency": {
      "@id": "https://noocodec.dev/ontology/dag/concurrency"
    },
    "throttle": {
      "@id": "https://noocodec.dev/ontology/dag/throttle"
    },
    "reservoir": {
      "@id": "https://noocodec.dev/ontology/dag/reservoir"
    },
    "gather": {
      "@id": "https://noocodec.dev/ontology/dag/gather"
    },
    "dagReference": {
      "@id": "https://noocodec.dev/ontology/dag/dagReference",
      "@type": "@id"
    },
    "DagReference": {
      "@id": "https://noocodec.dev/ontology/dag/DagReference"
    },
    "from": {
      "@id": "https://noocodec.dev/ontology/dag/from"
    },
    "path": {
      "@id": "https://noocodec.dev/ontology/dag/path"
    },
    "candidates": {
      "@id": "https://noocodec.dev/ontology/dag/candidates",
      "@container": "@set"
    },
    "candidateDag": {
      "@id": "https://noocodec.dev/ontology/dag/candidateDag",
      "@type": "@id"
    },
    "selectedDag": {
      "@id": "https://noocodec.dev/ontology/dag/selectedDag",
      "@type": "@id"
    },
    "resultField": {
      "@id": "https://noocodec.dev/ontology/dag/resultField"
    },
    "policy": {
      "@id": "https://noocodec.dev/ontology/dag/policy"
    },
    "reducer": {
      "@id": "https://noocodec.dev/ontology/dag/reducer"
    },
    "outcome": {
      "@id": "https://noocodec.dev/ontology/dag/outcome"
    },
    "phase": {
      "@id": "https://noocodec.dev/ontology/dag/phase"
    },
    "stateMapping": {
      "@id": "https://noocodec.dev/ontology/dag/stateMapping"
    },
    "container": {
      "@id": "https://noocodec.dev/ontology/dag/container"
    },
    "DAG": {
      "@id": "https://noocodec.dev/ontology/dag/DAG"
    },
    "Placement": {
      "@id": "https://noocodec.dev/ontology/dag/Placement"
    },
    "SingleNode": {
      "@id": "https://noocodec.dev/ontology/dag/SingleNode"
    },
    "ScatterNode": {
      "@id": "https://noocodec.dev/ontology/dag/ScatterNode"
    },
    "EmbeddedDAGNode": {
      "@id": "https://noocodec.dev/ontology/dag/EmbeddedDAGNode"
    },
    "GatherNode": {
      "@id": "https://noocodec.dev/ontology/dag/GatherNode"
    },
    "TerminalNode": {
      "@id": "https://noocodec.dev/ontology/dag/TerminalNode"
    },
    "PhaseNode": {
      "@id": "https://noocodec.dev/ontology/dag/PhaseNode"
    }
  },
  "@id": "urn:noocodec:dag:stream-event",
  "@type": "DAG",
  "name": "dag:stream-event",
  "version": "1.0",
  "entrypoints": {
    "main": "urn:noocodec:dag:stream-event/node/decode-payload"
  },
  "nodes": [
    {
      "@id": "urn:noocodec:dag:stream-event/node/decode-payload",
      "@type": "SingleNode",
      "name": "dag:stream-event/node/decode-payload",
      "node": "urn:noocodec:node:decode-payload",
      "outputs": {
        "decoded": "urn:noocodec:dag:stream-event/node/route-event-type-variant",
        "invalid": "urn:noocodec:dag:stream-event/node/rejected"
      }
    },
    {
      "@id": "urn:noocodec:dag:stream-event/node/route-event-type-variant",
      "@type": "SingleNode",
      "name": "dag:stream-event/node/route-event-type-variant",
      "node": "urn:noocodec:node:route-event-type-variant",
      "outputs": {
        "position-ping": "urn:noocodec:dag:stream-event/node/pipeline-position-ping",
        "sensor-reading": "urn:noocodec:dag:stream-event/node/pipeline-sensor-reading",
        "customs-event": "urn:noocodec:dag:stream-event/node/pipeline-customs-event",
        "facility-scan": "urn:noocodec:dag:stream-event/node/pipeline-facility-scan",
        "delivery-confirmation": "urn:noocodec:dag:stream-event/node/pipeline-delivery-confirmation"
      }
    },
    {
      "@id": "urn:noocodec:dag:stream-event/node/pipeline-position-ping",
      "@type": "EmbeddedDAGNode",
      "name": "dag:stream-event/node/pipeline-position-ping",
      "outputs": {
        "success": "urn:noocodec:dag:stream-event/node/done",
        "error": "urn:noocodec:dag:stream-event/node/rejected"
      },
      "dag": "urn:noocodec:dag:pipeline-position-ping",
      "stateMapping": {
        "output": {
          "canonicalVariant": "canonicalVariant",
          "raw": "raw",
          "normalized": "normalized",
          "currentEvent": "currentEvent",
          "geoContext": "geoContext",
          "resolvedGeo": "resolvedGeo",
          "legKm": "legKm",
          "routing": "routing",
          "enriched": "enriched",
          "capturedErrors": "capturedErrors"
        }
      }
    },
    {
      "@id": "urn:noocodec:dag:stream-event/node/pipeline-sensor-reading",
      "@type": "EmbeddedDAGNode",
      "name": "dag:stream-event/node/pipeline-sensor-reading",
      "outputs": {
        "success": "urn:noocodec:dag:stream-event/node/done",
        "error": "urn:noocodec:dag:stream-event/node/rejected"
      },
      "dag": "urn:noocodec:dag:pipeline-sensor-reading",
      "stateMapping": {
        "output": {
          "canonicalVariant": "canonicalVariant",
          "raw": "raw",
          "normalized": "normalized",
          "currentEvent": "currentEvent",
          "geoContext": "geoContext",
          "resolvedGeo": "resolvedGeo",
          "coldChainBreach": "coldChainBreach",
          "legKm": "legKm",
          "routing": "routing",
          "enriched": "enriched",
          "capturedErrors": "capturedErrors"
        }
      }
    },
    {
      "@id": "urn:noocodec:dag:stream-event/node/pipeline-customs-event",
      "@type": "EmbeddedDAGNode",
      "name": "dag:stream-event/node/pipeline-customs-event",
      "outputs": {
        "success": "urn:noocodec:dag:stream-event/node/done",
        "error": "urn:noocodec:dag:stream-event/node/rejected"
      },
      "dag": "urn:noocodec:dag:pipeline-customs-event",
      "stateMapping": {
        "output": {
          "canonicalVariant": "canonicalVariant",
          "raw": "raw",
          "normalized": "normalized",
          "currentEvent": "currentEvent",
          "geoContext": "geoContext",
          "resolvedGeo": "resolvedGeo",
          "customsDwellHours": "customsDwellHours",
          "legKm": "legKm",
          "routing": "routing",
          "enriched": "enriched",
          "capturedErrors": "capturedErrors"
        }
      }
    },
    {
      "@id": "urn:noocodec:dag:stream-event/node/pipeline-facility-scan",
      "@type": "EmbeddedDAGNode",
      "name": "dag:stream-event/node/pipeline-facility-scan",
      "outputs": {
        "success": "urn:noocodec:dag:stream-event/node/done",
        "error": "urn:noocodec:dag:stream-event/node/rejected"
      },
      "dag": "urn:noocodec:dag:pipeline-facility-scan",
      "stateMapping": {
        "output": {
          "canonicalVariant": "canonicalVariant",
          "raw": "raw",
          "normalized": "normalized",
          "currentEvent": "currentEvent",
          "geoContext": "geoContext",
          "resolvedGeo": "resolvedGeo",
          "pricedOrder": "pricedOrder",
          "shippingQuote": "shippingQuote",
          "deliveryEstimate": "deliveryEstimate",
          "legKm": "legKm",
          "gdprResult": "gdprResult",
          "routing": "routing",
          "enriched": "enriched",
          "capturedErrors": "capturedErrors"
        }
      }
    },
    {
      "@id": "urn:noocodec:dag:stream-event/node/pipeline-delivery-confirmation",
      "@type": "EmbeddedDAGNode",
      "name": "dag:stream-event/node/pipeline-delivery-confirmation",
      "outputs": {
        "success": "urn:noocodec:dag:stream-event/node/done",
        "error": "urn:noocodec:dag:stream-event/node/rejected"
      },
      "dag": "urn:noocodec:dag:pipeline-delivery-confirmation",
      "stateMapping": {
        "output": {
          "canonicalVariant": "canonicalVariant",
          "raw": "raw",
          "normalized": "normalized",
          "currentEvent": "currentEvent",
          "geoContext": "geoContext",
          "resolvedGeo": "resolvedGeo",
          "legKm": "legKm",
          "gdprResult": "gdprResult",
          "routing": "routing",
          "enriched": "enriched",
          "capturedErrors": "capturedErrors"
        }
      }
    },
    {
      "@id": "urn:noocodec:dag:stream-event/node/done",
      "@type": "TerminalNode",
      "name": "dag:stream-event/node/done",
      "outcome": "completed"
    },
    {
      "@id": "urn:noocodec:dag:stream-event/node/rejected",
      "@type": "TerminalNode",
      "name": "dag:stream-event/node/rejected",
      "outcome": "failed"
    }
  ]
}
Mermaid generated from the same DAG
Mermaid source
%%{init: {"flowchart":{"nodeSpacing":92,"rankSpacing":104,"padding":28}}}%%
flowchart TB
  %% dag:stream-event (v1.0)
  entry_main(["main"])
  entry_main --> urn_noocodec_dag_stream-event/node/decode-payload
  urn_noocodec_dag_stream-event/node/decode-payload["dag:stream-event/node/decode-payload"]
  urn_noocodec_dag_stream-event/node/decode-payload -->|decoded| urn_noocodec_dag_stream-event/node/route-event-type-variant
  urn_noocodec_dag_stream-event/node/decode-payload -->|invalid| urn_noocodec_dag_stream-event/node/rejected
  urn_noocodec_dag_stream-event/node/route-event-type-variant["dag:stream-event/node/route-event-type-variant"]
  urn_noocodec_dag_stream-event/node/route-event-type-variant -->|position-ping| urn_noocodec_dag_stream-event/node/pipeline-position-ping
  urn_noocodec_dag_stream-event/node/route-event-type-variant -->|sensor-reading| urn_noocodec_dag_stream-event/node/pipeline-sensor-reading
  urn_noocodec_dag_stream-event/node/route-event-type-variant -->|customs-event| urn_noocodec_dag_stream-event/node/pipeline-customs-event
  urn_noocodec_dag_stream-event/node/route-event-type-variant -->|facility-scan| urn_noocodec_dag_stream-event/node/pipeline-facility-scan
  urn_noocodec_dag_stream-event/node/route-event-type-variant -->|delivery-confirmation| urn_noocodec_dag_stream-event/node/pipeline-delivery-confirmation
  urn_noocodec_dag_stream-event/node/pipeline-position-ping[["dag:stream-event/node/pipeline-position-ping"]]
  urn_noocodec_dag_stream-event/node/pipeline-position-ping -->|success| urn_noocodec_dag_stream-event/node/done
  urn_noocodec_dag_stream-event/node/pipeline-position-ping -->|error| urn_noocodec_dag_stream-event/node/rejected
  urn_noocodec_dag_stream-event/node/pipeline-sensor-reading[["dag:stream-event/node/pipeline-sensor-reading"]]
  urn_noocodec_dag_stream-event/node/pipeline-sensor-reading -->|success| urn_noocodec_dag_stream-event/node/done
  urn_noocodec_dag_stream-event/node/pipeline-sensor-reading -->|error| urn_noocodec_dag_stream-event/node/rejected
  urn_noocodec_dag_stream-event/node/pipeline-customs-event[["dag:stream-event/node/pipeline-customs-event"]]
  urn_noocodec_dag_stream-event/node/pipeline-customs-event -->|success| urn_noocodec_dag_stream-event/node/done
  urn_noocodec_dag_stream-event/node/pipeline-customs-event -->|error| urn_noocodec_dag_stream-event/node/rejected
  urn_noocodec_dag_stream-event/node/pipeline-facility-scan[["dag:stream-event/node/pipeline-facility-scan"]]
  urn_noocodec_dag_stream-event/node/pipeline-facility-scan -->|success| urn_noocodec_dag_stream-event/node/done
  urn_noocodec_dag_stream-event/node/pipeline-facility-scan -->|error| urn_noocodec_dag_stream-event/node/rejected
  urn_noocodec_dag_stream-event/node/pipeline-delivery-confirmation[["dag:stream-event/node/pipeline-delivery-confirmation"]]
  urn_noocodec_dag_stream-event/node/pipeline-delivery-confirmation -->|success| urn_noocodec_dag_stream-event/node/done
  urn_noocodec_dag_stream-event/node/pipeline-delivery-confirmation -->|error| urn_noocodec_dag_stream-event/node/rejected
  urn_noocodec_dag_stream-event/node/done((("dag:stream-event/node/done")))
  urn_noocodec_dag_stream-event/node/rejected>"dag:stream-event/node/rejected"]
ts
const STREAM_EVENT_DAG_IRI = CARTOGRAPHER_IRIS.dag.streamEvent;

export const streamEventDAG: DAGType = new DAGBuilder(STREAM_EVENT_DAG_IRI, '1.0')

  // 1. decode-payload: reads 'source-payload' metadata, decodes wire format
  //    (json/csv/ndjson/yaml, optionally gzip), builds CanonicalEventVariant,
  //    and sets state.canonicalVariant. Routes 'decoded' or 'invalid'.
  .node(CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'decode-payload'), decodePayload, {
    'decoded': CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'route-event-type-variant'),
    'invalid': CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'rejected'),
  })

  // 2. route-event-type-variant: reads state.canonicalVariant.eventType and
  //    dispatches to the corresponding per-type sub-DAG. Sets state.routing.
  .node(CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'route-event-type-variant'), routeEventType, {
    'position-ping':         CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'pipeline-position-ping'),
    'sensor-reading':        CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'pipeline-sensor-reading'),
    'customs-event':         CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'pipeline-customs-event'),
    'facility-scan':         CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'pipeline-facility-scan'),
    'delivery-confirmation': CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'pipeline-delivery-confirmation'),
  })

  // 3a. pipeline-position-ping: geo + leg measurement.
  .embed<CartographerState, CartographerState>(CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'pipeline-position-ping'), CARTOGRAPHER_IRIS.dag.pipelinePositionPing, {
    'success': CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'done'),
    'error':   CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'rejected'),
  }, {
    'outputs': {
      'canonicalVariant': 'canonicalVariant',
      'raw':              'raw',
      'normalized':       'normalized',
      'currentEvent':     'currentEvent',
      'geoContext':       'geoContext',
      'resolvedGeo':      'resolvedGeo',
      'legKm':            'legKm',
      'routing':          'routing',
      'enriched':         'enriched',
      'capturedErrors':   'capturedErrors',
    },
  })

  // 3b. pipeline-sensor-reading: geo + cold-chain + leg measurement.
  .embed<CartographerState, CartographerState>(CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'pipeline-sensor-reading'), CARTOGRAPHER_IRIS.dag.pipelineSensorReading, {
    'success': CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'done'),
    'error':   CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'rejected'),
  }, {
    'outputs': {
      'canonicalVariant': 'canonicalVariant',
      'raw':              'raw',
      'normalized':       'normalized',
      'currentEvent':     'currentEvent',
      'geoContext':       'geoContext',
      'resolvedGeo':      'resolvedGeo',
      'coldChainBreach':  'coldChainBreach',
      'legKm':            'legKm',
      'routing':          'routing',
      'enriched':         'enriched',
      'capturedErrors':   'capturedErrors',
    },
  })

  // 3c. pipeline-customs-event: geo + customs-dwell + leg measurement.
  .embed<CartographerState, CartographerState>(CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'pipeline-customs-event'), CARTOGRAPHER_IRIS.dag.pipelineCustomsEvent, {
    'success': CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'done'),
    'error':   CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'rejected'),
  }, {
    'outputs': {
      'canonicalVariant':  'canonicalVariant',
      'raw':               'raw',
      'normalized':        'normalized',
      'currentEvent':      'currentEvent',
      'geoContext':        'geoContext',
      'resolvedGeo':       'resolvedGeo',
      'customsDwellHours': 'customsDwellHours',
      'legKm':             'legKm',
      'routing':           'routing',
      'enriched':          'enriched',
      'capturedErrors':   'capturedErrors',
    },
  })

  // 3d. pipeline-facility-scan: geo + facility canonicalization + order enrichment
  //     + GDPR-gated redaction.
  .embed<CartographerState, CartographerState>(CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'pipeline-facility-scan'), CARTOGRAPHER_IRIS.dag.pipelineFacilityScan, {
    'success': CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'done'),
    'error':   CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'rejected'),
  }, {
    'outputs': {
      'canonicalVariant':  'canonicalVariant',
      'raw':               'raw',
      'normalized':        'normalized',
      'currentEvent':      'currentEvent',
      'geoContext':        'geoContext',
      'resolvedGeo':       'resolvedGeo',
      'pricedOrder':       'pricedOrder',
      'shippingQuote':     'shippingQuote',
      'deliveryEstimate':  'deliveryEstimate',
      'legKm':             'legKm',
      'gdprResult':        'gdprResult',
      'routing':           'routing',
      'enriched':          'enriched',
      'capturedErrors':   'capturedErrors',
    },
  })

  // 3e. pipeline-delivery-confirmation: geo + recipient canonicalization +
  //     delivery confirmation + GDPR-gated redaction.
  .embed<CartographerState, CartographerState>(CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'pipeline-delivery-confirmation'), CARTOGRAPHER_IRIS.dag.pipelineDeliveryConfirmation, {
    'success': CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'done'),
    'error':   CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'rejected'),
  }, {
    'outputs': {
      'canonicalVariant': 'canonicalVariant',
      'raw':              'raw',
      'normalized':       'normalized',
      'currentEvent':     'currentEvent',
      'geoContext':       'geoContext',
      'resolvedGeo':      'resolvedGeo',
      'legKm':            'legKm',
      'gdprResult':       'gdprResult',
      'routing':          'routing',
      'enriched':         'enriched',
      'capturedErrors':   'capturedErrors',
    },
  })

  .terminal(CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'done'),     { outcome: 'completed' })
  .terminal(CARTOGRAPHER_IRIS.placementIri(STREAM_EVENT_DAG_IRI, 'rejected'), { outcome: 'failed' })

  .build();

/**
 * Bundle fragment for dag.ts. Registers decode-payload, route-event-type-variant,
 * and the stream-event DAG with the dispatcher. routeEventType re-registration is
 * a no-op (same instance; the bundle registrar is idempotent).
 */
export const streamEventBundle: DispatcherBundleType<CartographerState> = {
  'nodes': [decodePayload, routeEventType],
  'dags':  [streamEventDAG],
};

io body DAG

The io role runs the summary body as an embedded DAG after the scatter gather fold completes. Packaging the summary as a DAG keeps plugins, embedded flows, and container delegation on one interface.

insights-summary body DAG

2 placements
DAG JSON-LD registered with the dispatcher
{
  "@context": {
    "@version": 1.1,
    "name": {
      "@id": "https://noocodec.dev/ontology/dag/name"
    },
    "version": {
      "@id": "https://noocodec.dev/ontology/dag/version"
    },
    "entrypoints": {
      "@id": "https://noocodec.dev/ontology/dag/entrypoints",
      "@container": "@index"
    },
    "nodes": {
      "@id": "https://noocodec.dev/ontology/dag/nodes",
      "@container": "@set"
    },
    "outputs": {
      "@id": "https://noocodec.dev/ontology/dag/outputs"
    },
    "node": {
      "@id": "https://noocodec.dev/ontology/dag/node"
    },
    "dag": {
      "@id": "https://noocodec.dev/ontology/dag/dag"
    },
    "body": {
      "@id": "https://noocodec.dev/ontology/dag/body"
    },
    "source": {
      "@id": "https://noocodec.dev/ontology/dag/source"
    },
    "sources": {
      "@id": "https://noocodec.dev/ontology/dag/sources",
      "@container": "@index"
    },
    "itemKey": {
      "@id": "https://noocodec.dev/ontology/dag/itemKey"
    },
    "execution": {
      "@id": "https://noocodec.dev/ontology/dag/execution"
    },
    "concurrency": {
      "@id": "https://noocodec.dev/ontology/dag/concurrency"
    },
    "throttle": {
      "@id": "https://noocodec.dev/ontology/dag/throttle"
    },
    "reservoir": {
      "@id": "https://noocodec.dev/ontology/dag/reservoir"
    },
    "gather": {
      "@id": "https://noocodec.dev/ontology/dag/gather"
    },
    "dagReference": {
      "@id": "https://noocodec.dev/ontology/dag/dagReference",
      "@type": "@id"
    },
    "DagReference": {
      "@id": "https://noocodec.dev/ontology/dag/DagReference"
    },
    "from": {
      "@id": "https://noocodec.dev/ontology/dag/from"
    },
    "path": {
      "@id": "https://noocodec.dev/ontology/dag/path"
    },
    "candidates": {
      "@id": "https://noocodec.dev/ontology/dag/candidates",
      "@container": "@set"
    },
    "candidateDag": {
      "@id": "https://noocodec.dev/ontology/dag/candidateDag",
      "@type": "@id"
    },
    "selectedDag": {
      "@id": "https://noocodec.dev/ontology/dag/selectedDag",
      "@type": "@id"
    },
    "resultField": {
      "@id": "https://noocodec.dev/ontology/dag/resultField"
    },
    "policy": {
      "@id": "https://noocodec.dev/ontology/dag/policy"
    },
    "reducer": {
      "@id": "https://noocodec.dev/ontology/dag/reducer"
    },
    "outcome": {
      "@id": "https://noocodec.dev/ontology/dag/outcome"
    },
    "phase": {
      "@id": "https://noocodec.dev/ontology/dag/phase"
    },
    "stateMapping": {
      "@id": "https://noocodec.dev/ontology/dag/stateMapping"
    },
    "container": {
      "@id": "https://noocodec.dev/ontology/dag/container"
    },
    "DAG": {
      "@id": "https://noocodec.dev/ontology/dag/DAG"
    },
    "Placement": {
      "@id": "https://noocodec.dev/ontology/dag/Placement"
    },
    "SingleNode": {
      "@id": "https://noocodec.dev/ontology/dag/SingleNode"
    },
    "ScatterNode": {
      "@id": "https://noocodec.dev/ontology/dag/ScatterNode"
    },
    "EmbeddedDAGNode": {
      "@id": "https://noocodec.dev/ontology/dag/EmbeddedDAGNode"
    },
    "GatherNode": {
      "@id": "https://noocodec.dev/ontology/dag/GatherNode"
    },
    "TerminalNode": {
      "@id": "https://noocodec.dev/ontology/dag/TerminalNode"
    },
    "PhaseNode": {
      "@id": "https://noocodec.dev/ontology/dag/PhaseNode"
    }
  },
  "@id": "urn:noocodec:dag:insights-summary",
  "@type": "DAG",
  "name": "dag:insights-summary",
  "version": "1.0",
  "entrypoints": {
    "main": "urn:noocodec:dag:insights-summary/node/summarize"
  },
  "nodes": [
    {
      "@id": "urn:noocodec:dag:insights-summary/node/summarize",
      "@type": "SingleNode",
      "name": "dag:insights-summary/node/summarize",
      "node": "urn:noocodec:node:summarize",
      "outputs": {
        "success": "urn:noocodec:dag:insights-summary/node/done"
      }
    },
    {
      "@id": "urn:noocodec:dag:insights-summary/node/done",
      "@type": "TerminalNode",
      "name": "dag:insights-summary/node/done",
      "outcome": "completed"
    }
  ]
}
Mermaid generated from the same DAG
Mermaid source
%%{init: {"flowchart":{"nodeSpacing":92,"rankSpacing":104,"padding":28}}}%%
flowchart TB
  %% dag:insights-summary (v1.0)
  entry_main(["main"])
  entry_main --> urn_noocodec_dag_insights-summary/node/summarize
  urn_noocodec_dag_insights-summary/node/summarize["dag:insights-summary/node/summarize"]
  urn_noocodec_dag_insights-summary/node/summarize -->|success| urn_noocodec_dag_insights-summary/node/done
  urn_noocodec_dag_insights-summary/node/done((("dag:insights-summary/node/done")))
ts
/**
 * insights-summary: embedded summary body for the browser workers topology.
 *
 * The top-level workers DAG delegates this single-cardinality stage to the
 * `io` container role after the `cpu` scatter finishes. The body is the same
 * summarizeInsights node used by the in-process cartographer DAG, packaged as a
 * registered DAG so the worker registry and JSON-LD assembly use the same
 * canonical embed/plugin surface.
 */
export const insightsSummaryDAG: DAGType = new DAGBuilder(INSIGHTS_SUMMARY_DAG_IRI, '1.0')
  .node(CARTOGRAPHER_IRIS.placementIri(INSIGHTS_SUMMARY_DAG_IRI, 'summarize'), summarizeInsights, {
    'success': CARTOGRAPHER_IRIS.placementIri(INSIGHTS_SUMMARY_DAG_IRI, 'done'),
  })
  .terminal(CARTOGRAPHER_IRIS.placementIri(INSIGHTS_SUMMARY_DAG_IRI, 'done'), { outcome: 'completed' })
  .build();

What It Lets You Do

Multi-backend roles let applications bind different parts of one DAG to different execution backends without changing the canonical graph. Use this when CPU-bound stream processing, IO-bound summary work, and in-process orchestration need separate pools, quotas, or deployment targets.

Code Samples

Read the snippets with the diagrams nearby so the TypeScript behavior, JSON-LD graph shape, and runtime output line up as one contract.

Browser role binding

The runnable page creates two role bindings from the same registry-backed worker entry. The registry contains every DAG the worker can execute: the stream-event tree for cpu and the insights-summary DAG for io.

vue
// Separate role bindings use the same worker entry and registry while still
// keeping the DAG's execution boundaries explicit: process-stream runs on
// `cpu`, summarize-insights runs on `io`.
const cpuContainer = new CartographerWorkerContainer({
  'registryModule':  new URL('./cartographerWorkerEntry.ts', import.meta.url).href,
  'registryVersion': '1.0.0',
  'servicesConfig':  { 'useRecordedIp': true },
  'poolSize':        clampedPoolSize.value,
});
const ioContainer = new CartographerWorkerContainer({
  'registryModule':  new URL('./cartographerWorkerEntry.ts', import.meta.url).href,
  'registryVersion': '1.0.0',
  'servicesConfig':  { 'useRecordedIp': true },
  'poolSize':        1,
});

dispatcher = new CartographerBrowserObserver(_cartographerLogger, {
  'containers': {
    'cpu': cpuContainer,
    'io':  ioContainer,
  },
});
ts
const registry: RegistryModuleInterface = {
  async instantiate(servicesConfig: JsonObjectType): Promise<RegistryBundleInterface> {
    const useRecorded = servicesConfig['useRecordedIp'] !== false;
    const services = useRecorded ? GeoResolvers.recorded() : GeoResolvers.live();
    const geoBundle = GeoSourceResolveDAG.build(services.ipGeolocator, services.addressGeocoder);
    return {
      'bundle': {
        'nodes': [...geoBundle.nodes, ...cartographerWorkerRuntimeBundle.nodes],
        'dags':  [...geoBundle.dags,  ...cartographerWorkerRuntimeBundle.dags],
      },
      'registryVersion': '1.0.0',
      'restoreState': {
        restore(snapshot: JsonObjectType) {
          return CartographerState.restore(snapshot);
        },
      },
    };
  },
};

export default registry;

Details for Nerds

Node CLI companion

The repository also keeps examples/13-multibackend.ts as a Node CLI companion for worker-thread plus fork-container execution:

bash
pnpm example:13

That CLI exercises the same renderer role-color path, but the browser runnable for this principle is the Cartographer code above.

  • Role names preserve portability. cpu and io are deployment labels, not new placement types.
  • Multiple backends share one registry interface. The worker registry contains every DAG either role can execute.
  • JSON-LD remains canonical. Container delegation is a placement attribute in the same document the builder emits and the dispatcher consumes.
  • Browser and Node hosts choose different backends. The docs runnable uses WebWorkerContainer; the CLI companion can exercise Node container implementations.

Watched over by the Order of Dagon.