 Command

Pranesh Nikhar's personal site. Vim-style keybinds for navigation; theme + font pickers below.

Theme
 Font Body Code
Reader
Keybinds
Navigation
j / ↓ Next item k / ↑ Previous item g First item in region G Last item in region zz Center focused item h / l Move left/right region ] / [ Next/previous heading } / { Next/previous block d / u Half-page down/up
Layout
<zh> / <zl> Toggle left/right sidebar <zr> Toggle reader view <zj> / <zk> Focus main/navbar <S-h/j/k/l> Focus left/main/navbar/right ⌃H / ⌃L Focus left/right sidebar ⌃J / ⌃K Focus main/navbar ⇧C / ⇧E Collapse / expand all sections
Dialogs
⌃P / : Command palette ⌃X Theme picker / Search ? Show keybinds Esc / ⌃C Close dialog
History
n Next document b Previous document ⌃O History back ⌃I History forward
 Search
about: Pranesh Nikhar about/more: 🪪 More docs/test: Docs Test ideas: 💡 Ideas more: ➕ More now: Now posts: 📬 Posts projects: 📚 Projects webtui: Style posts/agentic-eda: AgenticEDA — Automated Exploratory Data Analysis with LangGraph posts/cap-theorem-outage-story: CAP Theorem with a Real Outage Story posts/codepilot: CodePilot — From Requirements to Deployable FastAPI Backend posts/common-auth-mistakes: Common Auth Mistakes Developers Make posts/compiled-vs-jit-vs-interpreted: Why Is X Language Fast or Slow? — Compiled vs JIT vs Interpreted posts/cs-degree-gaps: Things CS Degrees Don't Teach You posts/cve-2025-breach-analysis: CVE-2025 Breach Analysis — Midnight Blizzard and the 16 Billion Credential Leak posts/fixloop: FixLoop — AI Agent Loop for Self-Correcting Code posts/functional-vs-oop: Functional vs OOP — Same Problem, Both Ways posts/getman: Getman — Declarative API Tester for CLI & TUI posts/how-compilers-optimize: How Compilers Actually Optimize Your Code posts/http3-quic: HTTP/3 and QUIC — Why They Matter posts/leetcode-vs-engineering: LeetCode vs Real Engineering Skills posts/llm-from-scratch: LLM from Scratch — GPT-Style Transformer in PyTorch posts/lsm-trees-bloom-filters: LSM Trees & Bloom Filters — Production Deep Dive posts/mcp-workflow-builder: MCP Workflow Builder — Visual DAG for MCP Tools posts/persistent-memory: Persistent Memory — Long-Term Memory for AI Agents via MCP posts/playcli: PlayCLI — Terminal Video Player posts/postgres-mvcc: How PostgreSQL MVCC Works — Multi-Version Concurrency Control Deep Dive posts/raft-consensus: Raft Consensus Algorithm Explained posts/rust-borrow-checker: Rust Borrow Checker — Catches Real Bugs posts/titan: Titan — Terminal AI Coding Agent posts/what-happens-url: What Happens Between Typing a URL and Seeing the Page posts/what-happens-when-you-run-a-program: What Actually Happens When You Run a Program posts/zero-knowledge-proofs: Zero-Knowledge Proofs Explained Simply webtui/components/accordion: Accordion webtui/components/badge: Badge webtui/components/button: Button webtui/components/checkbox: Checkbox webtui/components/dialog: Dialog webtui/components/input: Input webtui/components/popover: Popover webtui/components/pre: Pre webtui/components/progress: Progress webtui/components/radio: Radio webtui/components/range: Range webtui/components/separator: Separator webtui/components/spinner: Spinner webtui/components/switch: Switch webtui/components/table: Table webtui/components/textarea: Textarea webtui/components/tooltip: Popover webtui/components/typography: Typography webtui/components/view: View webtui/contributing/contributing: Contributing webtui/contributing/contributing: ## Local Development webtui/contributing/contributing: ## Issues webtui/contributing/contributing: ## Pull Requests webtui/contributing/style-guide: Style Guide webtui/contributing/style-guide: ## CSS Units webtui/contributing/style-guide: ## Selectors webtui/contributing/style-guide: ## Documentation webtui/installation/astro: Astro webtui/installation/astro: ## Scoping webtui/installation/astro: ### Frontmatter Imports webtui/installation/astro: ### ‹style› tag webtui/installation/astro: ### Full Library Import webtui/installation/nextjs: Next.js webtui/installation/vite: Vite webtui/plugins/plugin-dev: Developing Plugins webtui/plugins/plugin-dev: ### Style Layers webtui/plugins/plugin-nf: Nerd Font Plugin webtui/plugins/theme-catppuccin: Catppuccin Theme webtui/plugins/theme-custom: Custom Theme webtui/plugins/theme-everforest: Everforest Theme webtui/plugins/theme-gruvbox: Gruvbox Theme webtui/plugins/theme-nord: Nord Theme webtui/plugins/theme-vitesse: Vitesse Theme webtui/start/ascii-boxes: ASCII Boxes webtui/start/changelog: Changelog webtui/start/installation: Installation webtui/start/installation: ## Installation webtui/start/installation: ## Using CSS webtui/start/installation: ## Using ESM webtui/start/installation: ## Using a CDN webtui/start/installation: ## Full Library Import webtui/start/installation: ### CSS webtui/start/installation: ### ESM webtui/start/installation: ### CDN webtui/start/intro: Introduction webtui/start/intro: ## Features webtui/start/plugins: Plugins webtui/start/plugins: ## Official Plugins webtui/start/plugins: ### Themes webtui/start/plugins: ## Community Plugins webtui/start/theming: Theming webtui/start/theming: ## CSS Variables webtui/start/theming: ### Font Styles webtui/start/theming: ### Colors webtui/start/theming: ### Light & Dark webtui/start/theming: ## Theme Plugins webtui/start/theming: ### Using Multiple Theme Accents webtui/start/tuis-vs-guis: TUIs vs GUIs webtui/start/tuis-vs-guis: ## Monospace Fonts webtui/start/tuis-vs-guis: ## Character Cells posts/termium: Termium — Chromium-Based Terminal Browser posts/new-projects-july-2026: New Projects — GRIT, Sage, Terrain Analyser, Self-Healing RAG, and More watchlist: 🎬 Watchlist posts/graph-engineering-rag: Graph Engineering replaced RAG at Microsoft, Stanford and Anthropic posts/graph-engineering-claude: Graph Engineering with Claude: 14-Step Roadmap from 0 to Graph Architect posts/agent-harness-loop-graph-engineering: Agent Harness Engineering vs Loop Engineering vs Graph Engineering
 Theme Current: Light j/k or ↑/↓ + Enter

Graph Engineering with Claude: 14-Step Roadmap from 0 to Graph Architect

A 14-step roadmap that turns a linear agent pipeline into a coordinated graph — fan out across a fleet, verify findings, and converge on results a lone agent could never hold.

The Problem with Linear Agents

Graph Engineering Overview

Most people who try to build a multi-step agent end up with a straight line. Step one, step two, step three — each waiting politely for the last to finish before it starts.

9/10 notice that half those steps never needed to wait at all.

They don’t route. They don’t branch. They don’t parallelize. They just queue — one head, one context, one thing at a time, until the window fills up and the agent forgets what it was doing.

Follow my Substack to get fresh AI alpha: movez.substack.com

This is the 14-step roadmap that turns that single-file line into a graph: one that fans out across a fleet, verifies its own findings, and converges on a result a lone agent could never hold.

Here’s the shift nobody spells out. A prompt is a sentence. A loop is a cycle. A harness is the floor the agent stands on. But the shape of the work itself — what runs before what, what can run at the same time, what has to wait for everything else — that shape is a graph. Nodes do the thinking. Edges carry the results.

Claude Code shipped the tooling to build these graphs directly: dynamic workflows. Claude writes a plain JavaScript orchestration script, then spawns a coordinated fleet of subagents to execute it — and the coordination itself costs zero model tokens, because it’s code, not a conversation.


01. Nodes are jobs. Edges are what flows.

Nodes and Edges

A graph has exactly two things, and getting them straight fixes most of the confusion. A node is a unit of work — one agent, one bounded job, one input in and one output out. An edge is a dependency: it says this node’s output feeds that node’s input. Nothing more.

The mistake is treating “and then” as an edge. “Summarize the file and then tell me the weather” has no edge between the two — the weather doesn’t consume the summary. That’s two disconnected nodes that a linear script needlessly chains. The edge only exists when data actually moves across it.

Learn to ask, for every “and then” in your agent: does the next step read the last step’s output? If not, there is no edge, and the wait is wasted.

Draw it as boxes and arrows. A box is an agent() call. An arrow is a variable passed from one call’s return into another’s prompt. If you can’t draw the arrow — if no variable crosses — the two boxes are independent, and independence is the thing you’ll exploit for the rest of this course.


02. Your linear script is a degenerate graph

Linear vs Graph

When you write an agent as “do A, then B, then C, then D,” you’ve drawn a graph — a single unbranching chain. Every node has exactly one edge in and one edge out. It runs correctly. It also runs slowly and is fragile, because a chain has no redundancy: if C stalls, D never happens, and A’s work is trapped upstream with nowhere to go.

The first real skill of graph engineering is redrawing the chain. Take your linear agent and, for each arrow, ask the Step 1 question. Most chains have two or three arrows that don’t carry data — they’re just the order you happened to type things in. Cut those arrows and the chain collapses into something wider: a few independent nodes that could all run at once, feeding a single node that needs them all.


03. Give every node a contract

Node Contract

A node you can’t reason about is a node you can’t parallelize. The fix is a contract: bounded input, bounded output, exactly one job. The input is whatever the node reads — passed in explicitly, never assumed from a shared window. The output is a defined shape, ideally validated, so the next node can consume it without guessing.

In a workflow this contract is enforced with a schema. When you hand Claude an agent() call with a JSON schema, the subagent Claude spawns is forced to return validated structured data — validation happens at the tool-call layer, so Claude retries on mismatch instead of handing you free text you have to parse and pray over.

This is the difference between a node Claude can wire into a graph and a node that only works when a human reads its output.

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,
  agentType: 'general-purpose',
});

04. Treat the edge as a data contract

Edge Contract

An edge isn’t just “B comes after A.” It’s a promise about what crosses: A produces this shape, and B is built to consume this shape. When you name the edge by its data — not its order — two things get easier. You can see instantly whether the edge is real (does data actually move?), and you can swap the node on either end without breaking the graph, as long as the shape holds.

In practice, the edge lives in plain JavaScript. The reduce step between fan-out and synthesis — flatten, dedupe, filter — is just code operating on the shapes your nodes returned. No agent needed. One of the quiet wins of graph thinking: a huge amount of what people burn model tokens on is really an edge, and edges are free.

The temptation is to spawn an agent to “combine the results.” Resist it. If combining means flatten-and-dedupe, that’s results.flatMap(...) and a Set — deterministic, instant, zero tokens. Save agents for judgment, not for plumbing.


05. Fan out with parallel()

Fan Out

This is the move that pays for everything. When you have N independent nodes — N sources to check, N files to review, N routes to audit — you don’t chain them. You tell Claude to fan them out and run them at once. In a workflow that’s parallel(): Claude takes an array of thunks and spawns one subagent per thunk, all executing concurrently, then hands you back the array of results.

Two details make it robust. First, parallel() is a barrier — it waits for every thunk before it returns, so the next stage sees the complete set. Second, a thunk that throws resolves to null instead of rejecting the whole batch, so one flaky agent can’t sink the run.

phase('Research');

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);

06. Fan in at a barrier

Fan In

A fan-out is only useful if something gathers it. The fan-in is the node where edges converge — where one agent (or one piece of code) sees all the upstream results at once and does something that requires the whole set: dedupe across sources, rank by impact, early-exit if the total came back empty. This is the one place a barrier earns its wall-clock cost.

The rule that keeps graphs fast: use a barrier only when a stage genuinely needs every prior result together.

const flat = collected.flatMap((c) => c.items);
log(`Collected ${flat.length} items`);

phase('Curate');
const curated = await agent(
  `Dedupe and rank these by impact:\n${JSON.stringify(flat)}`,
  { phase: 'Curate', schema: CURATED_SCHEMA },
);

Just flattening a list? That’s an edge, do it inline. If you wrote parallel → transform → parallel and that middle transform has no cross-item dependency, you should have used a pipeline and skipped the barrier entirely.


07. The diamond: split → work → merge

Diamond Topology

Put fan-out and fan-in together and you get the workhorse topology of every serious agent graph: the diamond.

One node splits the job, many nodes do the work in parallel, one node merges. It’s the shape behind a market scan, a dependency audit, a code review, a research report — swap the sources and prompts and the same skeleton adapts.

The canonical form has a name worth memorizing: fan out → reduce → synthesize. Fan out to gather breadth, reduce with plain code to compress it, synthesize with a final agent to write the answer.

Once you see the diamond, you stop asking “how do I make my agent do more steps” and start asking “where’s the split, where’s the merge” — which is the question that actually scales.


08. Route the edge at runtime with a conditional

Runtime Routing

Not every graph is fixed. Sometimes the edge to take depends on what a node found. A router node inspects a result and decides which downstream path fires — classify the ticket, then branch to the right handler; check the diff size, then either do a quick review or spin up a full audit. In a workflow this is just a JavaScript if or switch on a node’s validated output.

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}`);
}

09. Put a verifier on the edge

Verifier

The real leverage of a graph isn’t more agents — it’s the structure you can wrap around them to produce confidence. A verifier node sits on the edge before a result is allowed downstream, and its only job is to try to kill the finding. If it survives, it passes. If not, it never reaches the answer.

Three patterns to know: Adversarial verify (spawn N skeptics to refute each finding), Perspective-diverse verify (correctness, security, does-it-reproduce), and Judge panel (generate N attempts, score with parallel judges, synthesize).


10. Isolate nodes so one failure can’t poison the graph

Node Isolation

In a chain, a failure cascades. In a graph, failure should be contained to its node. A thunk that throws inside parallel() resolves to null, so eight good agents still return while one bad one drops out. Design every fan-in to tolerate missing inputs rather than assume a full set.

The subtler failure is nodes stepping on each other. When agents write files in parallel, they can collide. The fix is isolation via worktrees — each agent runs in its own git worktree, merges cleanly.


11. Add a cycle — but make it converge

Cycle Convergence

Sometimes you don’t know how big the job is until you’re in it. That needs a cycle — a controlled edge back to an earlier node. The pattern that converges is loop-until-dry: keep spawning finders until K consecutive rounds surface nothing new.

const seen = new Set(); const confirmed = []; let dry = 0;
while (dry < 2) {
  // ... spawn finders, filter fresh, verify, push confirmed
}

Dedupe against everything seen, not just confirmed results — otherwise rejected findings reappear every round forever.


12. Tier the models across the nodes

Model Tiering

Not every node needs your best model. Run the boring nodes (extract, classify) on a cheaper model and spend expensive tokens where judgment lives. The model option on agent() tells Claude to route just that node elsewhere.


13. Topology is your cost and latency

Topology Latency

Default to pipeline(), not parallel(). A pipeline streams each item through all stages independently with no barrier — fast items finish early instead of idling. Reach for a barrier only when a stage truly needs every prior result at once.


14. Let Claude draw the graph — self-routing

Self-Routing

With dynamic workflows, you describe the objective and Claude writes the orchestration script itself. Three ways in: say “workflow” in your prompt, run a saved one like /deep-research, or turn on ultracode. Press s on a good run to save it into .claude/workflows/.


Six graphs to build with Claude this week

Six Graphs

  1. Security sweep — one subagent per route file hunting for missing auth
  2. Cited report with /deep-research — parallel searches, adversarial verify
  3. Port a module — fan out across files, test gate on each, loop failures
  4. Adversarial diff review — route by size, parallel audit, judge panel
  5. Ecosystem scan — many sources in parallel, rank at barrier, write digest
  6. Discovery of unknown size — loop-until-dry bug finding

Conclusion

The linear agent was never the ceiling — it was just the first shape. Once you can see the nodes and the edges, you stop asking the agent to do more and start asking the graph to do it wider: fan out where the work is independent, gate the edges where confidence matters, tier the models where judgment doesn’t.

Most people will keep queueing steps in a line. The ones who learn to draw the graph will run a fleet.

 praneshnikhar.site / posts / graph-engineering-claude · Top 1:1