24 · Graph Engineering
Graph engineering taught from Codex and crewAI source plus Anthropic's production data: organizing work across agent, code, tool, and human nodes.
Chapter brief
Question to answer
When a task no longer fits one loop, how should work split across agent, code, tool, and human nodes?
By the end, you can
- Choose an executor among agent, code, tool, and human nodes
- Define payload, state owner, budget, and failure semantics on every edge
- Estimate delegation cost and place human gates
- Read this now if
- Engineers deciding on workflows, multiple agents, human gates, or asynchronous orchestration
- Prerequisites
- Understand a single-agent loop; Understand state ownership
- Deliverable
- A node, edge, state, and acceptance specification for one real task
- Evidence boundary
- Cases and production data depend on workload and organizational context; they do not prove universal multi-agent gains
Decide whether to split one real task
Section titled “Decide whether to split one real task”Task: read the official docs for eight agent platforms, extract versions and capabilities, produce a cited comparison, and publish the recommendation to the team’s knowledge base.
Put everything into one agent loop and familiar failures appear: retrieval crowds out writing context, version extraction repeats, missing citations surface only at the end, and publishing shares permissions with research. Do not jump straight to “use multiple agents.” Choose the executor for each subtask first:
| Subtask | Preferred node | Why |
|---|---|---|
| Fetch pages and parse version fields | Code or tool node | Deterministic and testable; no reason to keep spending model tokens |
| Map equivalent concepts across vendor vocabulary | Agent node | Requires semantic judgment that is hard to enumerate fully |
| Check whether citations cover claims | Code check plus reviewer node | Deterministic coverage first, semantic quality second |
| Publish to the team knowledge base | Tool node behind a human gate | Irreversible outbound action requires explicit approval |
Split only when at least one condition is true: independent work can run in parallel, contexts contaminate each other, permissions must differ, or a human must become a formal gate. Otherwise keep one loop; every avoided handoff avoids information loss.
Graph engineering owns two decisions: which node executes, and what every edge guarantees during handoff. There are four node types: improvisational but expensive agents, deterministic code, model-facing tools, and humans who approve or judge. Every edge states at least goal, context, permissions, budget, and output format.
The deliverable is not a pretty diagram. It is an acceptance-ready graph specification: each node’s input and output, state owner, failure route, budget, and human gate can be tested.
What an edge carries: three independent sources, one answer
Section titled “What an edge carries: three independent sources, one answer”The most underrated element in a graph is the edge. Split nodes beautifully and the graph still collapses if a handoff drops context, leaks permissions, or ships without a budget. Three unrelated systems converged on nearly the same answer for what one edge must carry.
Hermes’s delegate_task:
delegate_task(goal, context, toolset, max_iterations)# goal context permission allowlist budgetcrewAI (commit f15844b), tools/agent_tools/delegate_work_tool.py:
class DelegateWorkToolSchema(BaseModel): task: str = Field(..., description="The task to delegate") context: str = Field(..., description="The context for the task") coworker: str = Field(..., description="The role/name of the coworker")Anthropic’s orchestrator prompt requires every subtask description to include: an objective, an output format, tool guidance, and clear task boundaries. They also left the counterexample on record: early on, the lead agent could issue one-liners like “research the semiconductor shortage” — one subagent went off exploring the 2021 automotive chip crisis while two others duplicated 2025 supply-chain searches. Underspecified edges produce duplicated or drifting work.
Put together, a complete edge has five elements: goal, context, permissions, budget, output format. Skip one and that’s the one that blows up at runtime.
The return payload has a copyable implementation too. In smolagents (agents.py:868, commit e3a5b89), a managed agent doesn’t return a bare conclusion: the answer is wrapped in a fixed report template, optionally followed by a <summary_of_work> section of truncated key steps. The parent receives conclusion plus evidence — it can judge quality instead of trusting blindly.
What an edge must not carry: permissions only narrow
Section titled “What an edge must not carry: permissions only narrow”This discipline is written in black and white in Codex’s source. codex-rs/core/codex_delegate.rs (commit fa1d4c4), spawning a subagent:
inherited_exec_policy: Some(Arc::clone(&parent_session.services.exec_policy)), // L130inherited_multi_agent_version: Some(MultiAgentVersion::Disabled), // L143Line one: the child forcibly inherits the parent’s exec policy — no “spawn a child to bypass permissions” backdoor. Line two is harsher: the child’s multi-agent capability is set to Disabled at construction — a subagent is born unable to spawn subagents. The recursion ban isn’t a prompt plea; it’s a constructor argument.
Hermes’s counterpart hard-removes 5 tools from every child’s toolset: delegating again, asking the user, writing long-term memory, sending cross-channel messages, executing code. Both systems agree on one sentence: an edge is where permissions narrow, never where they widen.
Five topologies, two readable implementations
Section titled “Five topologies, two readable implementations”Ways to organize work collapse into five shapes. The first four have implementations you can read today in crewAI and Anthropic’s system.
Pipeline with gates. Fixed, ordered steps chained with deterministic checks between them. crewAI’s Process.sequential is exactly this: tasks run in order, each task’s output feeding the next as context. Precondition: the steps can be fixed in advance.
Triage routing. A cheap node at the entrance classifies, then routes to a specialized lane. Each lane’s prompt and toolset stays narrow and sharp; an inaccurate router is worse than no triage.
Parallel fan-out. Independent subtasks run simultaneously: sectioning (each owns a slice) or voting (same question, multiple runs, compare). Anthropic’s numbers: the lead spawns 3–5 subagents in parallel, each using 3+ tools in parallel, cutting complex-query time by 90%. The price is aggregation logic — decide who resolves conflicts up front.
Orchestrator and workers. When subtasks can’t be listed in advance, an agent node orchestrates at runtime. crewAI’s Process.hierarchical implements this explicitly, and enforces its own precondition with a validator — crew.py’s check_manager_llm raises unless you provide manager_llm or manager_agent. The orchestrator isn’t decoration; it’s the topology’s definition.
Orchestration also needs an effort scale: how many workers for how big a job? Anthropic wrote the scale straight into the prompt: simple fact-finding, 1 agent with 3–10 tool calls; direct comparisons, 2–4 subagents with 10–15 calls each; complex research, 10+ subagents with divided responsibilities. Worth stealing — they added it because early versions would spawn 50 subagents for a simple query.
Review loop. One node works, another critiques, cycle until pass. The legitimate cycle in the graph — but it must carry an exit: a round cap plus “stop when comments stop changing.”
Real systems are composites. Anthropic’s full flow: orchestrator-workers (parallel search) → synthesis → a deterministic CitationAgent bolted on as a pipeline tail to attach citations. Three shapes in one graph.
Who owns state: blackboard or mail
Section titled “Who owns state: blackboard or mail”Multiple nodes working means state questions: where do intermediate results live, who may write.
Two base schemes. Shared blackboard: everyone reads and writes one state. Convenient, but write access must be policed — Hermes banning children from writing long-term memory is blackboard policing: unverified child conclusions written into the parent’s memory poison the well. Message passing: information moves only through edge payloads; state stays private. Better isolation, at the price of designing every payload.
Practice mixes both. One Anthropic detail deserves attention: the lead agent saves its research plan to Memory before spawning workers — because context beyond 200K tokens gets truncated, and the plan must live outside the loop to survive truncation. That’s the blackboard’s correct use: things that must outlive any single context window. Todo lists are the same category; chapter 21 covers four implementations.
At the principle level, 12-Factor’s Factor 5: unify execution state and business state. Store “which step the graph is on” and “what state the business data is in” separately and they will eventually disagree. An event log carries both — each agent node’s log concatenated is the whole graph’s execution history (see Loop Engineering, component 4).
Humans are nodes, not exceptions
Section titled “Humans are nodes, not exceptions”Most systems implement human involvement as a special case: popups, blocking waits. Graph engineering treats humans as a regular node type: trigger conditions, inputs, outputs, timeout behavior.
The mechanism is 12-Factor’s Factor 7: the model contacts humans through tool calls. request_approval(action, reason) and ask_human(question) are ordinary tools. Human involvement lands in the event log — replayable, countable.
Three design points. Routing: whose screen? Codex routes everything back to the parent session; children never own UI — a user facing three popups can’t tell which is which. Timeouts: fallbacks (conservative defaults, suspension) defined in advance; the graph can’t hang on someone’s evening off. Granularity: grade by irreversibility — reads pass, reversible writes report after the fact, irreversible actions (outbound messages, deletions, spending) approve first.
Freezing: the direction graphs evolve
Section titled “Freezing: the direction graphs evolve”Static regions (code picks edges) and dynamic regions (the model picks edges) share one graph, and the border moves. The healthy direction is freezing from dynamic toward static.
The test is one question: did this path’s last 100 executions take the same route? If yes, rewrite the agent node as a code node — no tokens, no bad days, unit-testable. Anthropic’s CitationAgent is a live example: attaching citations is a fixed path, so it was split out of the main loop into a dedicated pipeline-tail node. The 12-Factor author’s observation points the same way: the best production “AI products” are mostly deterministic code with LLM steps only where improvisation is genuinely needed.
The reverse holds too: a hardcoded flow whose exception branches outgrow its if-else chain wants improvisation — hand it to an agent node. Graph engineering is continuous rebalancing.
Runaway protection
Section titled “Runaway protection”In a dynamic graph the model can open nodes on its own, so guardrails go into code, not prompts.
- Depth caps. Hermes hardcodes MAX_DEPTH=2; Codex sets the child’s
MultiAgentVersion::Disabled(source above) — a depth cap of 1 enforced by the type system. - Recursion bans. The delegation tool isn’t passed down. Blocks the child-spawns-grandchild explosion — Anthropic’s 50-subagent incident is what no quantity gate looks like.
- Concurrency caps plus heartbeats. Cap active nodes; long-running nodes emit heartbeats (Hermes: every 30s) so the outside can tell “working” from “dead.”
- Budgets travel down edges. Every edge carries max_iterations; the parent’s budget is a hard ceiling over its children’s sum.
Build it yourself: when you actually need a graph
Section titled “Build it yourself: when you actually need a graph”Honest answer: usually you don’t. Every edge is a context retelling and every retelling drops information; multi-agent token costs are multiplied (that’s exactly why it works, and why it’s expensive). Anthropic states plainly their architecture wins only on breadth-first queries with parallelizable directions.
Split when three signals appear:
- Contexts poisoning each other. Two workstreams crowding one window — split into two nodes.
- Permissions need layering. Part runs free, part locks down; edges are natural permission boundaries.
- A human needs in. Model the approval point as a human node instead of a popup inside the loop.
Then work in order: write each edge’s five elements (goal, context, permissions, budget, output format), settle state ownership (plan on the blackboard, details as messages), install guardrails (depth, concurrency, recursion bans). Node internals are the least of it — every agent node is a standard loop, and Loop Engineering’s components apply as-is.
Common traps
Section titled “Common traps”Graphs for the sake of graphs. Seven nodes for one loop’s work buys seven rounds of handoff loss. Node count is a cost, not a maturity score.
One-line delegation. “Research the semiconductor shortage”-grade vagueness made Anthropic’s workers duplicate and drift. All five edge elements, every time — output format is the one most often forgotten.
Conclusions without evidence on edges. Copy smolagents: a report template plus optional work summary. Parents must be able to judge child conclusions.
Guardrails in the prompt. “Please don’t recurse” is a request; MultiAgentVersion::Disabled is a constructor argument. Models forget requests in long contexts; they can’t route around code.
Human nodes without timeouts. Approval goes out, the human’s on vacation, the graph hangs a week. Every human node gets a timeout and a fallback.
One model for every node. Anthropic’s recipe is heterogeneous by design: Opus orchestrates, Sonnet works. Per-node model selection is a direct dividend of splitting — use it.
What to carry forward and the next experiment
Section titled “What to carry forward and the next experiment”Keep one loop until parallel value, context contamination, permission layering, or a human gate justifies a split. After splitting, every edge carries goal, context, permissions, budget, and output format. Node count, agent count, and graph complexity are not maturity metrics.
Use the opening eight-platform research task as the next experiment. Run a single-loop baseline, then split only a deterministic version-extraction node and a citation reviewer. Compare total tokens, wall-clock time, duplicate retrievals, missing citations, and human correction time. Add more nodes only when at least one quality or time metric improves and the added cost is explainable.
Appendix: open when you want to review
Section titled “Appendix: open when you want to review”Six checks and three exercises
Check questions
Section titled “Check questions”Q1 · Basics: How does graph engineering relate to graph frameworks like LangGraph? One sentence on what graph engineering governs.
Frameworks are implementation plumbing; graph engineering is the design decision you face with or without one. It governs two things: how nodes are divided (which work goes to a model, which to deterministic code, which to a human) and how edges are wired (what a handoff carries, how results return, who owns failures).
The four node types are a complete option set: agent nodes (costly, slow, improvises — for unwriteable paths), code nodes (cheap, testable — for known paths), tool nodes (model-facing code interfaces), human nodes (approvals and judgment). Any “multi-agent architecture” decomposes into combinations of these four.
Source: the crewAI and Codex implementations in this chapter’s research notes. Follow-up: “Which part of graph engineering does LangGraph’s StateGraph correspond to?” It implements edge execution (message passing, checkpoints) for you. How nodes divide and what edges carry remain your design decisions — the framework doesn’t answer those.
Q2 · Edge design: Which five elements must a delegation edge carry? What incident does each omission cause?
Goal, context, permissions, budget, output format. Three independent sources converge on this: Hermes’s delegate_task(goal, context, toolset, max_iterations), crewAI’s DelegateWorkTool(task, context, coworker), and Anthropic’s orchestrator prompt (objective / output format / tool guidance / task boundaries).
Incidents, matched up: no goal → drift; no context → guessing; no permissions → overreach; no budget → runaway; no output format → duplicated work. Anthropic has the real case on record: the one-liner “research the semiconductor shortage” sent one worker off to the 2021 automotive chip crisis while two others duplicated 2025 supply-chain searches.
The return leg is part of the edge too: conclusions must carry evidence. smolagents wraps a managed agent’s answer in a report template plus a work summary, so the parent can judge quality.
Source: research/crewAI/.../delegate_work_tool.py (f15844b); smolagents agents.py:868.
Follow-up: “Which of the five gets forgotten most?” Output format. Everyone writes goals and context; skip the format and the parent ends up writing parser code for every freestyle reply.
Q3 · Permissions: Why is “an edge is where permissions narrow, never widen”? Give code-level evidence.
Because a parent can’t supervise a child step by step, any widening is an unwatched opening. Classic incidents: a child holding tools the parent lacks (escalation), a child spawning children (recursive blowup), a child writing the parent’s long-term memory (unverified conclusions poisoning the well).
The code evidence is in open-source Codex, codex_delegate.rs (commit fa1d4c4): L130 inherited_exec_policy — the child forcibly inherits the parent’s exec policy, no bypass channel; L143 inherited_multi_agent_version: Some(MultiAgentVersion::Disabled) — the child is born unable to delegate. The recursion ban is a constructor argument, not a prompt plea.
Hermes’s equivalent: 5 tools hard-removed from every child’s toolset (delegate, ask-user, memory-write, cross-channel send, code exec).
Source: research/codex/codex-rs/core/src/codex_delegate.rs L130/L143.
Follow-up: “Why can’t guardrails live in the prompt?” Models forget requests in long contexts but can’t route around hardcoded constants. “Please don’t recurse” is a request; Disabled is law.
Q4 · Cost judgment: Anthropic’s data shows multi-agent beating single-agent by 90.2% — why should you still default to not splitting?
Look at the attribution: within that gain, token usage alone explained 80% of the performance variance. Multi-agent works mainly because separate context windows let the system spend more tokens per problem — buying performance with money, not architecture magic.
So the criterion is economic: does the task’s value cover a multiplied token bill, and can the subtasks actually parallelize? Anthropic’s own boundary is “breadth-first queries with parallelizable directions”; splitting a linear task only adds handoff loss (every edge is a context retelling, every retelling drops information).
The proper triggers are three: contexts poisoning each other, permissions needing layers, a human needing in. Without one of those, one loop plus good tools is optimal.
Source: the BrowseComp variance analysis in Anthropic’s multi-agent report. Follow-up: “How to set the orchestrator’s effort scale?” Copy Anthropic’s prompt-embedded scale: simple facts, 1 agent, 3-10 calls; comparisons, 2-4 workers at 10-15 calls; complex research, 10+ workers with divided duties. They added it after early versions spawned 50 subagents for a simple query.
Q5 · Evolution: What does “freezing from dynamic toward static” mean? Give an operational test and a real example.
Dynamic regions (model picks edges) and static regions (code picks edges) share one graph, and the border moves. The healthy direction is rewriting stabilized dynamic paths into deterministic code: no tokens, no bad days, unit-testable.
The operational test in one sentence: did this path’s last 100 executions take the same route? If yes, freeze it into a code node.
Real example: Anthropic’s CitationAgent. Attaching citations follows a fixed path (take conclusions and documents, locate sources, insert markers), so it was split out of the main loop into a dedicated pipeline-tail node. The reverse also holds: a hardcoded flow drowning in exception branches wants improvisation — hand it to an agent node.
Source: the architecture diagram in Anthropic’s report (LeadResearcher → Subagents → CitationAgent). Follow-up: “Does freezing kill the agent’s flexibility?” It freezes paths that no longer need flexibility. Graph engineering is continuous rebalancing — check quarterly which dynamic paths have worn into straight lines.
Q6 · Open-ended: Design an automated refund-request system. Draw your nodes and edges — where do agents, code, and humans go?
One reference answer (not unique; the reasoning is what’s graded):
Entry triage is a code node (or a small model): route by amount and order status — the rules are clear, no improvisation needed. Small refunds matching policy go through a code node directly: the path is 100% fixed; an agent there is waste. Disputed cases go to an agent node: reading chat history, checking logistics, judging responsibility — unwriteable paths. Amounts over a threshold hit a human node: irreversible actions (sending money) approve before, via a tool call request_approval(order, reason), 24h timeout suspending to a human queue.
Edge design: the agent node’s toolset holds query tools only, no payment tool (permissions narrow); budget 15 steps; output format fixed as “verdict + three pieces of evidence + recommended action.” Payment is always executed by a code node; the agent only produces recommendations.
Only one node in this design is an agent — and that’s correct. Graph-engineering maturity is measured by whether each node type is used where it belongs, not by agent count.
Source: topologies map to crewAI’s two Process modes; approval routing maps to Codex’s route-to-parent design.
Follow-up: “What if the dispute agent’s verdict quality is unstable?” Add a review loop: a second, cheaper model audits against a checklist before release, round cap 2. Cheaper than upgrading the main model.
Exercises
Section titled “Exercises”- Read source: open
research/crewAI/lib/crewai/src/crewai/crew.py, find thecheck_manager_llmvalidator, and answer: why does hierarchical mode require a manager while sequential doesn’t? - Extend the design: give the human node in the refund system (Q6) a full definition: trigger condition, inputs/outputs, timeout, fallback path. Write it as a tool’s JSON schema.
- Audit: pick a multi-agent framework you’ve used (or your own delegation code) and check its delegation edge against the five elements: which is missing? Are the guardrails in the prompt or in code?
Research notes
Section titled “Research notes”This chapter is grounded in first-hand material (repos cloned into this workspace’s research/ directory; line numbers refer to the listed commits):
| Source | Version | What was read |
|---|---|---|
| openai/codex | fa1d4c4 | codex-rs/core/src/codex_delegate.rs (policy inheritance L130, recursion ban L143), protocol/src/protocol.rs SubAgentSource (L2822) |
| crewAIInc/crewAI | f15844b | process.py (sequential/hierarchical), crew.py check_manager_llm, tools/agent_tools/delegate_work_tool.py |
| huggingface/smolagents | e3a5b89 | agents.py managed agents’ __call__ (task template, report template, summary_of_work) |
| How we built our multi-agent research system | Anthropic | the 90.2% gain, token-variance 80%, the four delegation elements, effort scales, the 50-subagent incident, CitationAgent |
| Building Effective Agents | Anthropic | workflow/agent distinction, the five patterns’ origin |
| 12-Factor Agents | HumanLayer | Factors 5 / 7 / 10 |
For the four harnesses’ subagent machinery side by side (OpenClaw’s push events, Hermes’s 5 hard-blocked tools, source included), see chapter 10 and chapter 12 — that’s the line-by-line implementation detail; this chapter is the method.
For how each node runs reliably inside, go back to Loop Engineering.