Mahax (@mahaximus_) posted a long-form article on X on 29th July 2026 explaining graph engineering for AI work. His complaint is fair: most posts on the topic are either too vague to use or too technical to follow, and nobody defines a graph before telling you to build one. His mental model holds up. His implementation half does not, so I checked every claim against the Claude Code documentation. Both halves are below.

What a graph actually is
Two parts, and that is the whole thing.
A node is one unit of work. One agent, one input, one output. Not "research this topic and write a summary and check the sources". Just one of those. The smaller and tighter the job, the more useful the node.
An edge is a dependency. It exists only when the second node genuinely uses what the first one produced. Not when the second one merely comes after the first.
That second definition is the load-bearing one. Sequence is not dependency, and most people write prompts as if it were.
Your workflow is already a graph
"Research this topic, then summarise what you find, then write a draft" is a graph. It is the simplest possible one: an unbranching chain where every step waits for the one before it. It runs correctly. It also runs at the sum of all its steps, and it breaks completely when any single step returns something unusable.
The first move in graph engineering is not learning anything new. It is looking at what you already wrote and asking whether every step really needs to wait.
The fake-edge test
Five minutes, any workflow:
- Write out every step as a box.
- Draw an arrow between each pair of consecutive steps.
- For each arrow, ask: does data from step A actually go into step B?
- Yes, keep the arrow. It is a real dependency.
- No, delete it. That is a fake edge.
- Anything with no incoming arrow can start immediately.
- Anything with no outgoing arrow is a final output.
His example: "review file A for bugs, then review file B for bugs." It reads as a sequence. The review of B never looks at what A returned. They run one after the other only because that is the order you typed them in.
Expect to find two or three fake edges in almost anything you have written. Each one is waiting time you are giving away.
The diamond
Remove the fake edges and one shape appears more than any other. One node fans out into several parallel nodes, and those all feed one node that combines them. Drawn out, it is a diamond.
| Version | Shape |
|---|---|
| Linear | search, then read source 1, then source 2, then source 3, then synthesise |
| Diamond | search, then read all three sources at once, then synthesise |
The synthesis node gets the same inputs either way. It waits for the slowest read instead of the sum of all three.
Two rules make it work. The parallel nodes must be genuinely independent. The convergence node must genuinely need all of them, otherwise the extra reads are wasted work.
I would add a third. The convergence point is a barrier, and the barrier is often a fake edge in disguise. Everything stops there until the slowest branch lands. Claude Code's workflow scripts make the distinction explicit: pipeline() pushes each item through every stage independently, so item A can be in stage 3 while item B is still in stage 1. Reach for the barrier only when the final stage needs the whole set at once: to deduplicate across all findings, to exit early on a zero count, or to compare results against each other. "I need to flatten the list first" is not a reason to make everything wait.
Where graphs fail
The article names two failure modes and one fix.
A bad node goes undetected. Three sources run in parallel and one returns a hallucination or an empty result. That output flows into synthesis next to the two good ones. The synthesis node has no idea one of its inputs is wrong, so it produces a confident answer built on bad material. The parallel structure that bought the speed also removed the checkpoints where you would have caught it.
The error cascades. In a chain, a bad step produces a visibly bad output. In a converging graph, the bad output mixes with good ones and the damage becomes diluted and hard to trace.
Both get the same fix: a checker node sitting between the parallel layer and the convergence point. It does no real work. It asks whether each output is usable, then passes, flags, retries or drops it. The five things it should catch:
- Empty or null outputs
- Outputs that contradict each other in ways that cannot both be true
- Outputs that drifted off-topic
- Confidence signals too low to trust
- Format errors that will break the next node's parsing
There is a third failure mode the article misses, and it is the one that actually bit Anthropic. Building a C compiler with 16 parallel Claude instances, they hit collision: "every agent would hit the same bug, fix that bug, and then overwrite each other's changes." Nodes that look independent are not independent if they write to the same files. The fix is isolation, giving each agent its own copy of the repo and merging afterwards.
The same write-up puts the checker argument better than the X post does: "Claude will work autonomously to solve whatever problem I give it. So it's important that the task verifier is nearly perfect."

Static graphs and dynamic graphs
A static graph is defined before it runs. Fast, predictable, auditable. A dynamic graph builds itself as it goes: a node finishes, looks at what it found, and decides what should come next. Flexible, and much harder to debug because the structure that ran is not the one you drew.
His rules, which I agree with:
- Static when the task is repeatable and the structure never changes
- Static when speed and predictability beat flexibility
- Dynamic when the scope depends on what you find along the way
- Static first, always. Switch only when the static version hits a wall
- Never dynamic when you need to audit exactly what ran and why
Most workflows that feel like they need a dynamic graph just need a better static one.
For business people
The graph is worth building when the task repeats often enough for the time saving to compound, or when a mistake in the middle costs more than the checker node costs to run. For a one-off you will never repeat, the linear version is faster to build and faster to finish.
What it does not do is save money. Running 15 agents costs roughly 15 agents' worth of tokens whether they run at once or in a line. A graph buys wall-clock time and, with a checker, reliability. Claude Code flags a run that schedules more than 25 agents or projects past 1.5 million tokens, which tells you what scale of spend is considered worth a warning.
The risk is the one named above and worth repeating: parallel work removes the moments where you would have noticed something was wrong. Speed and blindness arrive together. Budget for the checker or do not build the diamond.
My Council skill is already this shape. A panel of expert agents critiques the same artifact in parallel, scores converge, one maker revises. Diamond, with the scoring acting as the checker.
For technical people
Claude Code gives you four ways to run more than one agent, and they differ by who holds the plan.
| Mechanism | Who decides what runs next | Scale |
|---|---|---|
| Subagents | Claude, turn by turn | A few delegated tasks per turn |
| Skills | Claude, following the prompt | Same as subagents |
| Agent teams | A lead agent, turn by turn | A handful of long-running peers |
| Workflows | The script | Dozens to hundreds of agents per run |
Only the last one is a graph in the article's sense, because only there does the structure live outside a context window. Intermediate results sit in script variables, so Claude's context holds the final answer and nothing else.
Opting in. Type ultracode in the prompt, or just ask in words: "use a workflow" works the same way. /effort ultracode turns it on for the whole session and Claude then plans a workflow for every substantive task. The literal trigger keyword was workflow before v2.1.160.
The script. Plain JavaScript with top-level await. agent() spawns one subagent. pipeline() runs one per item in a list.
export const meta = {
name: 'audit-routes',
description: 'Audit every route handler for missing auth checks',
}
const found = await agent('List every .ts file under src/routes/.', {
schema: { type: 'object', required: ['files'], properties: { files: { type: 'array', items: { type: 'string' } } } },
})
const audits = await pipeline(found.files, file =>
agent(`Audit ${file} for missing authentication checks.`, { label: file }),
)
return audits.filter(Boolean)
An agent() call resolves to null if it is stopped or hits an unrecoverable API error, which is why the script ends with .filter(Boolean).
The limits that shape your design:
| Limit | Value |
|---|---|
| Concurrent agents per workflow run | 16, fewer on fewer CPUs |
| Total agents per run | 1,000 |
| Concurrent subagents outside a workflow | 20 (CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS) |
| Subagent nesting depth | 3 (CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH) |
| Default size guideline | medium, under 15 agents |
| Large-run warning | Over 25 agents or 1.5M projected tokens |
One behaviour worth designing around: on resume, cached results stop at the first agent that did not finish, and everything that started after it runs again even if it completed. Many small nodes therefore preserve far more progress than one long node. That is a better argument for small nodes than the one the article gives.
/deep-research ships as a bundled workflow and is the diamond with a checker, already built: it fans searches across several angles, cross-checks the sources, votes on each claim, and drops the claims that fail the vote.

Where the article overreaches
The depends_on syntax does not exist. The post says Claude Code has a workflow keyword that parses a dependency block you write in your prompt. It does not. You describe the work and Claude writes the orchestration script, or you launch several subagents in one turn. Dependencies live in the script's control flow, never in a field you type.
Its own example breaks its own rule. He redraws "Research, Summarize, Write, Check sources, Format, Publish" as running research and source-checking at the same time. Checking sources needs the sources. That is a real edge he deleted.
Some of it I could not check. The code blocks, the comparison table and the four ready-to-paste prompts did not survive the fetch from X. Everything quoted here comes from the prose.
A graph is not cheaper. Removing a fake edge is free and saves waiting. Adding parallel branches and checker nodes costs more tokens, not fewer. Buy the speed and the reliability knowingly.
None of that sinks the model. The fake-edge test alone is worth the read, and his closing instruction is the right one: run it on one workflow this week and find the first fake edge in something you built yourself.
Further reading
- The original X post by @mahaximus_
- Claude Code: dynamic workflows
- Claude Code: subagents in the SDK
- Anthropic Engineering: building a C compiler with parallel Claudes