cd /news/ai-agents/graph-engineering · home topics ai-agents article
[ARTICLE · art-122647] src=pub.towardsai.net ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Graph Engineering

Graph engineering, the practice of designing the structure an AI agent runs inside, is introduced as a solution for complex multi-step tasks that overwhelm single-agent loops. The article defines key terms—harness, loop, and graph—and outlines four core patterns (sequential, branching, parallel, and conditional routing) that cover most production systems. It emphasizes that graphs prevent steps from interfering with each other, improving reliability over monolithic agent prompts.

read12 min views1 publishedSep 7, 2026

You have probably built an agent. Maybe it calls a search tool, reasons over the results, formats a response, done. One loop. Works fine. You feel good about it.

Then a coworker drops a harder problem on your desk. Not a single-step retrieval task. Something with moving parts, multiple specialised checks, parallel processing, a human who needs to approve before anything irreversible happens. You try to cram it all into one agent loop. The system starts behaving strangely. It skips steps. It forgets what it was doing three turns back. You add more instructions to the prompt. Things get worse.

This is the moment graph engineering becomes relevant.

Graph engineering is the practice of designing the structure an AI agent runs inside, not the agent itself.

Think of it this way. An agent is like an employee: it has skills, memory, tools, and judgment. Graph engineering is the org chart that defines who that employee talks to, when they talk to them, what information gets passed along, and under what conditions work moves forward versus gets sent back. You are not just hiring smart people. You are designing how the organisation works.

The nodes are the workers. The edges are the handoffs. The state is the memory shared between them.

Before the patterns, three terms. Get these right and the rest of the article clicks.

Every agent sits inside what practitioners call a harness. It is the wrapper around the model itself , the memory it has access to, the tools it can call, and the guardrails that prevent it from doing something catastrophic. The harness is not the agent’s intelligence. It is the agent’s operating environment.

A well-built harness gives the agent exactly what it needs for its specific job and nothing extra. A poorly built harness dumps everything into the context window and hopes for the best.

A loop is the iterative cycle inside a single agent. The agent reads a problem, decides whether to call a tool or produce an answer, acts, observes the result, and goes again. This is the ReAct pattern. It is powerful for focused tasks.

The key word is focused. One agent. One goal. Loop until done.

Loops break down when the problem has distinct phases with different context requirements. Asking one agent to review code for security issues and performance issues and API design and test coverage in one loop is like asking one person to simultaneously be your security auditor, your performance engineer, your API designer, and your QA lead. They cannot fully inhabit all four mental models at once.

A graph is a structured workflow that connects multiple nodes — each node being an agent, a function, or a processing step — with edges that define how work moves between them.

The simplest graph is one node with an edge looping back to itself. That is just a loop with extra vocabulary. The interesting graphs are the ones with branching, parallelism, synchronisation points, and conditional routing. Those are the patterns below.

These four patterns cover the overwhelming majority of what real production systems do. Everything complex you will ever build is some combination of these.

The simplest structure. Node A completes, passes its output to Node B, which completes and passes to Node C. Each step processes the output of the previous one.

[Node A] → [Node B] → [Node C] → Output

When do you use this? When a task genuinely benefits from decomposition into sequential steps. A code review pipeline might: first parse the diff, then check it for security issues, then format the findings into a report. Each step has a different job and benefits from receiving a focused, clean input rather than raw everything.

The critical point here: the graph is not making the steps smarter. It is preventing them from interfering with each other.

A small code example shows the shape clearly:

Each function receives the full state and returns only the fields it changed. The graph wires them together. No node needs to know what the others are doing.

This is where graphs justify their existence.

Fan-out takes one task and splits it into multiple parallel sub-tasks that run simultaneously. Join is the synchronisation node that waits for all of them to finish, then merges the results.

┌→ [Security Agent] ─┐              ├→ [Perf Agent]    ──┤Input → Router┤→ [API Agent]     ──┼→ [Join] → Output              ├→ [Test Agent]    ──┤              └→ [Quality Agent] ─┘

The real-world example from the video is a pull request review system. You could send the same PR diff through a security reviewer, a performance reviewer, and a test coverage reviewer sequentially. Or you fan out to all three simultaneously, wait for all three to finish, and merge the findings. Total time is roughly equal to the slowest single reviewer, not the sum of all three.

The fan-out pattern in code uses the Send API to dispatch parallel branches:

from langgraph.constants import Senddef fan_out_to_reviewers(state: State) -> list[Send]:    # Dispatch all reviewers simultaneously    # Each runs independently, returns findings to the same state field    return [        Send("security_agent", state),        Send("performance_agent", state),        Send("api_agent", state),        Send("test_agent", state),    ]

The join happens automatically when you use an accumulator on the findings field:

from typing import Annotatedimport operatorclass State(TypedDict):    # operator.add concatenates lists from parallel branches safely    # Without this, the last agent to finish would overwrite all the others    findings: Annotated[list, operator.add]

That single operator.add annotation is doing serious work. When four agents all return findings simultaneously, LangGraph collects every write and concatenates them instead of letting the last writer win. This is not a coincidence. It is a deliberate design decision you make when you define the state schema.

A router reads the current state and sends work to a different node depending on what it finds.

┌→ [Fixer Agent] → back to startInput → [Reviewer] ─┤                    └→ [Human Review Gate] → Approved → Done

From the video: after a code review agent evaluates a pull request, the router checks the outcome. Tests failed? Send it to a fixer agent. Tests passed? Route it to a human for final approval. The reviewer’s output determines where work goes next.

This is where the distinction between LLM routing and Python routing matters enormously. You could ask the LLM to decide where to send the work. Or you could write a Python function that reads a structured field from the state and returns a node name.

<cite index=”22–1">The router’s output should be structured, recorded, and testable rather than buried in a free-form prompt.</cite> LLM routing is non-deterministic and expensive. Python conditional routing is testable in two lines and costs nothing. Production systems move toward Python routing as fast as they can.

This pattern runs a generator and an evaluator in a loop until the output meets a quality threshold — or until a retry counter says enough.

[Generator] → [Evaluator] → Good? Yes → Done                          → No, here's why → back to [Generator]

The generator produces something. The evaluator grades it with structured output — not a prose critique, an actual typed judgment with a pass/fail field. If it fails, the feedback flows back into the generator’s next call. If it passes, execution moves on.

class EvalResult(BaseModel):    passed: bool    feedback: str   # only matters if passed is False
php
def route_after_eval(state: State) -> str:    if state["eval_result"].passed:        return "done"    if state["retry_count"] >= 3:        return "escalate"   # never loop forever    return "generator"      # try again with feedback

The retry counter is not optional. Without it, a generator that consistently fails the evaluator runs until you run out of money or patience.

This trips people up, so worth being direct.

It is not a knowledge graph. A knowledge graph is a data structure for representing facts and their relationships — think of a database where entities are nodes and relationships are edges. Neo4j, GraphRAG, graph neural networks, these are knowledge graph tools. Graph engineering is about execution flow, not data storage.

It is not an agent swarm. An agent swarm is a collection of agents that interact dynamically, often without a predefined topology. Agents negotiate, bid for tasks, and self-organise. Swarms are flexible and adaptable. They are also harder to debug and less predictable.

Graph engineering sits at the other end of that spectrum. You pre-define the nodes, the edges, and the conditions under which work moves. The path is explicit. When something goes wrong, you can trace exactly which node produced the bad output and why.

Neither approach is universally better. Complex open-ended research tasks might benefit from swarm flexibility. A pull request review pipeline that needs to reliably block broken code from merging benefits from graph predictability. Know which problem you have.

The graph handles the structure. Shared state handles the information.

Every node in a graph reads from a shared state object and writes updates back to it. This is not just passing variables around. It is the mechanism that lets a fan-out of five agents all contribute their findings to one coherent result, lets a router make a decision based on what three previous nodes discovered, and lets the final report node see everything that happened without any node needing to talk directly to any other.

Google’s Agent Development Kit makes this explicit. Each node in a graph can read the shared state for context relevant to its job, write its results back, and the graph engine handles the merging, the ordering, and the persistence.

The state schema is the most important design decision in the whole system. Designed well, agents collaborate without interfering. Designed poorly — fields that overwrite instead of accumulate, missing fields that agents expect to find, no versioning strategy — and the graph produces subtly wrong outputs that are hard to trace.

Here is the minimal shape every state schema needs:

from typing import TypedDict, Annotatedimport operatorclass GraphState(TypedDict):    # Overwrite fields: last write wins, only one valid value at a time    status: str    current_task: str    # Accumulator fields: parallel branches append safely    findings: Annotated[list, operator.add]    errors:   Annotated[list, operator.add]

Overwrite fields for things where only one value is valid at a time. Accumulator fields for everything that multiple agents might contribute to simultaneously. The difference between these two is the difference between parallel agents working correctly and parallel agents silently eating each other’s output.

The video uses this example. It is worth walking through completely because it uses all four patterns in one system.

A developer pushes a pull request. Here is what the graph does:

Step 1 — Ingestion (Sequential). A single node parses the diff, extracts the changed files, and detects any function signature changes that might affect external callers. This is preprocessing. No LLM involved. Pure Python.

Step 2 — Fan-Out (Parallel). The orchestrator fans out to four or five specialist reviewer agents simultaneously: security, performance, API design, test coverage, code quality. They all read the same parsed diff from state. They all write their findings back to the accumulator field. Total time: the slowest single reviewer, not the sum.

Step 3 — Join. Once all reviewers finish, the join node has all findings in one list. It deduplicates, ranks by severity, computes a security score.

Step 4 — Router. The router reads the aggregated findings. Critical security issue? Route to a human approval checkpoint using interrupt(). No critical issues, all tests passing? Route to auto-approve.

Step 5 — Human Gate (Optional). If routing sent work here, the graph s. A human sees the findings. They approve, reject, or ask for changes. The graph resumes exactly where it left off. The PR either merges or goes back to the developer.

Every piece of that is a pattern you have seen above. Fan-out, join, router, and the human gate is just the interrupt-based HITL from the LangGraph series. The graph is the composition of these patterns, not a new invention.

From compiler control-flow graphs to database query plans, graph topology has always been the standard language for describing how work flows.

Graph engineering did not invent nodes and edges. It borrowed them from a tradition that goes back to Dijkstra’s work on compiler optimisation, to RACI matrices in organisational design, to Petri nets in concurrent systems theory. The insight is not structural novelty. It is recognising that the problems practitioners are hitting with AI agents — coordination failures, unpredictable routing, context getting lost between steps, are the same problems that led to those tools in the first place.

What changed is that AI agents introduced a new kind of node: one that is probabilistic, context-sensitive, and capable of producing structured outputs from natural language inputs. The old graph machinery needed new defaults. LangGraph, Google ADK, and Microsoft AutoGen are those defaults.

One speaker in the video raises an interesting question toward the end: are we just reinventing computer science? Stack engineering, hash engineering — will those be next? Maybe. But the more grounding observation is this: every generation of software complexity has produced a new organisational layer. Procedures replaced raw assembly. Objects replaced procedures. Microservices replaced monoliths. Graph engineering is the organisational layer for coordinated AI agents. That it resembles what came before is not a criticism. It is evidence that the problem is real and the solution is recognisable.

If you are building something with one agent doing one focused job in one loop, graph engineering is overhead you do not need. Keep it simple.

If your system has genuinely independent sub-tasks that could run in parallel, distinct specialists that each need their own context, conditional paths where different inputs need different handling, or irreversible actions that need a human in the loop, that is when a graph earns its place.

Start with the state schema. Draw the graph on paper before writing code. Identify which nodes accumulate and which overwrite. Then build, one pattern at a time.

The structure is the system. Get that right and the agents have somewhere sensible to work.

Graph Engineering was originally published in Towards AI on Medium, where people are continuing the conversation by highlighting and responding to this story.

── more in #ai-agents 4 stories · sorted by recency
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/graph-engineering] indexed:0 read:12min 2026-09-07 ·