From Diagram to Execution: Compiling a React Flow Graph into a Runnable Agent Pipeline

8 min readYaseen Khatib · AI Architect

A React Flow canvas is gorgeous and entirely inert. It is a set of nodes and edges with positions. The interesting engineering question is the one the demos skip: how do you actually run it? A freeform graph has no inherent order, may fan out and back in, and — because a user drew it — may contain a cycle that would loop forever. Turning that into a deterministic, runnable pipeline is a compilation problem, and it has a clean, classic solution.

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. Reduce the React Flow state to a dependency map: for every node, the set of nodes that must complete before it can start. That adjacency structure — not the visual graph — is what you compile.

VISUAL GRAPHABCDcompile()↑ cycle? throwEXECUTION PLANstep 1A · Bstep 2Cstep 3D
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 computes it by repeatedly taking nodes with no remaining unmet dependencies. As a bonus, it doubles as your cycle detector — if you finish with nodes still unprocessed, the graph contains a cycle, and you refuse to run rather than hang.

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. You get correct concurrency not by hand-annotating it but by reading it off the structure.

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. The visual graph is for humans; the compiled plan is for the machine, and keeping them separate is what makes the system both editable and reliable.

The canvas is the source; the compiled plan is the program. Topological sort is the compiler — and the same pass that orders your nodes is the one that proves the graph can run at all.

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.

Looking to architect a similar system?

Let's ship it at AI-speed.

Start a conversation →