A single agent runs in a straight line
Most multi-step agents are a straight line. Step one, step two, step three, each waiting politely for the last to finish before it starts. It runs. It also runs slowly, and it forgets: one head, one context window, one thing at a time, until the window fills and the agent loses the thread of what it was doing.
The tell is that half those steps never needed to wait. They do not route, they do not branch, they do not run at the same time. They queue, because a line is the shape that matches how we type, not the shape of the work.
The work has a shape of its own. What runs before what, what can run together, what has to wait for everything else. That shape is a graph. The move this piece is about is drawing it on purpose, then running it wide.
The line
One agent, one line.
Four steps, each waiting for the last. It is correct, and it is slow: some of these edges carry no data at all. They are just the order you happened to type things in.
The shape is fifty years old. The author is new.
Be honest about what is new here, because a senior reader will check. Shaping work as a graph of jobs joined by their data dependencies is not an invention. It is dataflow programming from 1974, MapReduce from 2004, and Pregel's round-based graph convergence from 2010. Every DAG workflow engine since, Airflow, Dagster, Prefect, Temporal, is a version of it.
The agent-specific version was named before the current tooling too. Anthropic's "Building Effective Agents" laid out routing, parallelization, orchestrator-workers, and evaluator-optimizer a full year before a model could write the orchestration for you. "Graph engineering" is a newer, louder label, and a contested one. Treat it as a lens, not a discovery.
What is genuinely new is narrow and worth stating precisely. A model now writes the orchestration itself, as a plain, rerunnable script, and the coordination logic in that script is ordinary code. That one shift is what makes the graph cheap to draw and cheaper to change. The rest of this piece is the shapes worth drawing, and the honest cost of running them.
Nodes are jobs. Edges are what flows.
A graph has two things, and getting them straight removes most of the confusion. A node is one unit of work: one agent, one bounded job, one input in and one output out. An edge is a dependency: this node's output feeds that node's input. That is all an edge is.
The mistake is treating "and then" as an edge. "Summarize the file, and then tell me the weather" has no edge between the two, because the weather does not read the summary. Those are two independent nodes a linear script chains for no reason. The edge exists only when data actually moves across it.
So for every "and then" in an agent, ask one question: does the next step read the last step's output? If not, there is no edge, and the wait is wasted. That question is the whole method, and the rest is learning to exploit the independence it finds.
The weather does not read the summary. These are two independent nodes a line chains for nothing. Fan them out and they finish at the same time.
Give every node a contract
A node you cannot reason about is a node you cannot parallelize. The fix is a contract: bounded input, bounded output, one job. The input is passed in explicitly, never assumed from a shared window. The output is a defined shape, validated, so the next node consumes it without guessing.
In a workflow that contract is a schema. Hand a node a JSON schema and the subagent is forced to return validated structured data. The validation happens at the tool-call layer, so a mismatch is retried, not handed back as free text you have to parse and hope over. That is the line between a node you can wire into a graph and one that only works when a human reads its output.
"research one source"One bounded job. A node that does three things cannot be reasoned about, reused, or safely run next to others.
source.promptPassed in explicitly. A node never reaches into a shared window for its input; everything it needs crosses an edge.
ITEM // { title, url, impact }The output contract. The subagent is forced to return this shape, and a mismatch is retried at the tool-call layer, not handed back as free text.
{ title, url, impact: "high" }A validated shape the next node consumes without guessing or parsing. This is what makes the edge trustworthy.
"cheaper for a bounded job"Set per node. Repetitive extraction runs on a small model; the judgment nodes keep the expensive one.
"worktree" // only if it writesA node that edits files in parallel gets its own git worktree so two nodes cannot collide. The seatbelt for the one topology that needs it, not a default tax.
Bounded in, validated out, one job. That is what makes a node safe to run next to a hundred others, and what makes its edge trustworthy.
Fan out, then fan in: the diamond
This is the move that pays for everything. When N nodes are independent, N sources to check, N files to review, N routes to audit, you do not chain them. You fan them out and run them at once, then gather the results. One node splits, many nodes work in parallel, one node merges. That split-work-merge shape is the diamond, and it is the workhorse of every serious agent graph.
Two details make it hold. The gather is a barrier: it waits for every branch before the next stage sees the set, which is what a real merge needs. And a branch that fails resolves to nothing rather than sinking the batch, so one flaky node cannot take down the run. You drop the empty results and keep going.
The merge in between is usually not a node at all. Flatten, dedupe, filter: that is plain code operating on the shapes the nodes returned. A large share of what people spend model tokens on is really an edge, and edges are free. Spend agents on judgment, not on plumbing.
// Nine independent sources, nine agents, all at once.
const found = (await parallel(
SOURCES.map((s) => () => agent(s.prompt, { schema: ITEM })),
)).filter(Boolean); // drop the branches that failed
// The merge is an edge, not a node: plain code, zero model tokens.
const items = found.flatMap((r) => r.items);Route the edge at runtime
Not every graph is fixed. Sometimes the edge to take depends on what a node found. A router inspects a result and decides which downstream path fires: classify the input, then branch to the right handler; check the size of a change, then either do a quick pass or a full audit.
The classification can be a model's judgment, but the routing is code. So it runs the same way every time for the same classification. You get the model's judgment at the node and the script's determinism at the edge. There is no emergent "it decided to skip the audit", because a skip would have to be written into the graph, and it is not.
// A node classifies; code picks the edge.
const { risk } = await agent(`Classify this diff:\n${diff}`, { schema: RISK });
const review = risk === "high"
? await parallel(files.map((f) => () => agent(`Audit ${f}`))) // full fan-out
: await agent(`Quick review of ${diff}`); // one passA one-line copy fix in the footer
diff is tiny, low risk
One node reads it and moves on. Spinning up a fleet for a typo pays rent on wiring you do not need.
Verify before it counts
The real gain from a graph is not more agents. It is the structure you can wrap around them to earn confidence. A verifier sits on the edge before a result is allowed downstream, and its one job is to try to kill the finding. If it survives, it passes. If not, it never reaches the answer.
Three shapes are worth having in hand. Adversarial verify: for each finding, spawn independent skeptics prompted to refute it, and keep it only if a majority survive. Perspective-diverse verify: give each verifier a different lens, correctness, security, does-it-reproduce, because diversity catches failure modes that identical checks never will. Judge panel: generate several attempts from different angles, score them with parallel judges, and build the answer from the winner while grafting the best of the rest.
This is the same separation of powers I reach for in a grounded RAG system, where an agent proposes and a verifier and a human decide. The research workflow bundled in Claude Code is that shape run wide: it fans out searches, cross-checks the sources, and marks each claim verified, refuted, or unverified, so a claim it could not check is flagged as unchecked rather than quietly passed. A verifier that says "I could not confirm this" is worth more than one that rubber-stamps.
This route is missing its auth check
Confirmed: the gate is absent before the DB write.
A non-member can reach it. Real.
Reproduced against a foreign workspace.
A loop that converges, not one that runs forever
Sometimes you do not know how big the job is until you are in it: a bug sweep where finding one bug reveals three more. That needs a cycle, an edge back to an earlier node. The danger is obvious. A cycle that does not converge is an infinite loop that spawns agents until the budget is gone.
The shape that converges is loop-until-dry: keep spawning finders until a few rounds in a row surface nothing new, then stop. The one detail that makes or breaks it, and the mistake almost everyone makes the first time, is what you dedupe against. Dedupe against everything seen, not just against what survived. Otherwise rejected findings reappear every round, the loop never runs dry, and you have built a machine that pays to rediscover the same dead ends.
const seen = new Set(); const kept = []; let dry = 0;
while (dry < 2) { // stop after 2 empty rounds
const found = (await parallel(FINDERS.map((f) => () =>
agent(f.prompt, { schema: BUGS })))).filter(Boolean).flatMap((r) => r.bugs);
const fresh = found.filter((b) => !seen.has(key(b)));
if (!fresh.length) { dry++; continue; } // nothing new, toward dry
dry = 0;
fresh.forEach((b) => seen.add(key(b))); // dedupe vs SEEN, not kept
kept.push(...(await verify(fresh)));
}First pass. Five findings, all new. Add them to seen, verify, keep the survivors.
The shape is the cost, and sometimes the cost is a straight line
Topology is not cosmetic. It is the single biggest lever on both wall-clock time and money. A barrier makes everything wait for the slowest node before the next stage starts. A pipeline streams each item through all its stages independently, so a fast item finishes instead of idling behind a slow one. Default to the pipeline; reach for a barrier only when a stage truly needs the whole set at once.
The other lever is the model. Not every node needs the strongest one. Run the bounded, repetitive nodes, extract this field, classify this input, on a cheap model, and keep the expensive tokens for the judgment nodes that adjudicate a finding or write the synthesis. A graph makes that split obvious in a way a single agent never does.
And the honest part. A graph is not always the answer. Tightly coupled work, where every step needs the last step's full context, belongs in one agent; splitting it turns the phases into a game of telephone that degrades at every handoff. A fleet also costs several times to an order of magnitude more tokens than one conversation doing the same task: Anthropic has reported its own multi-agent research system running roughly fifteen times the tokens of a single chat. Fan-out buys thoroughness, not correctness. When the work is a straight line, the straight line is the right architecture.
agent()one nodeparallel()fan out at a barrierpipeline()stream, no barriera schemathe edge contractplain codethe free edgesworktreeparallel writesmodel per nodethe cost leverStop asking the agent to do more. Ask the graph to do it wider.
The linear agent was never the ceiling. It was the first shape, the one everyone reaches for because it matches how we type. Once you can see the nodes and the edges, the question changes. You stop asking one agent to do more steps and start asking where the work splits, where it merges, and where a verifier has to stand before a result counts.
The coordination is code, so drawing the graph is cheap and changing it is cheaper. The fleet still costs real tokens, so you draw it only where the work is actually wide. That is the whole discipline: fan out where the work is independent, gate the edges where confidence matters, tier the models where judgment does not, and keep a straight line where a straight line was always fine.