Skip to content

Chat Event Orchestration

What It Is

Chat event orchestration is the host pattern for agent applications: register the DAG once, then run a fresh execution for every inbound message, queue event, browser action, or HTTP request turn.

The DAG remains the canonical assembly. DAGBuilder emits your graph as JSON-LD, Dagonizer registers it, DagRunner seeds and projects state, and EventTrigger or RequestTrigger adapts the outside world into runner.run(...).

How It Works

The host owns transport concerns. The DAG owns control flow. Each inbound event becomes:

  1. A raw host message or request.
  2. A typed runner input.
  3. A fresh state instance with correlation data, user text, and injected context.
  4. One dispatcher.execute(dagIri, state, options) call through DagRunner.
  5. A projected output returned to the host, published to a socket, or written to a stream.

Long-lived subscriptions use EventTrigger. Request/response APIs use RequestTrigger. Both surfaces run the same registered DAG; they differ only in how the host decides when to fire.

Diagrams, Examples, and Outputs

The inner loop is a ReAct-style agent DAG authored with plain DAGBuilder: build request, call model, normalize the response, dispatch tools through scatter, collect observations, and loop until the assistant response is final.

ts
/**
 * 29-agent-dag/dags: explicit DAGBuilder 8-node agent loop.
 *
 * The exported `dag` is a JSON-LD document built from the same DAGBuilder
 * surface applications use for custom graph authoring. Runnable examples
 * provide concrete node instances with the same names and register this
 * topology by name.
 */

import { DAGBuilder, DAGIdentity, PlaceholderNode } from '@studnicky/dagonizer';
import type { DAGType, NodeStateInterface } from '@studnicky/dagonizer';

export const dagIri = 'urn:noocodec:dag:my-agent' as const;
const placement = (placementIdentifier: string): string => DAGIdentity.placementId(dagIri, placementIdentifier);

const topologyNodes = {
  'chatRequest':         new PlaceholderNode<NodeStateInterface, 'ready' | 'error'>('urn:noocodec:node:build-request', ['ready', 'error']),
  'callModel':           new PlaceholderNode<NodeStateInterface, 'text' | 'tools' | 'mixed' | 'error'>('urn:noocodec:node:call-model', ['text', 'tools', 'mixed', 'error']),
  'normalizeResponse':   new PlaceholderNode<NodeStateInterface, 'text' | 'tools' | 'mixed' | 'empty' | 'error'>('urn:noocodec:node:normalize-response', ['text', 'tools', 'mixed', 'empty', 'error']),
  'decodeTextToolCalls': new PlaceholderNode<NodeStateInterface, 'decoded' | 'empty' | 'error'>('urn:noocodec:node:decode-tools', ['decoded', 'empty', 'error']),
  'normalizeToolCalls':  new PlaceholderNode<NodeStateInterface, 'valid' | 'empty' | 'error'>('urn:noocodec:node:normalize-tools', ['valid', 'empty', 'error']),
  'toolWorksets':        new PlaceholderNode<NodeStateInterface, 'ready' | 'empty' | 'error'>('urn:noocodec:node:build-worksets', ['ready', 'empty', 'error']),
  'collectToolResults':  new PlaceholderNode<NodeStateInterface, 'done' | 'empty' | 'error'>('urn:noocodec:node:collect-results', ['done', 'empty', 'error']),
  'appendAssistant':     new PlaceholderNode<NodeStateInterface, 'done' | 'error'>('urn:noocodec:node:append-assistant', ['done', 'error']),
};

export const dag: DAGType = new DAGBuilder(dagIri, '1')
  .node(placement('build-request'), topologyNodes.chatRequest, {
    'ready': placement('call-model'),
    'error': placement('end-error'),
  })
  .node(placement('call-model'), topologyNodes.callModel, {
    'text':  placement('normalize-response'),
    'tools': placement('normalize-response'),
    'mixed': placement('normalize-response'),
    'error': placement('end-error'),
  })
  .node(placement('normalize-response'), topologyNodes.normalizeResponse, {
    'text':  placement('append-assistant'),
    'tools': placement('decode-tools'),
    'mixed': placement('decode-tools'),
    'empty': placement('end-error'),
    'error': placement('end-error'),
  })
  .node(placement('append-assistant'), topologyNodes.appendAssistant, {
    'done':  placement('end-done'),
    'error': placement('end-error'),
  })
  .node(placement('decode-tools'), topologyNodes.decodeTextToolCalls, {
    'decoded': placement('normalize-tools'),
    'empty':   placement('end-error'),
    'error':   placement('end-error'),
  })
  .node(placement('normalize-tools'), topologyNodes.normalizeToolCalls, {
    'valid': placement('worksets'),
    'empty': placement('end-error'),
    'error': placement('end-error'),
  })
  .node(placement('worksets'), topologyNodes.toolWorksets, {
    'ready': placement('dispatch-tools'),
    'empty': placement('end-error'),
    'error': placement('end-error'),
  })
  .scatter(
    placement('dispatch-tools'),
    'safeWorkset',
    { 'dag': { 'from': 'item', 'path': 'dagIri', 'candidates': ['urn:noocodec:tool:calculator'] } },
    {
      'all-success': placement('join-tool-results'),
      'partial':     placement('join-tool-results'),
      'all-error':   placement('join-tool-results'),
      'empty':       placement('join-tool-results'),
    },
    {
      'itemKey': 'currentItem',
    },
  )
  .gather(placement('join-tool-results'), { [placement('dispatch-tools')]: {} }, {
    'strategy': 'map',
    'mapping':  { 'output': 'toolOutputs' },
  }, {
    'success': placement('collect-results'),
    'error':   placement('end-error'),
    'empty':   placement('collect-results'),
  })
  .node(placement('collect-results'), topologyNodes.collectToolResults, {
    'done':  placement('build-request'),
    'empty': placement('build-request'),
    'error': placement('end-error'),
  })
  .terminal(placement('end-done'))
  .terminal(placement('end-error'), { 'outcome': 'failed' })
  .build();

Agent event loop DAG

12 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:react-agent",
  "@type": "DAG",
  "name": "react-agent",
  "version": "1",
  "entrypoints": {
    "main": "urn:noocodec:dag:react-agent/node/build-request"
  },
  "nodes": [
    {
      "@id": "urn:noocodec:dag:react-agent/node/build-request",
      "@type": "SingleNode",
      "name": "build-request",
      "node": "urn:noocodec:node:build-request",
      "outputs": {
        "ready": "urn:noocodec:dag:react-agent/node/call-model",
        "error": "urn:noocodec:dag:react-agent/node/end-error"
      }
    },
    {
      "@id": "urn:noocodec:dag:react-agent/node/call-model",
      "@type": "SingleNode",
      "name": "call-model",
      "node": "urn:noocodec:node:call-model",
      "outputs": {
        "text": "urn:noocodec:dag:react-agent/node/normalize-response",
        "tools": "urn:noocodec:dag:react-agent/node/normalize-response",
        "mixed": "urn:noocodec:dag:react-agent/node/normalize-response",
        "error": "urn:noocodec:dag:react-agent/node/end-error"
      }
    },
    {
      "@id": "urn:noocodec:dag:react-agent/node/normalize-response",
      "@type": "SingleNode",
      "name": "normalize-response",
      "node": "urn:noocodec:node:normalize-response",
      "outputs": {
        "text": "urn:noocodec:dag:react-agent/node/append-assistant",
        "tools": "urn:noocodec:dag:react-agent/node/decode-tools",
        "mixed": "urn:noocodec:dag:react-agent/node/decode-tools",
        "empty": "urn:noocodec:dag:react-agent/node/end-error",
        "error": "urn:noocodec:dag:react-agent/node/end-error"
      }
    },
    {
      "@id": "urn:noocodec:dag:react-agent/node/append-assistant",
      "@type": "SingleNode",
      "name": "append-assistant",
      "node": "urn:noocodec:node:append-assistant",
      "outputs": {
        "done": "urn:noocodec:dag:react-agent/node/end-done",
        "error": "urn:noocodec:dag:react-agent/node/end-error"
      }
    },
    {
      "@id": "urn:noocodec:dag:react-agent/node/decode-tools",
      "@type": "SingleNode",
      "name": "decode-tools",
      "node": "urn:noocodec:node:decode-tools",
      "outputs": {
        "decoded": "urn:noocodec:dag:react-agent/node/normalize-tools",
        "empty": "urn:noocodec:dag:react-agent/node/end-error",
        "error": "urn:noocodec:dag:react-agent/node/end-error"
      }
    },
    {
      "@id": "urn:noocodec:dag:react-agent/node/normalize-tools",
      "@type": "SingleNode",
      "name": "normalize-tools",
      "node": "urn:noocodec:node:normalize-tools",
      "outputs": {
        "valid": "urn:noocodec:dag:react-agent/node/worksets",
        "empty": "urn:noocodec:dag:react-agent/node/end-error",
        "error": "urn:noocodec:dag:react-agent/node/end-error"
      }
    },
    {
      "@id": "urn:noocodec:dag:react-agent/node/worksets",
      "@type": "SingleNode",
      "name": "worksets",
      "node": "urn:noocodec:node:build-worksets",
      "outputs": {
        "ready": "urn:noocodec:dag:react-agent/node/dispatch-tools",
        "empty": "urn:noocodec:dag:react-agent/node/end-error",
        "error": "urn:noocodec:dag:react-agent/node/end-error"
      }
    },
    {
      "@id": "urn:noocodec:dag:react-agent/node/dispatch-tools",
      "@type": "ScatterNode",
      "name": "dispatch-tools",
      "source": "safeWorkset",
      "body": {
        "dag": {
          "@type": "DagReference",
          "from": "item",
          "path": "dagIri",
          "candidates": [
            "urn:noocodec:tool:lookup"
          ]
        }
      },
      "outputs": {
        "all-success": "urn:noocodec:dag:react-agent/node/join-tool-results",
        "partial": "urn:noocodec:dag:react-agent/node/join-tool-results",
        "all-error": "urn:noocodec:dag:react-agent/node/join-tool-results",
        "empty": "urn:noocodec:dag:react-agent/node/join-tool-results"
      },
      "itemKey": "currentItem",
      "reducer": "aggregate"
    },
    {
      "@id": "urn:noocodec:dag:react-agent/node/join-tool-results",
      "@type": "GatherNode",
      "name": "join-tool-results",
      "sources": {
        "urn:noocodec:dag:react-agent/node/dispatch-tools": {}
      },
      "gather": {
        "strategy": "map",
        "mapping": {
          "output": "toolOutputs"
        }
      },
      "outputs": {
        "success": "urn:noocodec:dag:react-agent/node/collect-results",
        "error": "urn:noocodec:dag:react-agent/node/end-error",
        "empty": "urn:noocodec:dag:react-agent/node/collect-results"
      }
    },
    {
      "@id": "urn:noocodec:dag:react-agent/node/collect-results",
      "@type": "SingleNode",
      "name": "collect-results",
      "node": "urn:noocodec:node:collect-results",
      "outputs": {
        "done": "urn:noocodec:dag:react-agent/node/build-request",
        "empty": "urn:noocodec:dag:react-agent/node/build-request",
        "error": "urn:noocodec:dag:react-agent/node/end-error"
      }
    },
    {
      "@id": "urn:noocodec:dag:react-agent/node/end-done",
      "@type": "TerminalNode",
      "name": "end-done",
      "outcome": "completed"
    },
    {
      "@id": "urn:noocodec:dag:react-agent/node/end-error",
      "@type": "TerminalNode",
      "name": "end-error",
      "outcome": "failed"
    }
  ]
}
Mermaid generated from the same DAG
Mermaid source
%%{init: {"flowchart":{"nodeSpacing":92,"rankSpacing":104,"padding":28}}}%%
flowchart TB
  %% react-agent (v1)
  entry_main(["main"])
  entry_main --> urn_noocodec_dag_react-agent/node/build-request
  urn_noocodec_dag_react-agent/node/build-request["build-request"]
  urn_noocodec_dag_react-agent/node/build-request -->|ready| urn_noocodec_dag_react-agent/node/call-model
  urn_noocodec_dag_react-agent/node/build-request -->|error| urn_noocodec_dag_react-agent/node/end-error
  urn_noocodec_dag_react-agent/node/call-model["call-model"]
  urn_noocodec_dag_react-agent/node/call-model -->|text| urn_noocodec_dag_react-agent/node/normalize-response
  urn_noocodec_dag_react-agent/node/call-model -->|tools| urn_noocodec_dag_react-agent/node/normalize-response
  urn_noocodec_dag_react-agent/node/call-model -->|mixed| urn_noocodec_dag_react-agent/node/normalize-response
  urn_noocodec_dag_react-agent/node/call-model -->|error| urn_noocodec_dag_react-agent/node/end-error
  urn_noocodec_dag_react-agent/node/normalize-response["normalize-response"]
  urn_noocodec_dag_react-agent/node/normalize-response -->|text| urn_noocodec_dag_react-agent/node/append-assistant
  urn_noocodec_dag_react-agent/node/normalize-response -->|tools| urn_noocodec_dag_react-agent/node/decode-tools
  urn_noocodec_dag_react-agent/node/normalize-response -->|mixed| urn_noocodec_dag_react-agent/node/decode-tools
  urn_noocodec_dag_react-agent/node/normalize-response -->|empty| urn_noocodec_dag_react-agent/node/end-error
  urn_noocodec_dag_react-agent/node/normalize-response -->|error| urn_noocodec_dag_react-agent/node/end-error
  urn_noocodec_dag_react-agent/node/append-assistant["append-assistant"]
  urn_noocodec_dag_react-agent/node/append-assistant -->|done| urn_noocodec_dag_react-agent/node/end-done
  urn_noocodec_dag_react-agent/node/append-assistant -->|error| urn_noocodec_dag_react-agent/node/end-error
  urn_noocodec_dag_react-agent/node/decode-tools["decode-tools"]
  urn_noocodec_dag_react-agent/node/decode-tools -->|decoded| urn_noocodec_dag_react-agent/node/normalize-tools
  urn_noocodec_dag_react-agent/node/decode-tools -->|empty| urn_noocodec_dag_react-agent/node/end-error
  urn_noocodec_dag_react-agent/node/decode-tools -->|error| urn_noocodec_dag_react-agent/node/end-error
  urn_noocodec_dag_react-agent/node/normalize-tools["normalize-tools"]
  urn_noocodec_dag_react-agent/node/normalize-tools -->|valid| urn_noocodec_dag_react-agent/node/worksets
  urn_noocodec_dag_react-agent/node/normalize-tools -->|empty| urn_noocodec_dag_react-agent/node/end-error
  urn_noocodec_dag_react-agent/node/normalize-tools -->|error| urn_noocodec_dag_react-agent/node/end-error
  urn_noocodec_dag_react-agent/node/worksets["worksets"]
  urn_noocodec_dag_react-agent/node/worksets -->|ready| urn_noocodec_dag_react-agent/node/dispatch-tools
  urn_noocodec_dag_react-agent/node/worksets -->|empty| urn_noocodec_dag_react-agent/node/end-error
  urn_noocodec_dag_react-agent/node/worksets -->|error| urn_noocodec_dag_react-agent/node/end-error
  urn_noocodec_dag_react-agent/node/dispatch-tools[/"dispatch-tools"/]
  urn_noocodec_dag_react-agent/node/dispatch-tools -->|all-success| urn_noocodec_dag_react-agent/node/join-tool-results
  urn_noocodec_dag_react-agent/node/dispatch-tools -->|partial| urn_noocodec_dag_react-agent/node/join-tool-results
  urn_noocodec_dag_react-agent/node/dispatch-tools -->|all-error| urn_noocodec_dag_react-agent/node/join-tool-results
  urn_noocodec_dag_react-agent/node/dispatch-tools -->|empty| urn_noocodec_dag_react-agent/node/join-tool-results
  urn_noocodec_dag_react-agent/node/join-tool-results{"join-tool-results"}
  urn_noocodec_dag_react-agent/node/join-tool-results -->|success| urn_noocodec_dag_react-agent/node/collect-results
  urn_noocodec_dag_react-agent/node/join-tool-results -->|error| urn_noocodec_dag_react-agent/node/end-error
  urn_noocodec_dag_react-agent/node/join-tool-results -->|empty| urn_noocodec_dag_react-agent/node/collect-results
  urn_noocodec_dag_react-agent/node/collect-results["collect-results"]
  urn_noocodec_dag_react-agent/node/collect-results -->|done| urn_noocodec_dag_react-agent/node/build-request
  urn_noocodec_dag_react-agent/node/collect-results -->|empty| urn_noocodec_dag_react-agent/node/build-request
  urn_noocodec_dag_react-agent/node/collect-results -->|error| urn_noocodec_dag_react-agent/node/end-error
  urn_noocodec_dag_react-agent/node/end-done((("end-done")))
  urn_noocodec_dag_react-agent/node/end-error>"end-error"]

The Dispatcher runnable shows how a real in-browser support flow surrounds that style of agent work: classify an inbound message, compose a response or park for an operator, converge on send-response, and end the turn.

ts

const setup           = new PlaceholderNode<DispatcherState, 'ready'>('urn:noocodec:node:dispatcher-setup', ['ready']);
const classifyMessage = new PlaceholderNode<DispatcherState, 'routine' | 'escalate' | 'off-topic'>('urn:noocodec:node:classify-message', ['routine', 'escalate', 'off-topic']);
const aiCompose       = new PlaceholderNode<DispatcherState, 'drafted'>('urn:noocodec:node:ai-compose', ['drafted']);
const parkForOperator = new PlaceholderNode<DispatcherState, 'parked' | 'ready'>('urn:noocodec:node:park-for-operator', ['parked', 'ready']);
const sendResponse    = new PlaceholderNode<DispatcherState, 'sent'>('urn:noocodec:node:send-response', ['sent']);
const decline         = new PlaceholderNode<DispatcherState, 'declined'>('urn:noocodec:node:decline', ['declined']);

const supportDispatcherDagIri = 'urn:noocodec:dag:support-dispatcher' as const;
const placement = (placementIdentifier: string): string =>
  DAGIdentity.placementId(supportDispatcherDagIri, placementIdentifier);

export const supportDispatcherDAG: DAGType = new DAGBuilder(supportDispatcherDagIri, '1')
  // Pre-phase: stamps runId before the entrypoint runs.
  .phase(placement('setup'), 'pre', setup)

  // Entrypoint: classify the inbound message.
  .node(placement('classify-message'), classifyMessage, {
    'routine':   placement('ai-compose'),
    'escalate':  placement('park-for-operator'),
    'off-topic': placement('decline'),
  })

  // Routine branch: AI composes a canned reply -> send -> done.
  .node(placement('ai-compose'), aiCompose, {
    'drafted': placement('send-response'),
  })

  // Escalation branch: HITL suspension point.
  .node(placement('park-for-operator'), parkForOperator, {
    'parked': placement('end'),
    'ready':  placement('send-response'),
  })

  // Shared convergence: both routine and escalated paths flow through send-response.
  .node(placement('send-response'), sendResponse, {
    'sent': placement('end'),
  })

  // Off-topic branch: decline and close.
  .node(placement('decline'), decline, {
    'declined': placement('end'),
  })

  .terminal(placement('end'), { 'outcome': 'completed' })

  .build();

Dispatcher support conversation DAG

7 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:support-dispatcher",
  "@type": "DAG",
  "name": "dag:support-dispatcher",
  "version": "1",
  "entrypoints": {
    "main": "urn:noocodec:dag:support-dispatcher/node/classify-message"
  },
  "nodes": [
    {
      "@id": "urn:noocodec:dag:support-dispatcher/node/setup",
      "@type": "PhaseNode",
      "name": "dag:support-dispatcher/node/setup",
      "node": "urn:noocodec:node:dispatcher-setup",
      "phase": "pre"
    },
    {
      "@id": "urn:noocodec:dag:support-dispatcher/node/classify-message",
      "@type": "SingleNode",
      "name": "dag:support-dispatcher/node/classify-message",
      "node": "urn:noocodec:node:classify-message",
      "outputs": {
        "routine": "urn:noocodec:dag:support-dispatcher/node/ai-compose",
        "escalate": "urn:noocodec:dag:support-dispatcher/node/park-for-operator",
        "off-topic": "urn:noocodec:dag:support-dispatcher/node/decline"
      }
    },
    {
      "@id": "urn:noocodec:dag:support-dispatcher/node/ai-compose",
      "@type": "SingleNode",
      "name": "dag:support-dispatcher/node/ai-compose",
      "node": "urn:noocodec:node:ai-compose",
      "outputs": {
        "drafted": "urn:noocodec:dag:support-dispatcher/node/send-response"
      }
    },
    {
      "@id": "urn:noocodec:dag:support-dispatcher/node/park-for-operator",
      "@type": "SingleNode",
      "name": "dag:support-dispatcher/node/park-for-operator",
      "node": "urn:noocodec:node:park-for-operator",
      "outputs": {
        "parked": "urn:noocodec:dag:support-dispatcher/node/end",
        "ready": "urn:noocodec:dag:support-dispatcher/node/send-response"
      }
    },
    {
      "@id": "urn:noocodec:dag:support-dispatcher/node/send-response",
      "@type": "SingleNode",
      "name": "dag:support-dispatcher/node/send-response",
      "node": "urn:noocodec:node:send-response",
      "outputs": {
        "sent": "urn:noocodec:dag:support-dispatcher/node/end"
      }
    },
    {
      "@id": "urn:noocodec:dag:support-dispatcher/node/decline",
      "@type": "SingleNode",
      "name": "dag:support-dispatcher/node/decline",
      "node": "urn:noocodec:node:decline",
      "outputs": {
        "declined": "urn:noocodec:dag:support-dispatcher/node/end"
      }
    },
    {
      "@id": "urn:noocodec:dag:support-dispatcher/node/end",
      "@type": "TerminalNode",
      "name": "dag:support-dispatcher/node/end",
      "outcome": "completed"
    }
  ]
}
Mermaid generated from the same DAG
Mermaid source
%%{init: {"flowchart":{"nodeSpacing":92,"rankSpacing":104,"padding":28}}}%%
flowchart TB
  %% dag:support-dispatcher (v1)
  entry_main(["main"])
  entry_main --> urn_noocodec_dag_support-dispatcher/node/classify-message
  urn_noocodec_dag_support-dispatcher/node/setup(["dag:support-dispatcher/node/setup (pre)"])
  urn_noocodec_dag_support-dispatcher/node/classify-message["dag:support-dispatcher/node/classify-message"]
  urn_noocodec_dag_support-dispatcher/node/classify-message -->|routine| urn_noocodec_dag_support-dispatcher/node/ai-compose
  urn_noocodec_dag_support-dispatcher/node/classify-message -->|escalate| urn_noocodec_dag_support-dispatcher/node/park-for-operator
  urn_noocodec_dag_support-dispatcher/node/classify-message -->|off-topic| urn_noocodec_dag_support-dispatcher/node/decline
  urn_noocodec_dag_support-dispatcher/node/ai-compose["dag:support-dispatcher/node/ai-compose"]
  urn_noocodec_dag_support-dispatcher/node/ai-compose -->|drafted| urn_noocodec_dag_support-dispatcher/node/send-response
  urn_noocodec_dag_support-dispatcher/node/park-for-operator["dag:support-dispatcher/node/park-for-operator"]
  urn_noocodec_dag_support-dispatcher/node/park-for-operator -->|parked| urn_noocodec_dag_support-dispatcher/node/end
  urn_noocodec_dag_support-dispatcher/node/park-for-operator -->|ready| urn_noocodec_dag_support-dispatcher/node/send-response
  urn_noocodec_dag_support-dispatcher/node/send-response["dag:support-dispatcher/node/send-response"]
  urn_noocodec_dag_support-dispatcher/node/send-response -->|sent| urn_noocodec_dag_support-dispatcher/node/end
  urn_noocodec_dag_support-dispatcher/node/decline["dag:support-dispatcher/node/decline"]
  urn_noocodec_dag_support-dispatcher/node/decline -->|declined| urn_noocodec_dag_support-dispatcher/node/end
  urn_noocodec_dag_support-dispatcher/node/end((("dag:support-dispatcher/node/end")))

The ReAct memory and routing examples show the two pieces chat hosts usually need after the first turn: trace persistence and concurrent stream demultiplexing.

ts
export const traceDag: DAGType = {
  '@context':   DAG_CONTEXT,
  '@id': 'urn:noocodec:dag:react-agent-memory-trace',
  '@type':      'DAG',
  'name':       'react-agent-memory-trace',
  'version':    '1',
  'entrypoints': { 'main': 'urn:noocodec:dag:react-agent-memory-trace/node/scatter-steps' },
  'nodes': [
    {
      '@id': 'urn:noocodec:dag:react-agent-memory-trace/node/scatter-steps',
      '@type':       'ScatterNode',
      'name':        'scatter-steps',
      'body':        { 'node': 'urn:noocodec:node:record-step' },
      'source':      'source',
      'itemKey':     REASONING_ITEM_KEY,
      // A free performance choice, not a correctness requirement:
      // RecordReasoningStepNode derives the wasInformedBy chain from each
      // item's own `ordinal` field, so raising this is safe at any value.
      'execution': { 'mode': 'item', 'concurrency': 1 },
      'outputs': {
        'all-success': 'urn:noocodec:dag:react-agent-memory-trace/node/collect-steps',
        'partial': 'urn:noocodec:dag:react-agent-memory-trace/node/collect-steps',
        'all-error': 'urn:noocodec:dag:react-agent-memory-trace/node/collect-steps',
        'empty': 'urn:noocodec:dag:react-agent-memory-trace/node/end',
      },
    },
    {
      '@id': 'urn:noocodec:dag:react-agent-memory-trace/node/collect-steps',
      '@type': 'GatherNode',
      'name': 'collect-steps',
      sources: { 'urn:noocodec:dag:react-agent-memory-trace/node/scatter-steps': {} },
      'gather': { 'strategy': 'append', 'target': 'steps' },
      'outputs': {
        'success': 'urn:noocodec:dag:react-agent-memory-trace/node/end',
        'error': 'urn:noocodec:dag:react-agent-memory-trace/node/end',
        'empty': 'urn:noocodec:dag:react-agent-memory-trace/node/end',
      },
    },
    {
      '@id': 'urn:noocodec:dag:react-agent-memory-trace/node/end',
      '@type':   'TerminalNode',
      'name':    'end',
      'outcome': 'completed',
    },
  ],
};

Reasoning trace memory DAG

3 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:react-agent-memory-trace",
  "@type": "DAG",
  "name": "react-agent-memory-trace",
  "version": "1",
  "entrypoints": {
    "main": "urn:noocodec:dag:react-agent-memory-trace/node/scatter-steps"
  },
  "nodes": [
    {
      "@id": "urn:noocodec:dag:react-agent-memory-trace/node/scatter-steps",
      "@type": "ScatterNode",
      "name": "scatter-steps",
      "body": {
        "node": "urn:noocodec:node:record-step"
      },
      "source": "source",
      "itemKey": "reasoning-step-item",
      "execution": {
        "mode": "item",
        "concurrency": 1
      },
      "outputs": {
        "all-success": "urn:noocodec:dag:react-agent-memory-trace/node/collect-steps",
        "partial": "urn:noocodec:dag:react-agent-memory-trace/node/collect-steps",
        "all-error": "urn:noocodec:dag:react-agent-memory-trace/node/collect-steps",
        "empty": "urn:noocodec:dag:react-agent-memory-trace/node/end"
      }
    },
    {
      "@id": "urn:noocodec:dag:react-agent-memory-trace/node/collect-steps",
      "@type": "GatherNode",
      "name": "collect-steps",
      "sources": {
        "urn:noocodec:dag:react-agent-memory-trace/node/scatter-steps": {}
      },
      "gather": {
        "strategy": "append",
        "target": "steps"
      },
      "outputs": {
        "success": "urn:noocodec:dag:react-agent-memory-trace/node/end",
        "error": "urn:noocodec:dag:react-agent-memory-trace/node/end",
        "empty": "urn:noocodec:dag:react-agent-memory-trace/node/end"
      }
    },
    {
      "@id": "urn:noocodec:dag:react-agent-memory-trace/node/end",
      "@type": "TerminalNode",
      "name": "end",
      "outcome": "completed"
    }
  ]
}
Mermaid generated from the same DAG
Mermaid source
%%{init: {"flowchart":{"nodeSpacing":92,"rankSpacing":104,"padding":28}}}%%
flowchart TB
  %% react-agent-memory-trace (v1)
  entry_main(["main"])
  entry_main --> urn_noocodec_dag_react-agent-memory-trace/node/scatter-steps
  urn_noocodec_dag_react-agent-memory-trace/node/scatter-steps[/"scatter-steps"/]
  urn_noocodec_dag_react-agent-memory-trace/node/scatter-steps -->|all-success| urn_noocodec_dag_react-agent-memory-trace/node/collect-steps
  urn_noocodec_dag_react-agent-memory-trace/node/scatter-steps -->|partial| urn_noocodec_dag_react-agent-memory-trace/node/collect-steps
  urn_noocodec_dag_react-agent-memory-trace/node/scatter-steps -->|all-error| urn_noocodec_dag_react-agent-memory-trace/node/collect-steps
  urn_noocodec_dag_react-agent-memory-trace/node/scatter-steps -->|empty| urn_noocodec_dag_react-agent-memory-trace/node/end
  urn_noocodec_dag_react-agent-memory-trace/node/collect-steps{"collect-steps"}
  urn_noocodec_dag_react-agent-memory-trace/node/collect-steps -->|success| urn_noocodec_dag_react-agent-memory-trace/node/end
  urn_noocodec_dag_react-agent-memory-trace/node/collect-steps -->|error| urn_noocodec_dag_react-agent-memory-trace/node/end
  urn_noocodec_dag_react-agent-memory-trace/node/collect-steps -->|empty| urn_noocodec_dag_react-agent-memory-trace/node/end
  urn_noocodec_dag_react-agent-memory-trace/node/end((("end")))
ts
export const routingDag: DAGType = {
  '@context':   DAG_CONTEXT,
  '@id': 'urn:noocodec:dag:react-agent-routing',
  '@type':      'DAG',
  'name':       'react-agent-routing',
  'version':    '1',
  'entrypoints': { 'main': 'urn:noocodec:dag:react-agent-routing/node/scatter-chunks' },
  'nodes': [
    {
      '@id': 'urn:noocodec:dag:react-agent-routing/node/scatter-chunks',
      '@type':       'ScatterNode',
      'name':        'scatter-chunks',
      'body':        { 'node': 'urn:noocodec:node:route-chunk' },
      'source':      'source',
      'itemKey':     ROUTE_ITEM_KEY,
      // Chunks route by their own `routeKey`; TranscriptStore.append is safe
      // under any interleaving (single-threaded event loop), so raising this
      // is a free performance choice, not a correctness requirement.
      'execution': { 'mode': 'item', 'concurrency': 4 },
      'outputs': {
        'all-success': 'urn:noocodec:dag:react-agent-routing/node/collect-chunks',
        'partial': 'urn:noocodec:dag:react-agent-routing/node/collect-chunks',
        'all-error': 'urn:noocodec:dag:react-agent-routing/node/collect-chunks',
        'empty': 'urn:noocodec:dag:react-agent-routing/node/end',
      },
    },
    {
      '@id': 'urn:noocodec:dag:react-agent-routing/node/collect-chunks',
      '@type': 'GatherNode',
      'name': 'collect-chunks',
      sources: { 'urn:noocodec:dag:react-agent-routing/node/scatter-chunks': {} },
      'gather': { 'strategy': 'append', 'target': 'chunks' },
      'outputs': {
        'success': 'urn:noocodec:dag:react-agent-routing/node/end',
        'error': 'urn:noocodec:dag:react-agent-routing/node/end',
        'empty': 'urn:noocodec:dag:react-agent-routing/node/end',
      },
    },
    {
      '@id': 'urn:noocodec:dag:react-agent-routing/node/end',
      '@type':   'TerminalNode',
      'name':    'end',
      'outcome': 'completed',
    },
  ],
};

Routed stream sink DAG

3 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:react-agent-routing",
  "@type": "DAG",
  "name": "react-agent-routing",
  "version": "1",
  "entrypoints": {
    "main": "urn:noocodec:dag:react-agent-routing/node/scatter-chunks"
  },
  "nodes": [
    {
      "@id": "urn:noocodec:dag:react-agent-routing/node/scatter-chunks",
      "@type": "ScatterNode",
      "name": "scatter-chunks",
      "body": {
        "node": "urn:noocodec:node:route-chunk"
      },
      "source": "source",
      "itemKey": "routed-chunk-item",
      "execution": {
        "mode": "item",
        "concurrency": 4
      },
      "outputs": {
        "all-success": "urn:noocodec:dag:react-agent-routing/node/collect-chunks",
        "partial": "urn:noocodec:dag:react-agent-routing/node/collect-chunks",
        "all-error": "urn:noocodec:dag:react-agent-routing/node/collect-chunks",
        "empty": "urn:noocodec:dag:react-agent-routing/node/end"
      }
    },
    {
      "@id": "urn:noocodec:dag:react-agent-routing/node/collect-chunks",
      "@type": "GatherNode",
      "name": "collect-chunks",
      "sources": {
        "urn:noocodec:dag:react-agent-routing/node/scatter-chunks": {}
      },
      "gather": {
        "strategy": "append",
        "target": "chunks"
      },
      "outputs": {
        "success": "urn:noocodec:dag:react-agent-routing/node/end",
        "error": "urn:noocodec:dag:react-agent-routing/node/end",
        "empty": "urn:noocodec:dag:react-agent-routing/node/end"
      }
    },
    {
      "@id": "urn:noocodec:dag:react-agent-routing/node/end",
      "@type": "TerminalNode",
      "name": "end",
      "outcome": "completed"
    }
  ]
}
Mermaid generated from the same DAG
Mermaid source
%%{init: {"flowchart":{"nodeSpacing":92,"rankSpacing":104,"padding":28}}}%%
flowchart TB
  %% react-agent-routing (v1)
  entry_main(["main"])
  entry_main --> urn_noocodec_dag_react-agent-routing/node/scatter-chunks
  urn_noocodec_dag_react-agent-routing/node/scatter-chunks[/"scatter-chunks"/]
  urn_noocodec_dag_react-agent-routing/node/scatter-chunks -->|all-success| urn_noocodec_dag_react-agent-routing/node/collect-chunks
  urn_noocodec_dag_react-agent-routing/node/scatter-chunks -->|partial| urn_noocodec_dag_react-agent-routing/node/collect-chunks
  urn_noocodec_dag_react-agent-routing/node/scatter-chunks -->|all-error| urn_noocodec_dag_react-agent-routing/node/collect-chunks
  urn_noocodec_dag_react-agent-routing/node/scatter-chunks -->|empty| urn_noocodec_dag_react-agent-routing/node/end
  urn_noocodec_dag_react-agent-routing/node/collect-chunks{"collect-chunks"}
  urn_noocodec_dag_react-agent-routing/node/collect-chunks -->|success| urn_noocodec_dag_react-agent-routing/node/end
  urn_noocodec_dag_react-agent-routing/node/collect-chunks -->|error| urn_noocodec_dag_react-agent-routing/node/end
  urn_noocodec_dag_react-agent-routing/node/collect-chunks -->|empty| urn_noocodec_dag_react-agent-routing/node/end
  urn_noocodec_dag_react-agent-routing/node/end((("end")))

What It Lets You Do

Use this pattern when an app needs to accept chat events from several host shapes without making the DAG depend on sockets, HTTP frameworks, queues, or UI components.

The same agent graph can serve:

  • A browser demo button through a request-like trigger.
  • A WebSocket or event-bus subscription through EventTrigger.
  • An HTTP route through RequestTrigger.
  • A CLI or worker command through the same DagRunner subclass.
  • Concurrent conversations through per-run conversationId or routeKey fields on state.

Code Samples

Author the agent DAG

The framework does not own a prebuilt agent loop. Use DAGBuilder to author the graph your application needs, then register concrete node subclasses referenced by that JSON-LD. Core gives you the assembly language (DAGBuilder and JSON-LD), the registry, and the host trigger surfaces; your application owns the topology.

Example 29: Agent DAG with JSON-LD is the runnable reference for this section. It shows the full agent loop, explicit placement IRIs, the first-class GatherNode after tool scatter, and the registered tool DAG candidates. The diagram above is generated from that same source, so the topology and prose share one grimoire instead of a hand-copied spell.

The tool scatter reads safeWorkset, dispatches each item through a dynamic DagReference, gathers output into toolOutputs via a GatherNode, and loops through collect-results -> build-request. State field names are application-owned; DAG and placement identity stays IRI-owned.

Put host code behind DagRunner

DagRunner is the only place the host converts inbound input into state and final state into output.

typescript
import { DagRunner } from '@studnicky/dagonizer/runner';
import type { ExecutionResultType } from '@studnicky/dagonizer';

type ChatTurnInput = {
  conversationId: string;
  message: string;
};

type ChatTurnOutput = {
  conversationId: string;
  reply: string;
  completed: boolean;
};

class ChatTurnRunner extends DagRunner<ChatTurnInput, AgentState, ChatTurnOutput> {
  protected override seedState(input: ChatTurnInput): AgentState {
    const state = new AgentState();
    state.conversationId = input.conversationId;
    state.prompt = input.message;
    return state;
  }

  protected override projectResult(result: ExecutionResultType<AgentState>): ChatTurnOutput {
    return {
      conversationId: result.state.conversationId,
      reply: result.state.assistantText,
      completed: result.state.lifecycle.variant === 'completed',
    };
  }
}

The runner is deliberately boring. Keep authentication, sockets, framework request objects, and UI handles outside the DAG. Put only serializable per-run facts on state.

Fire one run per subscription event

Use EventTrigger when the host is a subscription: WebSocket messages, queue items, DOM events, app-level event buses, or server push.

typescript
import { EventTrigger } from '@studnicky/dagonizer/runner';

type InboundChatMessage = {
  conversationId: string;
  body: string;
};

class ChatMessageTrigger extends EventTrigger<InboundChatMessage, ChatTurnInput, AgentState, ChatTurnOutput> {
  constructor(private readonly bus: ChatBus) {
    super();
  }

  protected override subscribe(onMessage: (message: InboundChatMessage) => void): () => void {
    return this.bus.subscribe('message', onMessage);
  }

  protected override toInput(message: InboundChatMessage): ChatTurnInput {
    return {
      conversationId: message.conversationId,
      message: message.body,
    };
  }

  protected override selectDag(_message: InboundChatMessage): string {
    return 'workflow:support-agent';
  }
}

EventTrigger intentionally does not await each run inside the subscription handler. That makes inbound events independent. If the host needs strict per-conversation ordering, enforce it in the host queue keyed by conversationId, not by sharing mutable node state.

Fire one run per request turn

Use RequestTrigger when the caller expects a returned value for the current request.

typescript
import { RequestTrigger } from '@studnicky/dagonizer/runner';
import type { ExecuteOptionsType } from '@studnicky/dagonizer';

type ChatHttpRequest = {
  body: { conversationId: string; message: string };
  signal: AbortSignal;
};

class ChatRequestTrigger extends RequestTrigger<ChatHttpRequest, ChatTurnInput, AgentState, ChatTurnOutput> {
  protected override toInput(request: ChatHttpRequest): ChatTurnInput {
    return {
      conversationId: request.body.conversationId,
      message: request.body.message,
    };
  }

  protected override selectDag(_request: ChatHttpRequest): string {
    return 'workflow:support-agent';
  }

  protected override requestOptions(request: ChatHttpRequest): ExecuteOptionsType {
    return {
      signal: request.signal,
      deadlineMs: 30_000,
    };
  }
}

RequestTrigger.fire(request) returns the runner's projected output. This is the right fit for browser demos, server routes, chat webhooks, and turn-based APIs.

Details for Nerds

Event trigger vs. request trigger

Host shapeTriggerExecution modelOutput path
WebSocket, queue, app event busEventTriggerSubscribes once, fires parallel runner.run(...) calls per messageHost publishes projected output asynchronously
HTTP route, webhook, browser actionRequestTriggerStores the runner on attach, fires when the route calls fire(request)fire(...) resolves to the projected output
CLI or one-shot workerCliTrigger / OnceTriggerFires onceTrigger stores the result after attach

The DAG should not care which trigger invokes it. If the topology changes when the transport changes, the app is mixing host concerns into workflow concerns.

Correlation and stream routing

Every concurrent chat run needs a stable key on state: conversationId, runId, tenant id, or route key. Nodes read that key when they emit progress, stream model tokens, or write trace records.

The ReAct routing example demonstrates the sink side. One shared stream sink receives chunks from multiple conversations, and the routing DAG scatters over those chunks by routeKey so the transcripts do not bleed together.

Abort and deadline propagation

RequestTrigger.requestOptions(request) is the host boundary for cancellation. Forward the request's AbortSignal and set a deadline there. The engine passes that signal through node context and embedded/scatter execution, so nodes and adapters can stop work without importing the host framework.

Registration checklist

  • Register every concrete node instance once on the dispatcher.
  • Register tool DAGs or plugin bundles before the agent DAG if dispatch-tools resolves them from an item field such as dagIri; the field value is a DAG IRI/CURIE.
  • Return the DAG IRI from selectDag; the TypeScript parameter is named dagName, but registry lookup expands it as an IRI/CURIE.
  • Seed a fresh state per event or request; do not reuse a state object across concurrent runs.
  • Put per-run ids on state and use them for stream routing, trace persistence, and response correlation.

Watched over by the Order of Dagon.