Skip to content

See your agents call each other

Once you have a @Coordinator with three or four crew members, the question “which agent is doing the heavy lifting?” stops being answerable from logs. You need a graph. Atmosphere has one.

Every @Coordinator writes to CoordinationJournal. The journal is the same event stream your durable HITL + audit surfaces read. The admin plane exposes two REST endpoints that turn the journal into a graph payload your UI can render.

ceoresearchfinance11/12 · 842ms6/6 · 304ms

ceo dispatched research twelve times (eleven successful, averaging 842 ms) and finance six times (all successful, averaging 304 ms). Exactly what the two endpoints below return in JSON.

Render the entire journal as a flow graph. Optional lookbackMinutes=N constrains the window; omit for “all recorded history”.

Terminal window
curl http://localhost:8080/api/admin/flow?lookbackMinutes=60
{
"nodes": [
{"id": "ceo", "label": "ceo"},
{"id": "research-agent", "label": "research-agent"},
{"id": "finance-agent", "label": "finance-agent"}
],
"edges": [
{
"from": "ceo",
"to": "research-agent",
"dispatches": 12,
"successes": 11,
"failures": 1,
"averageDurationMs": 842
},
{
"from": "ceo",
"to": "finance-agent",
"dispatches": 6,
"successes": 6,
"failures": 0,
"averageDurationMs": 304
}
]
}

The shape is deliberately framework-agnostic — feed it to any graph library (vis.js, Cytoscape, D3 force-directed, React Flow) without post-processing.

Scope the graph to a single coordination run — the natural target for a drilldown when a user clicks a run in the audit log or admin dashboard.

Minimal React + React Flow skeleton:

import ReactFlow from 'reactflow';
import { useEffect, useState } from 'react';
export function CoordinatorFlow() {
const [data, setData] = useState({ nodes: [], edges: [] });
useEffect(() => {
const load = () => fetch('/api/admin/flow?lookbackMinutes=60')
.then(r => r.json())
.then(setData);
load();
const t = setInterval(load, 5000);
return () => clearInterval(t);
}, []);
const nodes = data.nodes.map((n, i) => ({
id: n.id,
data: { label: n.label },
position: { x: i * 180, y: 120 },
}));
const edges = data.edges.map((e, i) => ({
id: `e-${i}`,
source: e.from,
target: e.to,
label: `${e.successes}/${e.dispatches} ${e.averageDurationMs}ms`,
style: { stroke: e.failures > 0 ? '#d33' : '#666' },
}));
return <ReactFlow nodes={nodes} edges={edges} fitView />;
}

Under 60 lines and you have a live coordinator dashboard.

  • dispatches — raw dispatch count from parent → child.
  • successesAgentCompleted events observed for the edge.
  • failuresAgentFailed events. Edge turns red when non-zero.
  • averageDurationMs — mean Duration across successful runs. Sudden regressions here are the first signal of a degraded agent.

If successes + failures < dispatches, the remaining calls are in-flight — useful for surfacing stuck agents.

/api/admin/flow and /api/admin/flow/{coordinationId} are read endpoints. Default posture leaves them anonymous so a local demo console works without credentials.

Write endpoints (broadcast / disconnect / cancel) have their own triple-gate (feature flag → Principal → ControlAuthorizer) — see modules/admin/README.md for the full wiring.

Three possibilities:

  1. No @Coordinator on the classpath. The flow endpoint returns {"nodes": [], "edges": []} — not an error.
  2. No journal installed. Spring Boot auto-config wires the journal when atmosphere-coordinator is on the classpath; plain servlet users install via ServiceLoader or by writing to CoordinatorProcessor.COORDINATION_JOURNAL_PROPERTY.
  3. lookbackMinutes is too tight. Drop the parameter to see everything the journal has retained.

Dynatrace 2026 Agentic AI report

Calls out 44% of respondents reviewing agent-to-agent flows manually because the data is unstructured. This endpoint is the fix.