Graph engineering with Claude Code: the 14-step roadmap I use when linear agents stall
A straight-line agent is a degenerate graph. Claude Code dynamic workflows move orchestration into JavaScript so subagents fan out, verify, and converge without stuffing every intermediate result into one context window. Here is the 14-step roadmap I map onto production work, with topology diagrams, contracts, and the patterns I already ship.
In this post (8 sections)
Introduction
On July 20, 2026 a graph engineering roadmap blew up on X for a reason that has nothing to do with vibes. Teams finally have language for what they feel when a lone Claude session collapses mid-migration: the work has a shape, and a single context window is the wrong container for that shape.
I have been drawing the same boxes on whiteboards for months. Sequential vs parallel. Maker vs verifier. Diamond topologies for research and audits. The one-page visual is now a graph engineering with Claude note (vertical workflow, side-by-side steps, four phases). Claude Code's dynamic workflows are the first product surface that makes those drawings runnable without hand-rolling a custom orchestrator. This post is the long-form behind that map.
What "graph engineering" means here
Not Neo4j. Not knowledge graphs for RAG (different job; see agentic RAG when retrieval is the bottleneck). Graph engineering here means: model the agentic workflow as nodes (jobs) and edges (data dependencies), then execute that topology with a script Claude writes and you can rerun.
Per Anthropic's docs, workflows differ from subagents and agent teams because the script holds the loop, branching, and intermediate results. Claude's context keeps the final answer. That is the unlock for dozens to hundreds of agents per run without drowning the session.
| Dimension | Subagents / skills | Agent teams | Dynamic workflows |
|---|---|---|---|
| Who decides next step | Claude, turn by turn | Lead agent, turn by turn | The script |
| Where intermediates live | Context window | Shared task list | Script variables |
| What is repeatable | Worker / prompt definition | Team definition | The orchestration itself |
| Scale | A few tasks per turn | A handful of peers | Dozens to hundreds per run |
| Best for | Delegated slices | Long-running peers | Audits, migrations, cross-checked research |
The straight-line trap (why this matters)
Nine out of ten multi-step agents I audit are polite queues. Step one finishes. Step two starts. Step three waits.
Half those waits never needed to happen because no data crossed the boundary. B does not consume A. The weather node has no edge to anything. You still pay for the wait in a linear script.
Cut arrows that carry no data. Independent nodes run together. C only needs A. D needs C. Fake edges disappear and wall-clock time drops without changing the models.
Smell test for every "and then": does the next step read the previous step's output? If no, there is no edge.
The 14-step roadmap
I group the fourteen steps into four stages: vocabulary, the diamond, resilience, and economics. Skip stage one and every later diagram is theater.
Stage A: Steps 1–4: vocabulary and contracts
- 011. Nodes are jobs. Edges are what flowsA node is one bounded job with input in and output out. An edge exists only when data actually moves. Draw boxes and arrows on a whiteboard before you write a prompt. If you cannot draw the arrow, the two boxes are independent.
- 022. Your linear script is a degenerate graphA→B→C→D is valid and slow. Redraw: cut arrows that carry no data. Independence is what you exploit for fan-out. Same decision rule as sequential vs parallel workflow.
- 033. Give every node a contractBounded input, bounded output, one job. Prefer JSON Schema on agent() returns so the next node consumes validated shapes. Validation at the tool-call layer beats "parse free text and pray."
- 044. Treat the edge as a data contractFlatten, dedupe, and filter in plain JavaScript when you can. An agent that only merges arrays is rent on wiring. Save model tokens for judgment.
// Node with a real contract: bounded in, validated out, one job.
const ITEM = {
type: 'object',
additionalProperties: false,
properties: {
title: { type: 'string' },
url: { type: 'string' },
impact: { type: 'string', enum: ['high', 'medium', 'low'] },
},
required: ['title', 'url', 'impact'],
};
const result = await agent(source.prompt, {
label: `research:${source.key}`,
schema: ITEM, // forces structured output
agentType: 'general-purpose',
});
// result is a shape the next node can trust, not free text.Stage B: Steps 5–7: the diamond that pays rent
Put fan-out and fan-in together and you get the workhorse topology of every serious agent graph: the diamond. One node splits, many nodes work, one node merges.
The workhorse of serious agent graphs. One node splits the job, many nodes work in parallel, one node merges.
Canonical form: fan out → reduce in plain JavaScript → synthesize with one agent. Market scans, dependency audits, and research reports share this skeleton.
- 015. Fan out with parallel()N independent sources or files become N concurrent subagents. parallel() is a barrier: it waits for the set. Failed thunks should resolve to null so one flaky agent does not sink the batch. Always .filter(Boolean) before the next stage. Excess work queues when concurrency hits core limits; pass a hundred thunks and they still finish.
- 026. Fan in at a barrierUse a barrier only when a stage needs the whole set together (cross-source dedupe, ranking, early-exit on empty). If the middle transform has no cross-item dependency, you wanted a pipeline, not a sync point.
- 037. The diamond: split → work → mergeCanonical form: fan out → reduce in code → synthesize with one agent. Market scans, dependency audits, and research reports are the same skeleton with different prompts. Once you see the diamond, you stop asking "how do I add more steps" and start asking "where is the split, where is the merge."
phase('Research');
// Nine sources, nine agents, all at once.
const raw = await parallel(
SOURCES.map((s) => () =>
agent(s.prompt, {
label: `research:${s.key}`,
phase: 'Research',
schema: ITEM_SCHEMA,
agentType: 'general-purpose',
}),
),
);
const collected = raw.filter(Boolean); // drop nulls from failed agents
// Edge: plain JS, zero tokens
const flat = collected.flatMap((c) => c.items);
phase('Curate');
const curated = await agent(
`Dedupe and rank these by impact:\n${JSON.stringify(flat)}`,
{ phase: 'Curate', schema: CURATED_SCHEMA },
);| Question | Prefer barrier (parallel stage) | Prefer pipeline |
|---|---|---|
| Does the next stage need every prior result together? | Yes (dedupe across sources, compare findings) | No (each item transforms independently) |
| Can fast items finish while slow ones continue? | No until the barrier clears | Yes |
| Is "cleaner looking stages" your reason? | Not enough | Default here |
| Example | Curate/rank all research hits | Per-file migrate → test → commit |
| Cost risk | Idle agents waiting on the slowest peer | Harder to reason about global invariants |
Stage C: Steps 8–11: routing, verification, failure, cycles
Classify with an agent when you need judgment. Pick the downstream path in code (if / switch on validated output).
Claude decides at the node. The script owns the edge. No emergent "Claude skipped the audit" surprises, because the skip would have to be written into the graph.
- 018. Route the edge at runtimeClassify with an agent if you need judgment. Pick the downstream path in code (if/switch on validated output). You want Claude at the node and deterministic routing at the edge. No emergent "Claude decided to skip the audit" surprises, because the skip would have to be written into the graph.
- 029. Put a verifier on the edgeMaker ≠ verifier. Same golden rule as independent verifier loops. Three patterns: adversarial refute (N skeptics; keep only survivors), perspective-diverse lenses (correctness / security / repro), judge panels that score competing drafts and graft winners.
- 0310. Isolate nodes so one failure cannot poison the graphTolerate missing inputs at fan-in. When agents write in parallel, isolate with git worktrees or sandboxes. Containment is a topology property, not a prompt. A thunk that throws inside parallel() should become null, not a full abort.
- 0411. Add a cycle only if it convergesLoop-until-dry for unknown-size discovery. Stop after K empty rounds. Dedupe against everything seen, not only confirmed results, or rejected findings reappear forever and your budget dies rediscovering dead ends.
Maker finds or drafts. Verifier tries to kill the finding. Only survivors continue downstream.
Patterns that work: adversarial refute, perspective-diverse lenses (correctness / security / repro), and judge panels that score competing drafts. Same golden rule as independent verifier loops.
// Router: agent classifies, code picks the edge.
const { severity } = await agent(
`Classify this diff's risk:\n${diff}`,
{
schema: {
type: 'object',
properties: { severity: { enum: ['low', 'high'] } },
required: ['severity'],
},
},
);
let review;
if (severity === 'high') {
review = await parallel(FILES.map((f) => () => agent(`Audit ${f}`)));
} else {
review = await agent(`Quick review of ${diff}`);
}Stage D: Steps 12–14: cost, topology, self-routing
- 0112. Tier models across nodesFan-out extract/classify on cheaper models. Keep merge and adjudication on Sonnet 5 / Fable / Opus as your evals justify. Every subagent inherits the session model unless you override per agent() call. Check /model before a large run. Same discipline as Gemini Flash routing and stop paying frontier prices for classification.
- 0213. Topology is your cost and latencyShape beats prompt polish. Wrong barriers idle expensive agents. Wrong cycles never dry. Measure cost per completed task in the unified spend dashboard, not tokens alone.
- 0314. Let Claude draw the graphThree doors in: (1) ask for a workflow in the prompt (or say ultracode), (2) run /deep-research for the bundled research diamond, (3) set /effort ultracode so every substantive task plans a workflow. When a run is good, press s to save the script into .claude/workflows/ as version-controlled and re-runnable by name.
How I run this on an engagement
I do not flip ultracode on day one. I force the team to draw edges on a whiteboard, pick one diamond, and prove schemas before we scale concurrency.
- 01Day 1: dependency drawingIf the team cannot agree on edges, they are not ready for parallel(). No script until the arrows are named.
- 02Day 2–3: one diamond in stagingResearch or audit only. Schemas on every node output. Barrier only where cross-set logic exists.
- 03Day 4: autonomy and spend capsWire Auto-review and pre-push /review around write nodes (governing agent autonomy). Set WebSearch and subagent session caps before ultracode.
- 04Day 5: save and promoteSave the working script. Add model tier overrides. Only then expand to a second graph shape.
- Keep MCP tools on fan-out nodes scoped read-first. Live connectors plus public artifacts (Week 29) multiply risk if write tools sit on shared pages.
- Log phase, agent count, token total, and elapsed time per stage. Without that, you cannot tell whether a barrier is earning its wall-clock cost.
- Treat saved workflows like production code: review PRs that change .claude/workflows/ the same way you review CI.
Six starter graphs worth building this week
| Graph | Shape | When I reach for it | First success metric |
|---|---|---|---|
| /deep-research style | Fan-out search → fetch → adversarial verify → synthesize | Claims that need sources, not vibes | % of claims that survive verify |
| Codebase audit | Fan-out files → diverse-lens verify → merge findings | Bug sweeps where false positives kill trust | False-positive rate after gate |
| Migration | Discover sites → parallel transform → verify → report | Large refactors with measurable done criteria | Files migrated / files remaining |
| Panel | N design drafts → judge panel → graft winners | Architecture choices with wide solution space | Judge agreement + human pick rate |
| Map | Parallel area understand → reduce → brief | Onboarding a new repo region | Time to usable brief |
| Loop-until-dry | Find → verify → repeat until K empty rounds | Unknown bug/issue cardinality | Rounds to dry + unique confirmed |
Common mistakes
- Calling every edge an agent. Plumbing is free in JavaScript. Judgment is not.
- Barriers for aesthetics. Measure idle time behind the slowest peer.
- Skipping schemas on node outputs. Unstructured free text cannot be a reliable edge.
- Self-grading makers. If the same agent finds and verifies, you rebuilt the straight line with extra steps.
- Turning on ultracode before spend caps and session WebSearch/subagent caps are set.
- Using worktrees on every node "just in case." Isolation is a seatbelt for parallel writers, not a default tax.
Conclusion
The viral roadmap is right about the diagnosis: linear agents pretend work is a queue. Production work is a graph. Claude Code workflows give you a place to put that graph that is not another overloaded chat turn. Draw the edges, contract the nodes, verify on the path, tier the models. Then let Claude write the script you would have written anyway, and save it when it works.
Sources: Claude Code dynamic workflows docs at Claude blog; public discourse on the 14-step graph engineering roadmap (Jul 20, 2026) at x.com — 2079165300625330317.
Agentic AI patterns, delivered Thursdays
What I am shipping, watching, and pruning out of client stacks each week. One email. No fluff.