[ EXECUTIVE TEARDOWN // TL;DR ]
- Running a visual graph is a compilation problem: reduce the canvas to a dependency map, not the rendered nodes.
- Kahn's topological sort produces a deterministic run order and detects cycles in the same pass.
- Nodes at the same dependency depth can run concurrently — parallelism falls out of the structure for free.
- A compiled plan is cacheable, verifiable before execution, and deterministically replayable.
On IntegrateX, a React Flow canvas that looked finished was still entirely inert — a set of nodes and edges with positions. The interesting engineering question is the one the demos skip: how do you actually run it? The pretty graph was useless until we made it deterministic. A user-drawn graph has no inherent order, can fan out and then converge, and it may hide a cycle that will happily spin your workers forever. Turning that into a predictable, runnable pipeline is a compilation problem with a battle‑tested answer.
From render graph to execution plan
First, strip the canvas down to its essence. The executor does not care about positions or selection state; it cares about which capability feeds which. In the pattern I call Trinity Architecture, React Flow lives in the Presentation layer; the Reactive State / Orchestration layer (Zustand for IntegrateX) holds the active graph; and a Data / Serialization Adapter prepares what the runner needs. That adapter reduces rich UI state to a dependency map: for each node, the set of nodes that must finish before it can start. That adjacency — not the visual graph — is what you compile. A bonus from the same adapter on IntegrateX: we stripped UI‑only metadata and cut payload size by 94% before persisting.
Compilation: a topological sort turns a freeform graph into an ordered plan — and rejects cycles before they run.
Topological sort: the order falls out
A workflow graph is a DAG, and a DAG has a topological order: a linear sequence in which every node appears after all of its dependencies. Kahn's algorithm handles this cleanly. I let it pop nodes with no unmet deps, push successors when their indegree drops, and treat any leftovers as a hard error. That same pass becomes the cycle guard — if the processed count doesn't match, we fail fast before allocating a single worker or opening a stream.
compile.ts
// Kahn's algorithm: produces run order AND detects cycles
function compile(graph: RunGraph): Node[] {
const indeg = new Map(graph.nodes.map((n) => [n.id, 0]));
for (const e of graph.edges) indeg.set(e.target, indeg.get(e.target)! + 1);
const ready = graph.nodes.filter((n) => indeg.get(n.id) === 0);
const order: Node[] = [];
while (ready.length) {
const n = ready.shift()!;
order.push(n);
for (const m of successors(graph, n.id)) {
indeg.set(m, indeg.get(m)! - 1);
if (indeg.get(m) === 0) ready.push(byId(graph, m));
}
}
if (order.length !== graph.nodes.length) throw new Error("cycle detected");
return order;
}
Parallelism is free once you have levels
The topological sort gives you more than a line — it gives you levels. Nodes that share the same dependency depth have no ordering constraint between them, which means they can run concurrently. In the diagram, A and B occupy step one and execute in parallel; C waits for both; D waits for C. Read concurrency from the structure and you avoid hand‑tuned queues, head‑of‑line blocking, and the kind of backpressure bugs I fought in streamerOS. Cap parallelism per level, stream results, and you won't thrash renders or starve downstream consumers.
Why compile at all
Compilation is the seam that makes everything downstream tractable. A compiled plan is cacheable, serialisable, and — crucially — verifiable before a single agent runs. You can reject invalid graphs, estimate cost, and replay a run deterministically because the order is fixed. It also enforces the Trinity boundary: UI edits don't leak DB schemas, and the adapter never mutates UI state — it feeds the orchestrator, which drives the runner. The visual graph is for humans; the compiled plan is for the machine, and keeping them separate is what keeps edits fast and execution reliable.
The canvas is how you think; the compiled plan is how you guarantee it runs. Topological sort does both jobs in one pass: it orders the work and proves the graph is runnable.
This is the runtime half of using React Flow as an orchestration canvas. For persisting these graphs without shipping the cache, see the serialization adapter pattern — the adapter layer in my Trinity split that made IntegrateX practical at scale. That seam — a visual builder compiled into a reliable runtime — is the judgment a team gets in an engineer who ships the compiler, not just the canvas.
~/keep-reading
- 7 min readSerialization Adapters: How I Cut Payloads by 94%Rich UI objects make terrible database records. A Serialization Adapter I built for IntegrateX split render model from transport record and cut payloads by 94%.
- 7 min readHow I Run Agents Off a React Flow Canvas, Not a DiagramOn IntegrateX, the React Flow graph a PM drags is the exact spec the runtime executes — typed nodes and ports make it a debuggable agent orchestration layer.
- 8 min readCompressing the Wire: A 94% Payload Reduction in React FlowNode-graph editors serialize enormous JSON. A custom Serialization Adapter pattern that separates the React Flow render model from the transport record cut IntegrateX payloads by 94%.
YK
Yaseen Khatib · AI Architect
Ships autonomous AI products solo — five in the last twelve months. More about Yaseen →
Need an engineer who can build this?
I'm Yaseen Khatib — a Senior Full-Stack AI Engineer (MERN + TypeScript) who ships production AI systems solo. Open to senior and lead roles, remote or on-site.
Get in touch →See what I've shipped
Originally published at yaseenkhatib.streamerosai.com/blog/compiling-react-flow-graph-agent-pipeline/.
Top comments (0)