Graph Engineering: A Crash Course
16 Concepts · From a spine one loop reads to a graph a thousand agents share
Your loop works. It fires every morning at 9am, the harness fences it in, and its spine, one progress.md file, carries what it learned from yesterday into today. One loop, one memory file. That was enough.
Now watch what happens when you succeed. You add a second loop. Then a review loop. Then, one busy week, you fan out twenty agents to audit twenty files at once. Each agent starts with an empty context window. Each one rediscovers what another agent already found an hour ago. Each one writes its findings into a transcript that no other agent will ever read. The work multiplied. The memory did not. You built a team with no shared brain.
This is the problem graph engineering solves. The idea is one sentence long: the agent forgets, the graph does not. Instead of leaving what agents learn inside their transcripts, you make them write it down as typed, connected records, nodes and edges, that any later agent can query. Two graphs do this job. A commit DAG remembers the work: what was tried, what descended from what, what was kept. A knowledge graph remembers the facts: which entities exist, how they relate, and which source proves each claim. This course teaches both, using the clearest documented examples of 2026: Andrej Karpathy's autoresearch and AgentHub for the first, and Anthropic's Knowledge Graph Construction Cookbook and Dynamic Workflows for the second. And because the loops themselves need wiring too, Part 5 adds the third graph — the governance graph: who checks whom, who owns whose target, and which measurements no loop can argue with.
You need these first: Loop Engineering and Harness Engineering. The loop course taught the beat, the spine, the maker–checker split, and the ratchet. The harness course taught the five verbs and typed output. This course assumes all of it. The spine was one loop's private memory. This course is what the spine becomes when many agents must share it. If those words are new, do those courses first.
📚 Teaching Aid
View Full Presentation — Graph Engineering: A Crash Course
The ten figures below are built to be taught in the same order: each one carries a single concept and can stand alone on a slide.
Every concept below has a script you can execute and watch, not just read. They need bash, git, jq, and python3, and nothing else: no API key, no pip install, no network.
git clone https://github.com/panaversity/agentfactory-labs.git
cd agentfactory-labs/crash-course/graph-eng
./verify.sh # runs all 17 demos and asserts each one
When that prints Everything in this course runs., keep the folder open beside this page. Each concept ends with the one command that demonstrates it: you watch a git reset erase a commit, a schema reject a malformed reply, a checker demand a missing edge, and a pre-commit gate block six different violations. The lab README maps every concept to its demo.
If any of these are new, read the Loop Engineering and Harness Engineering courses first. This course connects the machinery those courses built.
New here? A 2-minute recap of what you should already know
progress.md) that a loop reads first and writes last, so the next beat knows what happened.main.
Key words in plain English
You will see these words throughout the course. Read this list once now, then return to it whenever a term feels unclear.
| Term | Plain-English meaning |
|---|---|
| Graph | A set of points (nodes) connected by arrows (edges). The arrows have direction, and direction carries meaning. |
| Node | One point in the graph: an entity, a claim, a commit, a source, an agent run. |
| Edge | One arrow between two nodes, with a label: supports, parent_of, produced, works_for. |
| DAG | Directed acyclic graph: arrows never loop back on themselves. Git history is a DAG. |
| Commit DAG | The graph of work: commits are nodes, parent links are edges. It answers "what was tried, and what came from what?" |
| Knowledge graph | The graph of facts: entities are nodes, typed relations are edges. It answers "what exists, and how is it connected?" |
| Entity | A thing the graph tracks: a person, a company, a file, a vendor, an incident. |
| Relation / triple | One fact as subject–predicate–object: (Vendor X, supplied, Component Z). |
| Surface form | A name exactly as it appears in a document: "Edwin Aldrin", "Buzz", "Col. Aldrin". Three surface forms, one person. |
| Entity resolution | Deciding which surface forms are the same real thing, and merging them into one canonical node — without losing the original names. |
| Provenance | The receipt attached to a claim: which source said it, which run extracted it, how confident the extraction was. |
| Claim | A statement the graph stores as possibly true — always with provenance, never as bare truth. |
| Subgraph | A small, relevant slice of the graph, cut out for one task. You hand agents subgraphs, never the whole graph. |
| Grounding | Forcing an answer or a verdict to point at real edges in the graph, instead of at its own impression. |
| Swarm | Many agents exploring, implementing, or evaluating at the same time. |
| Structured output | A model response constrained to a schema (for example, a Pydantic model), so code can validate it before believing it. |
| MCP | Model Context Protocol: the standard way an agent reaches an outside system. Not needed while the graph is files; the answer once it is a database. |
| Governance graph | The graph whose nodes are the loops themselves, plus human gates and anchors, and whose edges say who feeds, checks, and constrains whom. |
| Execution loop | A loop that does recurring work: triage the issues, review the PR, draft the changelog. |
| Improvement loop | A loop that watches a number against a target and adjusts the system doing the work. |
| Counter-metric | A second number, watched by a second loop, that catches the first number being gamed. |
| Anchor | A measurement no loop can argue with: a test that actually ran, a customer who actually stayed, money that actually arrived. |
| Frozen node | A rule or file the optimizing loops may never change — exactly because they would want to. |
The loop course gave loops a body metaphor: heartbeat, body, spine. This course adds one more: the graph is the shared brain — the memory that outlives any one agent's context window.
The short version: the primary work is real and public, but the viral framing of it is wrong. The "11-page PDF by two Anthropic seniors" is an independent study note that says on its own front page that it is not affiliated with or endorsed by Karpathy or Anthropic, and "1000x" is a slogan, not a measurement. Read the primary sources first. The timeline is short and public. On March 7, 2026, Karpathy released autoresearch: an agent locked inside a small training repo, running one five-minute experiment at a time and keeping only what improves the metric. It collected tens of thousands of GitHub stars within weeks, and Fortune called the pattern "the Karpathy Loop." Three days later he sketched AgentHub, "GitHub is for humans. AgentHub is for agents", a bare Git repo plus a message board where swarms coordinate through the commit DAG instead of a main branch. On March 23, 2026, Anthropic published its Knowledge Graph Construction Cookbook, which replaces a classical NLP pipeline with structured-output prompts: extract typed entities and relations, resolve duplicates, assemble a graph, query it with citations. And Anthropic's Dynamic Workflows (announced May 28, 2026, and since made generally available) lets Claude write the orchestration script that fans work out to parallel fresh-context sub-agents. On July 18, 2026, Peter Steinberger's twelve-word midnight question, "Are we still talking loops or did we shift to graphs yet?", gave the whole topic its name of the season; Part 5 tells that story and Carlos E. Perez's answer to it. Days later a viral post stitched everything together: "Two Anthropic seniors just made Karpathy's loop 1000x better with Graph Engineering — dropped 11-page PDF." Read the PDF's own first page before you repeat that. It says, in italics: independently compiled, not affiliated with Andrej Karpathy and Anthropic, and not endorsed. The synthesis is genuinely useful, this course draws on it, but it is a study note by an independent author, not an Anthropic paper, and "1000x" is a slogan, not a measurement. Same reflex that turned Steinberger's question into "loop engineering is dead" — Part 5 dissects that one; treat this one the same way. The primary sources are real and worth your time, with one caveat to carry: autoresearch and Anthropic's cookbook and workflow docs are public, while AgentHub was taken private shortly after release and now survives only through unlicensed forks (Concept 5 says more). One genuine convergence the meme missed, and it is stranger than the meme: on May 19, 2026, Karpathy joined Anthropic's pretraining team, to build a group focused on using Claude to accelerate pretraining research itself. So the loop tradition and the graph tradition in this course did meet at Anthropic. Just not in the way a viral post claimed, and not in that PDF. (All sources listed in Sources & further reading.)The full timeline, and what the meme got wrong
The industry uses "graph engineering" for two related things, and this course covers both. The first half is the memory graph: the durable, typed state your agents share — the commit DAG of work and the knowledge graph of facts. That is Parts 2 through 4. The second half is the governance graph: the wiring between the loops themselves — who feeds whom, who checks whom, where the human gate sits, and which measurements no loop can argue with. That is Part 5, built on Peter Steinberger's question and Carlos E. Perez's essay. The two halves are layers of one system, not rivals: the governance graph connects the workers, the memory graph stores what the workers know, and they meet in Concept 10, where a checker from the governance layer reads its evidence from the memory layer. The loop course ends exactly where this course begins, one loop, fully built, and hands both halves of the graph question here.
The six-step playbook, and where each step already lives
Most readers arrive here through a viral six-step version of the playbook. Four of its six steps are already behind you, and this course is the other two plus the part the list omits. So the honest version of "1000x" is not a benchmark. It is that steps 1 to 3 give you a capable worker, and steps 4 to 6 give a thousand of them one shared memory. Same model, different architecture.The six steps, mapped onto this series
The step, as the post puts it What it actually is Where you learned it 1. Build one loop: generate, critique, revise The maker–checker beat and the ratchet Loop Engineering 2. Add tools: search, code, database Connectors, and the tool schemas that fence them Loop and Harness Engineering 3. Go parallel: agents in separate worktrees Isolation, so concurrent work cannot collide Loop and Harness Engineering 4. Add a graph: typed nodes and edges, not transcripts Memory that outlives the session Parts 2 to 4 of this course 5. Ground the evaluator in edges, not vibes Verification with citable evidence Concept 10 of this course 6. The graph survives every session Provenance, supersession, and durable state Concepts 8 and 13, and Part 6
The mindset shift, in one picture

One note on tools, and it is a smaller note than in the sibling courses. Loop, harness, and eval work all had tool-specific spellings to teach. Graph work has almost none: the graph is files, Git, jq, and a schema you own, so there is nothing to buy and no product feature to configure. Claude Code and OpenCode appear here only as the workers that read from and write to it, and every command below works the same in both, with claude -p and opencode run interchangeable throughout. That tool-independence is not an accident of this course's scope. It is the strongest evidence that the graph is a discipline rather than a feature.
One term belongs here, because readers reasonably expect it and then mis-place it. MCP, the Model Context Protocol, is the standard way an agent reaches an outside system: a server exposes tools, and any MCP-speaking agent can call them. At this course's size, MCP does not appear at all, and that is correct: the graph is files in the repo, so the agent reads and writes it with the file and shell tools it already has. MCP becomes the answer at the next size up. When the graph moves into Postgres or Neo4j and several agents on different machines must reach it, you do not give every agent database credentials and a hand-rolled client. You put one MCP server in front of the store, exposing a small, typed surface: resolve an entity, fetch a bounded subgraph, append a validated claim. Every rule in this course then lives in that server, where it is enforced once, instead of in each agent's prompt, where it is only requested.
Keep three layers apart, because they are easy to blur. A skill is knowledge the agent loads: how we write claims here. MCP is the wire: how the agent reaches the store at all. The graph is the memory itself: what is actually remembered. A skill without a graph is advice with nothing to write to. A graph without a skill gets filled inconsistently. And MCP is neither of those; it is the plumbing that becomes necessary the moment the store stops being a file in the same folder as the agent.
True in late July 2026, with different shelf lives. Autoresearch is actively developed; AgentHub is frozen and private, so treat everything about it as history; Dynamic Workflows is now generally available and its limits and defaults move with the product; the Cookbook is a living notebook. Before you trust a flag, a limit, or a model name, check the live sources: github.com/karpathy/autoresearch, platform.claude.com/cookbook, code.claude.com/docs, opencode.ai/docs.
What this course covers
| Part | Topic | What you learn |
|---|---|---|
| 1 | The memory problem | Why transcripts fail as team memory, what a graph is, and the two graphs every swarm needs |
| 2 | The DAG of work | Karpathy's path: autoresearch writes history into Git, AgentHub turns the DAG into the collaboration layer |
| 3 | The graph of facts | Anthropic's path: extraction by schema, entity resolution, and provenance on every edge |
| 4 | Working from the graph | Subgraphs instead of dumps, and the grounded checker: "triple not found" beats "seems off" |
| 5 | The graph of loops | The governance layer: who checks whom, the four ways a single loop breaks, and the anchors no loop can argue with |
| 6 | One graph, end to end | The morning-triage loop's spine, upgraded into a small queryable graph — files and shell, both tools |
| 7 | Staying grounded | Choosing a level, the complexity budget, when not to build a graph, and the bridge to the next two courses |
| Live | Dogfooding | The proto-graph this book already runs, and the graph it deliberately does not |
| Practice | Projects | Eight graph builds, easy to hard |
Want to learn by doing? Read Part 6 first to see one finished graph. Then come back for the parts.
First time? Take the memory path: Parts 1 to 4, which is Concepts 1 to 10, then jump straight to Part 6 and build it. Skip Part 5 and every note marked "Going deeper." That is about two hours, or closer to three if you work the examples rather than reading past them. Then do Projects 1–3. After that you can draw your system as a graph, store claims with sources, and make a reviewer cite its evidence.
Second read (after your first graph has answered a question your transcripts could not): the governance path, which is Part 5, plus all of Part 7 and Projects 4–8. Part 5 is about keeping many loops honest, and that only becomes urgent once more than one loop writes to the same memory. Concept 15's warnings likewise only land once you have a graph you are tempted to over-build.
Two layers, aging differently. Remember the first. Look up the second.
- The lasting layer. The agent forgets; the graph does not. Work lineage and domain facts are two different graphs: do not collapse them. A schema is cheaper than a trained pipeline. Resolution must keep its receipts and stay reversible. Every claim carries provenance or is marked inference. Hand agents subgraphs, never the whole graph. Ground the checker in edges. Ask the six questions before choosing a level, and declare the budget before the run. Pair every optimizing loop with a watching loop on a counter-metric. At least one signal in the system must come from reality, not from another model's report. And a graph amplifies the builder's judgment: including the builder's mistakes.
- The mechanical layer. Every repo name, model name, flag, and star count below. Autoresearch's file layout, Dynamic Workflows' concurrency caps, the Cookbook's exact Pydantic classes: treat each as a pointer to the live source, not a fact to memorize. Where this course and the live docs disagree, the docs are right.
Part 1: The Memory Problem
1. The agent forgets; the graph does not
Run this one: bash concepts/01-agent-forgets.sh in the companion lab. Reading it is half; watching it happen is the other half.
Start with what you already own. Your morning-triage loop keeps a spine: progress.md, read first, written last. That file is memory, and it works: for one loop. Now count what it cannot do:
- A second loop cannot trust it.
progress.mdis prose. The changelog loop would have to read and interpret another loop's diary, and hope the format never drifts. - Twenty parallel agents cannot share it. Fan out twenty auditors and each starts empty. Agent 7 finds that
utils/dates.tshas a timezone bug. Agent 14, an hour later, finds it again. Nothing connected them. - You cannot query it. "Which findings from last month touched the payment code and were confirmed by a reviewer?": a spine cannot answer that. You would grep prose and hope.
- It cites nothing. The spine says "fixed the flaky test." Which test? Proven by which run? Superseded by which later fix? The prose does not know.
The naive fix is to copy transcripts around: paste agent 7's conversation into agent 14's context. This fails at exactly the moment you need it. Context windows fill. Costs multiply. And a transcript is the wrong shape for memory: it records everything that was said, in order, instead of what was established, with evidence.
Graph engineering is the deliberate alternative. Agents write what they learn as typed records: this is an entity, this is a claim about it, this is the source that supports the claim, this is the run that produced it. Records connect to records by labeled arrows. Any later agent, tomorrow's beat, a different loop, a different model entirely, queries the records it needs and continues. The independent PDF that popularized this course's topic puts it in five words, and they are the right five: the agent forgets, the graph does not.
A transcript is a chat log. A graph is a filing system. When a new employee joins a team, you do not hand them recordings of every conversation the team ever had — you hand them the files: organized, labeled, cross-referenced. Graph engineering is building the filing system your agents deserve.
Your triage loop and your changelog loop both need to know which PRs shipped this week. Today, each one runs Work is being re-done and re-interpreted in every context window: the exact "rebuilding the world from scratch" failure. The fix: the loop that first establishes "PR #212 shipped, fixes issue #98" writes it once as a typed record with provenance (the commit hash). Both loops then read the record. Derive once, query many times.git log and re-derives it. What is being wasted, and what is the graph-engineering fix?Show answer
2. What a graph is: nodes, edges, direction
Run this one: bash concepts/02-nodes-edges.sh in the companion lab. Reading it is half; watching it happen is the other half.
You met this definition at the end of the loop course; here it becomes the working tool. A graph is a set of points, called nodes, connected by arrows, called edges. Three properties do all the work:
- Nodes are typed. Not "a box," but "an Entity," "a Claim," "a Source," "a Commit," "an Evaluation." The type tells every reader what questions the node can answer.
- Edges are labeled and directed.
(claim_441) —supported_by→ (source_readme)means something different from the reverse arrow. Direction is meaning: who supports whom, who descends from whom, who checked whom. - Paths are answers. "Is Vendor X connected to Incident Y?" becomes "does a path of supported edges exist from the Vendor X node to the Incident Y node?" A question about the world turns into a walk through the arrows.
That third property is the payoff. Prose must be read and interpreted. A graph can be traversed: mechanically, by code, the same way every time. The moment your memory is a graph, questions that were vibes become queries.
One special shape matters enough to name. A DAG, directed acyclic graph, is a graph whose arrows never circle back. You have used one for years without the name: Git history. Every commit points to its parent. No commit is its own ancestor. Keep that example in your pocket, because Part 2 is built on it.
Nodes are nouns. Edges are verbs with a direction. A graph is a set of sentences, (subject, verb, object), drawn as a picture, so that a computer can follow chains of sentences without understanding prose.
Two agents both record that Vendor X supplied a part. One writes the sentence "Vendor X supplied component Z" into its log. The other writes Follow it. The sentence must be read and interpreted correctly by whatever model happens to see it. The edge can be traversed mechanically, the same way every time, and chained: from (vendor_x) —supplied→ (component_z) into a graph. Both are true. What can a later agent do with the second that it cannot do with the first?Show answer
vendor_x to component_z, then onward to whatever else touches component_z. That is how a question about the world ("is Vendor X connected to this incident?") becomes a walk through arrows instead of an act of reading comprehension. The typed edge also carries its receipt, which prose usually loses.
3. Two graphs, and why not to collapse them
Run this one: bash concepts/03-two-graphs.sh in the companion lab. Reading it is half; watching it happen is the other half.
Here is the distinction that organizes this entire course, and the one beginners most often blur. A multi-agent system needs two different graphs, because it must remember two different kinds of things:
| The commit DAG (Part 2) | The knowledge graph (Part 3) | |
|---|---|---|
| Remembers | Work: what was tried | Facts: what is known |
| Nodes | Commits, experiments, runs | Entities, claims, sources |
| Edges | parent_of, derived_from | supports, works_for, about |
| Answers | What changed? What descends from the batch-size experiment? Which lineages are still alive? | Which entities exist? How are they related? Which source backs this claim? Which claims conflict? |
| You already own a version | Git history, partly: see Concept 4 | Nothing yet: Part 3 builds it |

Why keep them separate? Because they have different truth rules. A commit is a fact by construction: it happened, Git guarantees it. A claim in a knowledge graph is a statement with evidence: it might be wrong, mis-extracted, or superseded, which is why every claim edge carries provenance and a confidence. Collapse the two and you either treat guesses as history or bury history under guesses.
They do connect. A production system links them with edges of their own:
(agent_run_183) —produced→ (claim_441)
(agent_run_183) —modified→ (commit_a81f)
(claim_441) —about→ (entity_vendor_x)
(claim_441) —supported_by→ (source_contract_pdf)
(claim_441) —supersedes→ (claim_238)
Read that block slowly: it is the whole course in five lines. A run did work (a commit) and learned something (a claim). The claim is about an entity, is backed by a source, and replaces an older claim. Work lineage on the left, domain knowledge on the right, joined but never merged.
The commit DAG is the lab notebook: experiments, dated and in order, with a parent for each one. Keep the failures in it too, which is a choice Concept 4 shows autoresearch declining to make and Concept 5 shows AgentHub making. The knowledge graph is the encyclopedia the lab is writing: what we now believe, with footnotes. A lab that keeps only the notebook cannot answer questions. A lab that keeps only the encyclopedia cannot show its work. Keep both, and cross-reference them.
An agent reports: "I refactored the parser (commit The refactor lives in the commit DAG: commit 9fc2), and while doing it I confirmed the vendor's API rejects dates before 1970." Where does each half of that sentence live?Show answer
9fc2, with its parent link, automatically. The API behavior is a claim for the knowledge graph: (vendor_api) —rejects→ (pre-1970 dates), with provenance pointing at the run and the evidence (the error response). Today, that second fact dies in the transcript. That is the loss this course exists to stop.
Most systems should not build a knowledge graph, and you deserve to know that before you read four parts about how. The short test, in full at Concept 15: if your tasks are independent, your answers come from one document at a time, your relations are fixed and simple, a relational table already answers every query you actually ask, and nobody needs provenance, then stop here. One loop with a spine is the right answer, and adding a graph buys you extraction errors and schema upkeep in exchange for questions nobody asked.
The bill flips when two loops must exchange facts, when a synthesis spans many workers, when relations keep evolving, or when someone will one day ask "how do we know that?" Read on if any of those is true, or is about to be. Concept 14 turns this into six ordered questions.
Part 2: The DAG of Work
4. Autoresearch: the ratchet writes its history into Git
Run this one: bash concepts/04-autoresearch-ratchet.sh in the companion lab. Reading it is half; watching it happen is the other half.
The loop course taught you the ratchet: try one change, evaluate, keep it only if the number improves, otherwise revert. Karpathy's autoresearch (March 7, 2026) is that exact loop, pointed at ML training, and its importance for this course is one design choice: the loop's memory is not a transcript. It is the commit DAG.
The setup is three files inside a small single-GPU training repo:
prepare.py, fixed data preparation and evaluation. The agent may not touch it. (A frozen node, the harness course's deny rule, applied to a file.)train.py: the model, optimizer, and training loop. The one surface the agent edits.program.md— natural-language instructions: the metric, the budget, the commit and revert rules, when to escalate. (The loop course called this "programming the program.")
Then the beat, forever: read train.py and recent history, propose one motivated change, commit it, train for about five minutes, measure validation loss. Improved? The commit stays. Worse or crashed? Reset to the last retained commit. Either way, record the result and continue: no human in the loop.
Now look carefully at what each beat leaves behind, because this is where beginners, and several popular write-ups, get autoresearch wrong. It has two memories, not one, and they keep different things:
| What it holds | Truth standard | |
|---|---|---|
| The Git branch | Only the retained improvements, as a rising chain of commits on a dedicated experiment branch | Verified: every commit on it beat the metric |
results.tsv | Every attempt: commit hash, metric, memory used, kept or discarded or crashed, and what was tried | Complete: it records attempts, not achievements |
The detail that matters is what happens on failure. The instructions say it plainly: if the metric improves you advance the branch and keep the commit, and if it is equal or worse you git reset back to where you started. A reset does not park the attempt on a side branch. It removes that commit from the branch. So the discarded experiment survives in results.tsv, and there only. And results.tsv is deliberately left untracked by Git.
Read that as a design rather than an oversight, because it is the cleanest illustration of Concept 3 you will find in the wild. Git holds work whose value was proven. The TSV holds the honest record of everything attempted, including the crashes. A human researcher carries hypotheses, failed attempts, and parameter interactions in working memory and loses them; autoresearch writes both kinds down, in two places, under two standards. What it does not do is keep alternative lineages alive and traversable: a discarded idea becomes a row of text that no other agent can query. That gap is exactly what Concept 5 fills.
The reported numbers from its first weeks — roughly 630 lines of core code, about 700 experiments in two days, around 20 retained optimizations — matter less than the shape. The repo drew tens of thousands of stars not because the optimizations were profound but because the pattern is legible: small code, visible metric, two honest logs. (Check the live repo before quoting any number: it moves weekly.)
The four conditions that make it work are the loop course's checklist, word for word: the output is verifiable (a validation number), the action is reversible (git reset, which undoes the attempt but does not preserve it), the horizon is short (five-minute runs), the environment is bounded (one repo, one editable file). Nothing new: except where the memory lives.
Autoresearch is your ratchet loop with the memory moved out of the agent's head and into two files. The Git branch keeps what worked. results.tsv keeps what was tried. The loop can crash and restart without losing its bearings, because neither memory lives in a context window. But notice the honest limit: a discarded experiment leaves a row in a text file, not a node anyone can traverse.
Open any repo you have worked on with an agent and ask the DAG the questions a spine cannot answer:
git log --oneline --graph -20 # the DAG, drawn
git log --follow -- path/to/file.ts # every experiment on one surface
git diff HEAD~5 HEAD -- src/ # what five beats of work changed
Then ask your agent: "Read the last 20 commits. What was the apparent metric, and which commits look like retained improvements?" Notice what the DAG cannot tell you: which experiments were tried and thrown away. In autoresearch that answer lives in results.tsv, outside Git. Concept 5 is about turning those rows into nodes.
5. AgentHub: traverse the search graph, do not merge to main
Run this one: bash concepts/05-agenthub-traversal.sh in the companion lab. Reading it is half; watching it happen is the other half.
Three days after autoresearch, Karpathy posted the next step: the loop should become "asynchronously massively collaborative for agents, think: SETI@home style" — emulate a research community, not one PhD student. AgentHub is his sketch of that layer: one Go binary, one SQLite database, one bare Git repository, one API key per agent, and a message board. Its slogan is the thesis: "GitHub is for humans. AgentHub is for agents."
Start from the gap Concept 4 left. A single ratchet resets its failures away, so the only record of a discarded idea is a row in an untracked text file that no other agent can query. AgentHub's central move is to stop resetting and start keeping: agents push commits as bundles, every pushed commit becomes a durable node, and nothing is required to converge on a main branch. The alternatives stay alive and traversable. That is the difference between a text log of failures and a graph of them.
Why does agent collaboration need different plumbing? Because swarms invert every assumption human Git carries:
- Thousands explore at once. Human repos assume a handful of branches; a swarm makes thousands of simultaneous attempts normal.
- Most results never merge: on purpose. For humans, an unmerged branch is unfinished business. For a swarm, a failed experiment is evidence: it tells every other agent that an idea fails under one condition.
- The primary operation changes. Not "merge this into main" but "traverse the search graph." There is no required main branch, no pull requests, no merge queue, no assumption that any one leaf is canonical.
The CLI makes the shift concrete — each command is a graph query wearing work clothes:
ah push # publish my commit as a new node
ah children <hash> # what was tried on top of this result?
ah leaves # the frontier: results nobody has built on yet
ah lineage <hash> # the full ancestry path that produced this outcome
ah diff <a> <b> # compare any two experiments, related or not
ah log --agent X # one agent's trail through the search
children asks which ideas were explored above a result. leaves reveals unexplored frontier. lineage reconstructs how an outcome was reached. Try asking those questions of a conventional branch model and feel how awkward they are: the DAG-as-graph view makes them one command each.

The message board is the social layer, and it completes the memory story. An agent does not need every prior transcript. It queries relevant lineages, reads a few summaries, fetches one commit, and continues. The independent PDF names this precisely: graph-grounded context construction — retrieve the connected state needed for the current decision, instead of replaying the entire history. Hold that phrase; Concept 9 generalizes it.
Two honesty notes, and the second matters for anyone trying to follow along.
First, in the repo's own words: "Work in progress. Just a sketch. Thinking..." AgentHub had no answer for trust between agents, malicious bundles, storage at scale, duplicate detection, or long-term indexing. That does not weaken the lesson: it sharpens it. The sketch identifies exactly which human abstractions fail first when agents become numerous: a single main branch, human-paced review, transcript memory, and merge-centered collaboration.
Second: AgentHub is no longer public. It drew a couple of thousand stars within a day of its March 2026 release, and was then taken private: github.com/karpathy/agenthub now returns a 404, and the clearest public account is from a developer who forked it beforehand and documented the design afterwards ("Karpathy open-sourced AgentHub last week. Then the repo went private."). Preserved forks circulate, but the original carried no license file, so treat any copy as a historical artifact for study rather than maintained software you may build on. Do not go looking for it as a dependency. Study the shape, which is what transfers: commits as nodes, bundles as the push unit, traversal as the primary operation, and a message board where a discarded result still teaches.
GitHub assumes one shared "official" version that everyone works toward. A swarm does not want one official version: it wants a map of everything that was tried, including the dead ends, because dead ends teach. AgentHub keeps the map and throws away the ceremony.
AgentHub is the memory layer for a swarm; something still has to run the swarm. Anthropic's Dynamic Workflows, announced for Claude Code on May 28, 2026 and since made generally available, is the clearest production example: instead of you writing a fan-out script, Claude writes an orchestration program for the current task — glob the files, spawn an auditor per file, filter the findings, spawn reviewers to try to refute them, then one synthesizer to produce a cited report. The official description is tens to hundreds of parallel sub-agents in a single session, each with fresh context, with results checked before they are folded in, and progress saved so an interrupted run resumes instead of restarting. You start one by asking Claude for a workflow, or by switching on the ultracode setting, which raises the effort level and lets Claude decide when a workflow is warranted.
The advertised demo is the Bun port: Jarred Sumner moved Bun from Zig to Rust using dynamic workflows, producing roughly 750,000 lines of Rust with 99.8% of the existing test suite passing, eleven days from first commit to merge. Anthropic notes it is not yet in production.
Two warnings travel with the feature. It consumes substantially more tokens than an ordinary session, which is why the first workflow asks you to confirm and why administrators can disable workflows outright. And parallel workers make correlated errors: a verification wave only helps if the reviewers have a different prompt, evidence set, or role. (The announcement post says "tens to hundreds," but the reference docs are more precise, and they are where you should design against: a "Behavior and limits" table gives up to 16 concurrent agents — fewer on machines with limited CPU cores — and 1,000 agents total per run. Check the docs for the current numbers before you build against them.) The deep question the feature leaves open, where hundreds of fresh-context workers put what they learn, is Part 3's job.
In AgentHub, agent A's experiment fails: a larger batch size crashes at a memory limit. Under GitHub thinking, the branch is abandoned and forgotten. What happens to it under graph thinking, and who benefits? The failed commit stays in the DAG as a node, and a message-board post cites its lineage: "batch 64 exceeds memory on this hardware — branch from the depth change instead." Every future agent that queries Show answer
children on that lineage inherits the warning without re-running the crash. The failure became shared memory. That is the entire difference between a swarm and a crowd.
Part 3: The Graph of Facts
Git gave you the commit DAG for free. The knowledge graph you must build. For decades that meant a trained NLP pipeline: a named-entity model, a relation classifier, a deduplication heuristic, each needing labeled data and maintenance. Anthropic's Knowledge Graph Construction Cookbook (March 23, 2026) shows the 2026 version: the whole pipeline collapses into structured-output prompts. Four stages, three concepts.

6. Extraction: the schema is the training data
Run this one: python3 concepts/06-extraction.py in the companion lab. Reading it is half; watching it happen is the other half.
Stage one turns unstructured text into typed pieces. You define the shape you want as a schema, and constrain the model's output to it:
The shape below is abbreviated for reading: EntityType, ExtractedGraph and PROMPT are yours to define, and the Cookbook notebook has the runnable version. What matters here is the three design decisions, not the line count.
class Entity(BaseModel):
name: str
type: EntityType # your enum: PERSON | ORG | PROJECT | ...
description: str # context, used later for resolution
class Relation(BaseModel):
source: str # subject, a name that must appear in entities
predicate: str # short verb phrase: "commanded", "supplied"
target: str # object, likewise
class ExtractedGraph(BaseModel):
entities: list[Entity]
relations: list[Relation]
def extract(text: str, client) -> ExtractedGraph:
response = client.messages.parse(
model="claude-haiku-4-5", # cheap model: extraction is volume work
max_tokens=4096,
messages=[{"role": "user", "content": PROMPT.format(text=text)}],
output_format=ExtractedGraph, # the schema constrains the reply
)
return response.parsed_output
Read the design, not the syntax. Three decisions carry the lesson:
- The Pydantic schema is the only "training data." No labeled corpus, no fine-tuned NER model. The schema plus a prompt replaces what used to be a team's quarter of work. Changing your ontology is now editing a class, not retraining a pipeline.
- A cheap model does the volume work. Extraction runs once per document, possibly across thousands of documents, so it runs on Haiku. The expensive judgment calls (next concept) go to a stronger model. This is the maker–checker economics of the loop course, applied to pipeline stages.
- The
descriptionfield is not decoration. It captures context around each entity, and context is exactly what Concept 7 needs to decide whether two names are the same thing.
This is also the harness course's typed-output rule, promoted from one reviewer verdict to the whole memory system: nothing enters the graph as prose. Code validates every extraction against the schema before the graph believes it.
You do not teach the model what a person or a company is: it already knows. You hand it a form to fill in, and you reject any reply that does not fit the form. The form is the whole pipeline.
No Python needed for the idea. In a repo with a README, run the headless worker you already know:
claude -p 'Read README.md. Return ONLY valid JSON:
{"entities":[{"name":"","type":"PERSON|ORG|TOOL|PROJECT","description":""}],
"relations":[{"source":"","predicate":"","target":""}]}
Every relation must connect two extracted entities. Predicates are short verb phrases.'
# OpenCode: opencode run '<same prompt>'
Pipe the output through jq . to validate. Congratulations: that is stage one of the Cookbook, at operating size.
7. Resolution: same thing, many names
Run this one: python3 concepts/07-resolution.py in the companion lab. Reading it is half; watching it happen is the other half.
Extraction produces surface forms, not a clean graph. The same astronaut appears as "Edwin Aldrin," "Buzz Aldrin," and "Col. Aldrin" across three documents. Left unmerged, your graph holds three disconnected people, and every multi-hop question through Aldrin silently fails. Entity resolution is deciding which surface forms are the same real thing.
Why not string similarity? Because it fails in both directions at once. "Edwin Aldrin" and "Buzz Aldrin" differ exactly where a name is supposed to identify someone — the given names share nothing — so a similarity score lands below any sane merge threshold and misses the merge. Meanwhile two different people named "Muhammad Khan" score a perfect match: a similarity score invents a merge. Names are evidence, not proof.
The Cookbook's move: treat resolution as a reasoning task. Group candidate entities by type, hand a stronger model (Sonnet) each group with the description fields from Concept 6 as context, and ask it to propose canonical clusters. The descriptions are what make "Edwin Aldrin, Apollo 11 lunar module pilot" and "Buzz Aldrin, second person on the Moon" mergeable, and two same-named strangers separable. At scale, cheap blocking signals (same type, overlapping context) narrow the candidate pairs before the model arbitrates, so you are not paying for every pairwise comparison.
Now the rule that separates a durable graph from a time bomb: resolution must be additive and reversible. A canonical entity keeps its aliases, its source documents, the model's merge rationale, a confidence, and the run that created the merge. Nothing is overwritten; surface forms are linked, not destroyed. Why so careful? Because a false merge is the catastrophic failure of knowledge graphs. Collapse two people into one node, and every downstream traversal combines their employers, projects, dates, and actions: confidently, invisibly, everywhere at once. With receipts kept, a bad merge is one reversal. Without them, it is a rebuild.
You know this pattern. It is the harness course's reversibility verb, checkpoints before risky actions, applied to memory instead of code.
Merging names is like combining two people's contact cards. Done right, you keep both original cards stapled behind the merged one, with a note saying why and how sure you were. Done wrong, two strangers stapled together, every message meant for one reaches the other, and nobody can say when it started.
Take the entities you extracted in Concept 6 and hand them back for resolution:
claude -p 'Here are extracted entities with descriptions: <paste>.
Group them by type, then propose canonical clusters. For each cluster return:
canonical_name, aliases[], rationale, confidence (0-1). Never drop an alias.
If two entities share a name but differ in description, keep them separate.'
Then plant a trap and run it again: add a second entity with a name you already have but a different description. If the model merges them, your descriptions are too thin, which is a Concept 6 fix, not a Concept 7 fix. That dependency between the stages is the lesson.
Your resolution step reports a compression ratio of 5:1 — five surface forms per canonical entity, up from 2:1 last week. The team celebrates the "cleaner" graph. What should you check before joining them? The false-merge rate. Compression alone rewards over-merging: the fastest way to a spectacular ratio is stapling strangers together. A high ratio is only good news if pairwise precision held: sample merged clusters and verify the members really are one thing. A connected graph that is connected by mistakes is worse than a fragmented one, because it answers confidently. (You will formalize this instinct, gold sets, precision, recall, in the next course.)Show answer
8. Provenance: every edge keeps its receipt
Run this one: bash concepts/08-provenance-invariants.sh in the companion lab. Reading it is half; watching it happen is the other half.
Assembly, the third stage, is mechanically simple: the Cookbook uses an in-memory NetworkX MultiDiGraph, and the same shape moves to Postgres or Neo4j when you outgrow memory. The engineering content is not the container. It is what every node and edge must carry:
Resolution (Concept 7) hands you one extra thing: a map from every surface form to the canonical name it belongs to. Assembly is that map, applied. Note that Entity and Relation are still the plain Concept 6 schemas: the canonical identity lives in the map, not in a new field on the model's output.
# from Concept 7: {"Edwin Aldrin": "Buzz Aldrin", "Buzz": "Buzz Aldrin", ...}
alias_to_canonical: dict[str, str] = resolve(entities, client)
def add_entity(G, entity: Entity, source_doc: str) -> None:
canonical = alias_to_canonical.get(entity.name)
if canonical is None: # unresolved: do not guess
return
if canonical in G: # merge, do not overwrite
G.nodes[canonical]["source_docs"].add(source_doc)
G.nodes[canonical]["aliases"].add(entity.name)
return
G.add_node(canonical,
entity_type=entity.type,
description=entity.description,
source_docs={source_doc}, # where this entity was seen
aliases={entity.name}) # resolution's receipts, kept
def add_relation(G, rel: Relation, source_doc: str) -> None:
src = alias_to_canonical.get(rel.source)
tgt = alias_to_canonical.get(rel.target)
if src is None or tgt is None: # never invent an endpoint
return
if src not in G or tgt not in G:
return
G.add_edge(src, tgt,
predicate=rel.predicate,
source_doc=source_doc) # the receipt
Two details are the whole lesson. Re-seeing an entity adds a source document and an alias rather than replacing what was there, so the node accumulates receipts instead of losing them. And anything the resolution step did not resolve is dropped rather than guessed, which is why both functions return early on a missing key instead of falling back to the raw surface form. That strictness has a cost you should see clearly: a resolution step that misses an entity silently loses every fact about it. The gentler policy is to accept the raw name and flag it for review, and either choice is defensible. What is not defensible is the middle: falling back silently, and then telling readers you dropped it. (The Cookbook's own notebook is the reference implementation; if you want a confidence number on each edge, that is a field you add to your schema, not one the Cookbook's Relation provides.)
Provenance, the receipt, is what upgrades a claim from "something a model once said" to "a statement you can audit." The independent PDF condenses the discipline into four invariants, and they are worth memorizing as a unit. Every graph write must satisfy:
- Every claim has a source, or is explicitly marked as inference.
- Every artifact has an authoring run and a version.
- Every evaluation identifies its rubric.
- Every superseded object remains addressable: replaced, never erased.
Notice what the invariants quietly forbid: a graph is not a truth machine. It stores claims, sources, and relationships so they can be inspected; it does not convert claims into truth. A biased corpus produces a biased graph. A missing document produces a missing edge. The graph's honest promise is smaller and more valuable: nothing in it is unattributed, and nothing in it is unexaminable. That promise is what makes Concept 10's grounded checking possible: a checker cannot demand evidence from a memory that never kept any.
Write five claims your agents established this week into a scratch file, one JSON object each, and fill in every field the invariants demand. The exercise is not the typing. It is noticing which claims you cannot source, because those are the ones you have been carrying as facts on the strength of a model's fluency. Mark each of those "source": {"kind": "inference"} and count them. That count is your current honesty baseline.
Invariant 4 deserves one more sentence, because it is the ratchet's sibling. When new evidence overturns a claim, you add (claim_new) —supersedes→ (claim_old) — you do not delete. The graph remembers being wrong, which is exactly what lets you ask, later, "what did we believe on March 10, and why?" An audit trail is a feature you cannot retrofit.
A claim without a receipt is a rumor. A graph full of rumors is worse than no graph, because it looks organized. The four invariants are one habit: never write anything down without also writing down how you know it and what it replaced.
Part 4: Working From the Graph
9. The subgraph, not the graph: context construction
Run this one: python3 concepts/09-subgraph.py in the companion lab. Reading it is half; watching it happen is the other half.
You built the graph to escape context dumping. The fastest way to ruin it is to invent a new kind: serializing the whole graph into every prompt. A graph with fifty thousand edges is as useless in a context window as fifty transcripts. The discipline is bounded retrieval: each worker gets a task-specific subgraph, and nothing more.
The Cookbook's query stage shows the shape, and the PDF generalizes it into a context builder any of your loops can follow:
- Resolve the entities the task mentions against the graph ("the vendor incident" →
entity_vendor_x,entity_incident_y). - Expand one or two hops from those nodes, over allowed edge types only: not every edge, everywhere.
- Include current artifact versions the task touches.
- Prioritize recent, verified claims over stale or low-confidence ones.
- Include the conflicts. If two claims contradict, the worker must see both: hiding uncertainty produces confident errors.
- Serialize within a token budget, as plain triples the model can read.
- Attach stable edge identifiers, so the worker's answer can cite
edge_1042and a checker can look it up.

Steps 6 and 7 close the loop with everything this book has taught about context: the serialized subgraph is a briefing document, assembled by code, from audited memory — the context-engineering ideal, finally with a durable source behind it. And notice what became possible that transcripts could never offer: an orchestrator coordinating twenty workers no longer copies twenty outputs into its own window. Workers publish typed graph updates; a synthesizer traverses the graph and combines findings even if no single agent ever saw all the source documents. The orchestrator's context stays clean. That single property is why multi-agent systems scale with a graph and suffocate without one.
Using the entities and relations you already extracted, pick one that a real task would mention. Then do the seven steps with your own hands: write down the resolved entity, list only its one-hop neighbours over two edge types you choose, add any conflicting claim, and serialize the result as plain triples with an ID on each line. Count the lines. If a real task's briefing fits in twenty triples, you have just proved why dumping fifty thousand edges was never necessary.
Do not hand the new employee the whole filing cabinet. Pull the three folders their task touches, plus the sticky note saying "these two folders disagree — check." That is a subgraph: small, relevant, honest about conflicts, and citable.
10. The grounded checker: "triple not found" beats "seems off"
Run this one: python3 concepts/10-grounded-checker.py in the companion lab. Reading it is half; watching it happen is the other half.
Here the memory layer meets the governance layer, and the course pays a debt to its siblings. The loop course gave you the maker–checker split. The harness course gave the checker typed output. But the checker's verdict was still an impression: a model reading work and reporting a feeling with a schema around it. A graph changes what a checker can be: it can check claims against edges.
Walk one claim through. The maker's report says: "Vendor X supplied the component involved in Incident Y." A grounded checker does not ask "does this sound right?" It asks the graph two mechanical questions: does a supported edge (vendor_x, supplied, component_z) exist? Does (component_z, involved_in, incident_y) exist? If either is missing, the verdict is not a vibe — it is a structured, actionable demand:
{
"decision": "revise",
"claim": "Vendor X supplied the component in Incident Y",
"reason": "No supported path from vendor_x to incident_y",
"required_evidence": [
"A source-backed 'supplied' relation from vendor_x",
"A source-backed 'involved_in' relation to incident_y"
]
}
Compare the two failure messages this concept's title promises. "Seems off" sends the maker back to guess what displeased the reviewer. "Triple not found: here are the two edges you must supply" tells the maker exactly which evidence to find or which claim to withdraw. One is a mood. The other is a work order.
And the demand is honest in both directions: sometimes the maker finds the source and the graph gets a new edge — grounding improves the memory, not just the report.
The same grounding upgrades every workflow pattern you know. In a chain, the graph is a gate between stages: do the entities this stage produced exist upstream? In a fan-out, it is the shared surface workers publish to without overlapping. In orchestrator–workers, it is the shared memory that keeps the orchestrator's context clean. In evaluator–optimizer, it is the evidence layer under every verdict. One graph, five patterns, the same three roles throughout: shared memory, grounding layer, persistent world model.
Now the sentence that keeps you honest, and hands you to the next course. A grounded verdict is only as good as two things the grounding does not test: whether the graph's edges are themselves correct (Concepts 7 and 8 reduce that risk; nothing removes it), and whether the checker reliably finds paths that do exist. "The checker consulted the graph" is a better claim than "the checker had an impression." But it is still a claim, produced by a model. How do you know the checker is any good? That question has a whole discipline, and it is the very next course: Trusting the Checker.
An ungrounded reviewer is a critic: "I did not like it." A grounded reviewer is an auditor: "Line 4 claims a payment; there is no receipt in the file; produce the receipt or remove the line." You can argue with a critic forever. An auditor's demand is either met or it is not.
Write three claims about your own project into a file, two supported by something real and one you invent. Then run the checker against your hand-built subgraph from Concept 9:
claude -p 'Subgraph (triples with ids): <paste>. Claims: <paste>.
For each claim, cite the triple ids that support it. If no triple supports it,
return decision "revise" with required_evidence naming the exact missing
relations. Never approve on plausibility. Return JSON only.'
Watch what happens to the invented claim. If the checker approves it anyway, you have found something more valuable than a working checker: you have found the reason the next course exists.
Your grounded checker returns PASS on every claim in a report, citing edge IDs for each. A week later, one claim proves false. List the two places the failure could live, and which course fixes each. Either the graph was wrong — a bad extraction or a false merge put a wrong edge in memory, which is a Part 3 discipline problem (tighten resolution, audit provenance, add the failure as a gold case), or the checker was wrong, it cited an edge that does not actually support the claim, which is a checker-quality problem: measuring it is Trusting the Checker, where fluent answers citing irrelevant edges is a named failure mode. Grounding narrowed "something is wrong somewhere" to two auditable suspects. That narrowing is the win.Show answer
Part 5: The Graph of Loops
Parts 2 through 4 built graphs whose nodes are records: commits, entities, claims, sources. This part changes what a node is. Zoom out until each of your loops becomes a single point, and ask how the points connect. That is the governance graph, the other half of "graph engineering", and it decides whether a system of loops stays honest or merely stays busy.
11. The wiring: who feeds whom, who checks whom
Run this one: python3 concepts/11-wiring.py in the companion lab. Reading it is half; watching it happen is the other half.
On July 18, 2026, Peter Steinberger — the "you should be designing loops that prompt your agents" voice from the very start of the loop course — posted a twelve-word question after midnight: "Are we still talking loops or did we shift to graphs yet?" Within hours it had been turned into a slogan: about four and a half hours later Hamel Husain published an article titled "Loop Engineering Is Dead. Enter Graph Engineering," and Santiago Valdarrama posted the line that spread furthest, "Loop engineering is dead. Long live graph engineering!" Be careful about who said what, one more time. Steinberger only asked a question, and his words read like a joke about the industry's naming treadmill, not an announcement; the obituary was written by other people, and read as a joke too. But behind the noise sits a real idea, and Carlos E. Perez's essay From Loop Engineering to Graph Engineering? maps it clearly.
You know what a loop is: one agent's behavior — a heartbeat, a body, a spine. In the governance graph, the loop is the node, and the graph is the wiring between the nodes. One correction before that settles in: not every node is a loop. A human gate is a node. A ground-truth check is a node. A frozen checker is a node. The precise version: an agent loop can be a node; the other nodes are human decisions, outside systems, and the measurements everyone must accept.
If this sounds familiar, it should: you built governance edges throughout the loop course without the name. The maker–checker split is an edge: one loop's output becomes another's input. The two-routine gate is a three-node graph: Routine A drafts, a human decides, the decision fires Routine B. The dreaming loop is a graph too: one loop reads every other loop's logs and proposes changes through a gate. So read the slogan honestly: a graph is loops, composed. Remove the loops and the graph is empty boxes.
One distinction organizes everything that follows. The word "loop" covers two different machines, and they fail differently:
- An execution loop does recurring work: triage the issues, review the PR, draft the changelog. Nearly everything the loop course taught.
- An improvement loop watches a number against a target and adjusts the system doing the work: measure the gap, act to shrink it, go around again. The dreaming loop is the clearest one you built.
An execution loop that misses its stopping condition needs a better spec. An improvement loop that games its own number needs structure around it, and that structure is the next concept.
When you think in graphs, three practical questions move to the center: none new to you, each now asked once per edge instead of once per loop. Routing: when loop A finishes, who receives the result, loop B, a human, or nobody? Trust boundaries: the edges are standing permissions, so which loop may fire which, and under whose identity? A missing edge is work that silently never arrives. Gate placement: put the human where a wrong automatic move is costly and hard to reverse. That was a dial you set per loop; now you set it per edge, with the gate sitting between nodes rather than inside one.
A loop is one worker who runs without you. The governance graph is the org chart that connects the workers, plus the manager who owns the targets, the referee who settles fights, and the measurements nobody can argue with. You cannot draw a useful org chart for workers who do not exist yet, which is why the loop course came first.
List every loop, checker, and human gate you run today, one per line. Then draw one arrow for each relationship, and label each arrow with its verb: fires, reviews, approves, audits. Two findings are almost universal on a first drawing. Some loop has no incoming arrow from anything that checks it, and some number has nobody watching it. Both are missing edges, and the next concept names what they cost.
12. Perez's four failures of the single loop
Run this one: python3 concepts/12-four-failures.py in the companion lab. Reading it is half; watching it happen is the other half.
Perez's essay opens with a story: a support team spends a quarter building a loop that optimizes ticket-resolution rate. The number climbs. Then the renewal data arrives, and customers are leaving at twice the old rate. The bot had learned to close tickets by pushing customers away, marking abandoned problems as "solved." The loop worked perfectly; its number had quietly stopped meaning the real outcome. You met this failure in miniature already: it is why autoresearch's agent may never edit prepare.py, and why the loop course's portfolio project forbids touching check.py.
Perez names four ways a single loop breaks. Each break is fixed by an edge in the graph — never by a better loop:
| How a single loop breaks | What it looks like | The graph's answer | You saw this already |
|---|---|---|---|
| Gaming (Goodhart's law) | The loop moves its number in ways that betray the real outcome | Pair every optimizing loop with a watching loop on a counter-metric — resolution rate paired with renewal rate | The prepare.py / check.py rules; the read-only reviewer |
| Blindness upward | Nothing inside a loop can ask whether its own target is right | A slower loop owns the faster loop's target, so changing targets is governed work | Only you can question what the loop is for — the loop course's context-advantage lesson |
| Conflict | Loops built separately fight each other, and each one looks perfect alone | An arbitration node above them, a supervising loop or a human gate, that owns the trade-off | The human gate deciding what ships |
| Measurement decay | Checking slides from checking reality into checking one report against another report | Independent audit loops that test whether the numbers still touch the world | The dreaming loop; "green does not mean done" |
Read the table as one sentence: every fix is an edge, never a better loop. That is the governance graph earning its name.
A loop can only see its own number, so it will chase the number even after the number stops meaning the real result. The fix is never "trust the loop more." The fix is structure: a watcher on a counter-metric, a slower loop that owns the target, a referee above conflicts, and an auditor that checks the numbers against reality.
Your triage loop's "issues closed per day" climbs for a month, and the team celebrates. Which of Perez's four failures should you rule out first, and what edge rules it out? Gaming. A rising close-rate is exactly the support-bot story's shape: the cheapest way to close issues is to close them badly. The edge that rules it out is a watching loop on a counter-metric: reopen rate, or reader complaints per closed issue. If the counter-metric held while closes rose, celebrate. If nobody is watching a counter-metric, the number is unaudited, and the celebration is premature by definition.Show answer
13. Anchors and frozen nodes: a graph can fool itself
Run this one: bash concepts/13-anchors-audit.sh in the companion lab. Reading it is half; watching it happen is the other half.
This is Perez's warning, and it is the part every slogan leaves out. It applies with equal force to both graphs this course teaches.
The governance version. Imagine a graph where every loop only reads other loops' reports. Loop A checks loop B's numbers. B's numbers come from C. C reads a dashboard built from A and B. Everything agrees with everything; nothing was checked against the real world. Perez calls this a circular graph. It fails exactly as the single loop failed: just later, more expensively, and with more green lights on the way down.
The memory version. Now replace "loops" with "claims." Every claim in your knowledge graph cites a source, and every source is another agent's report, whose sources are further agent reports. The four invariants of Concept 8 all pass, because the invariants enforce that receipts exist, not what the receipts are made of. Internally perfect, externally untethered: the same circle, drawn in JSON.
One failure in two costumes, so one fix, worn twice. A graph needs three things that no arrangement of arrows can supply:
- Anchors — measurements no loop can argue with, and sources no model produced: tests that actually ran, customers who actually stayed, money that actually arrived, documents humans wrote. Be strict about the last kind. A run log is an anchor only where the cited lines are captured output from something outside the model: a test runner, a compiler, a database, an API, the operating system. An agent's own prose inside a log file is model output wearing a filename, and citing it is how a circular graph passes its own audit.
- Frozen nodes — rules the optimizing loops are never allowed to change, exactly because they would want to:
check.py,prepare.py, the held-out test set, the claims schema itself. - A root judgment from outside the graph: the answer to "what does better mean?" The machinery cannot produce this answer, because every loop already assumes it. It comes from people. That is the loop course's Concept 1 — intent and accountability — reached from the other direction: they are not the last things the graph automates; they are the things the graph cannot contain at all.
The audit is the same in both costumes: follow the leaves. Pick ten random verdicts (governance) or ten random claims (memory) and walk each one all the way down. Count how many bottom out in reality rather than in another model's report. That count is your system's grounding, in one number.

What this changes for you, in practice. Nothing about your first loop: build it exactly as the loop course taught. Everything about your second. The moment two loops exchange work, share state, or trigger, review, or constrain each other, you are doing graph engineering, whether you use the name or not. The cheap starting version is two rules: every optimizing loop gets one watching loop, and at least one signal in your graph must come from reality, not from another model's report.
And one prediction, which Perez makes and this book agrees with: graphs of loops will fail too, circular, consistent, and convincing, wherever they are built without anchors. Then the discourse will jump to the next name. The lasting question was never loops versus graphs. It is grounded versus ungrounded: does your machinery, whatever its shape, still touch the reality it claims to improve?
Three newspapers citing each other in a ring is not three sources: someone has to have attended the event. That rule holds whether the ring is made of loops checking loops or claims citing claims. Anchors are the reporters who were actually there; frozen nodes are the ethics rules reporters may not rewrite; and "what counts as news" is decided by the editor, never by the printing press.
A team builds a beautiful graph: five loops, paired metrics, an audit loop, a human gate. But every loop reads only the reports the other loops produce. What is missing, and what goes wrong? An anchor. The graph is circular: every loop confirms every other loop, but nothing is checked against the real world. It will fail like a single loop: just later, more expensively, and with more green lights along the way. At least one signal must come from reality: a test that actually ran, a customer who actually stayed, money that actually arrived.Show answer
One caution, in this book's own spirit
One caution. The industry renames the frontier about once a season: prompt engineering, then context engineering, then harness engineering, then loop engineering — and now graph engineering, in two flavors at once. Each name is partly real and partly noise, and each layer wraps the one before it: a graph is many loops, composed and given shared memory. The idea is older than the slogan — MLOps pipelines, company governance, and the body's own regulation are all graphs of loops running at different speeds, over shared records. What became newly visible in 2026 is that capable agents can run these loops unattended, so both the wiring question and the memory question reached a much larger group of builders. The names change fast. The shape underneath grows slowly. Learn the shape, and the next rename costs you an afternoon, not a course.
Part 6: One Graph, End to End
Theory over. This part upgrades a system you have already built twice, the morning-triage loop from the loop course, fenced by the harness course, from a prose spine to a small, queryable graph. Files and shell only, no database, no framework: the graph is three JSON files in the repo, and the discipline is the schema, not the storage. Both tools run it; only the headless command differs.

The shape on disk
graph/
SCHEMA.md # the contract: fields, types, and the write rules
entities.json # nodes: things the loops talk about
claims.json # edges-with-receipts: what the loops have established
runs.json # the work side: which beat wrote what, and with what result
evidence/
run_2026-07-21-triage.log # raw tool output a claim can point at
Two directories, because they hold different things. graph/ is the curated memory, small and schema-checked. evidence/ is the raw output claims point back to: test runs, command output, API responses. Nothing in evidence/ is ever edited.
And when does JSON stop being enough? Later than instinct fears and sooner than this design suggests. A single file rewritten on every append is comfortable into the low thousands of claims, and starts hurting somewhere around ten thousand: jq scans slow down, the whole file is rewritten for one new row, and two loops appending at the same moment can lose a write. The upgrade path is deliberately boring, because the discipline is the schema rather than the storage. A relational table (Postgres, or SQLite on one machine) buys real ids, indexes on subject and predicate, transactions so concurrent appends stop colliding, and append-only enforced by a database trigger instead of a Git hook. A graph database (Neo4j, Neptune) buys one thing beyond that: multi-hop traversal as a query rather than as code you maintain, which starts to matter when your context builder walks three or four hops instead of one or two. None of those moves changes a single invariant. Move when a query gets slow or a write gets lost, not before.
One claim, in full — this is the whole discipline in twelve lines:
{
"id": "claim_0007",
"subject": "test_payments_flaky",
"predicate": "diagnosed_as",
"object": "tz_default_utc",
"confidence": 0.9,
"source": {
"kind": "tool_output",
"command": "pytest tests/test_payments.py -x",
"exit_code": 1,
"ref": "evidence/run_2026-07-21-triage.log#L88-L94",
"captured": "2026-07-21T09:14:22Z"
},
"produced_by": "run_2026-07-21-triage",
"supersedes": "claim_0004",
"created": "2026-07-21"
}
One thing to notice before you copy it: this claim carries a supersedes, so it assumes claim_0004 is already sitting in the file. Your first claim has no supersedes field at all. Add one that points at a claim you never wrote and the hook below will stop the commit, correctly, with supersedes points at a claim that does not exist. Note too that claims.json is an array, even when it holds a single claim: every jq query on this page starts with .[].
The other two files are smaller. An entity is an identity plus its aliases:
[
{
"id": "test_payments_flaky",
"type": "TEST",
"aliases": ["tests/test_payments.py::test_tz", "the flaky payments test"],
"first_seen": "2026-07-14"
}
]
And a run is one beat's receipt:
[
{
"id": "run_2026-07-21-triage",
"beat": "morning-triage",
"started": "2026-07-21T09:11:04Z",
"tool": "claude-code",
"evidence": ["evidence/run_2026-07-21-triage.log"],
"claims_written": ["claim_0007"],
"verdict": "PASS"
}
]
Now, two things about the claim above are deliberate, and both were nearly wrong.
There is no status field. Claims are append-only: nothing is ever edited, and nothing is ever deleted. Whether a claim is still current is not stored, it is derived at read time: a claim is active if no later claim supersedes it. One jq expression answers it, and the rule cannot drift, because there is no second place for the truth to live. This is the accounting analogy done properly. A correcting entry does not reach back and modify the original entry.
# active claims = those that nothing supersedes
jq '[.[].supersedes] as $dead
| [.[] | select(.id | IN($dead[]) | not)]' graph/claims.json
Read it as two steps: collect every id that a later claim supersedes, then keep the claims whose id is not in that list. One trap is worth naming, because the obvious one-liner is wrong: do not fold this into select(any(.[]; ...)) over .[], because inside the select the current input is a single claim, so any(.[]; ...) walks that claim's own fields and errors out. The two-step form avoids it and reads better.
The predicate is diagnosed_as, not caused_by. A failing test with exit code 1 proves that something failed. It does not prove what caused the failure: that step is the agent's reading of the output. So the claim states what it can support. Upgrading to caused_by needs more evidence than a red test — the failing assertion, the configuration line it points at, and ideally a rerun that passes after the timezone assumption is corrected. Then you append a new caused_by claim that supersedes this one, citing both runs. Choosing a predicate your evidence actually supports is the smallest, most repeatable act of honesty in this whole discipline.
The source names the tool, not the agent. "kind": "tool_output" with a command, an exit code, a timestamp, and a line range points at something no model wrote: pytest failed, and here is where it said so. Compare that with a source that pointed at a sentence the agent typed into its own log. That would satisfy the letter of invariant 1 while pointing at model output, which is Concept 13's circular graph in a single field. The rule: a run log is only an anchor where the cited lines are captured output from something outside the model — a test runner, a compiler, a database, an API, the operating system. An agent's own prose inside a log file is a claim, not evidence for one.
Check the claim against Concept 8's invariants: a real source (invariant 1), an authoring run (invariant 2), and a superseded predecessor that remains addressable because it was never touched (invariant 4). Invariant 3 arrives with the reviewer below.
The maker writes to the graph
One paragraph added to the triage skill changes what the loop leaves behind. Where the old skill said "update progress.md," the new one says:
## 5. Update the graph last
For every durable finding this beat established, append one claim to
graph/claims.json following the schema in graph/SCHEMA.md. Rules:
- claims.json is APPEND-ONLY. Never edit and never delete an existing
claim, including any of its fields. To correct a claim, append a new one
whose "supersedes" names the old id. The old claim is left untouched:
whether a claim is current is derived when the graph is read, never
stored on the claim itself.
- Every claim needs a source a later agent could open and verify. Prefer
captured tool output: save it under evidence/ and cite the command, the
exit_code, and a line range. If the finding is your own reasoning with
no external output behind it, mark it "source": {"kind": "inference"}.
- Never cite your own prose in a log as the evidence for your own claim.
- New entities go in entities.json first. Check aliases before adding:
do not create "payments-test" if "test_payments_flaky" exists.
- Session notes, dead ends, and chatter stay in progress.md. The graph
is for what was established, not what was said.
The last rule matters most. The spine does not disappear: it remains the loop's diary. The graph is the smaller, stricter record of what the diary proved. Two memories, two truth standards, exactly like Concept 3's two graphs.
And the harness makes the rules real, because a guardrail lives in the harness, never in the prompt. Three of the rules above are mechanically checkable, so the hook checks them:
#!/bin/sh
# .git/hooks/pre-commit — the graph gate (jq only, no framework)
C=graph/claims.json
fail() { echo "claims.json: $1 — commit blocked"; exit 1; }
# 1. required fields on every claim
jq -e 'all(.[]; has("id") and has("subject") and has("predicate")
and has("object") and has("source") and has("produced_by"))' "$C" \
>/dev/null || fail "a claim is missing a required field"
# 2. ids are unique
[ "$(jq 'length' "$C")" = "$(jq '[.[].id] | unique | length' "$C")" ] \
|| fail "duplicate claim id"
# 3. every supersedes target exists
jq -e --argjson ids "$(jq '[.[].id]' "$C")" \
'all(.[]; (has("supersedes") | not) or (.supersedes | IN($ids[])))' "$C" \
>/dev/null || fail "supersedes points at a claim that does not exist"
# 4. append-only: nothing already committed may change
git show HEAD:"$C" 2>/dev/null > /tmp/old.json || exit 0
jq -e --slurpfile new "$C" \
'all(.[]; . as $o | $new[0] | any(.[]; . == $o))' /tmp/old.json \
>/dev/null || fail "an existing claim was modified or removed"
Now be honest about the boundary of that gate, since this course keeps insisting on the difference between a request and an enforced rule. Those four checks are real: required fields, unique ids, resolvable supersession, and append-only history. What the hook does not check is field types, whether subject names an entity that exists in entities.json, whether a source block is well-formed, or whether the cited evidence file and line range actually exist. Each is another jq line, on the day you want it. Until you write it, that rule lives only in SCHEMA.md, which makes it guidance rather than a guardrail. Knowing which of your rules is which is the entire point of the distinction.
The reviewer reads from the graph
The reviewer's prompt gains one obligation, and its verdict gains one field. This is Concept 10 at operating size:
You are the reviewer. For every factual claim in the maker's report:
1. Find the claim in graph/claims.json that supports it. Cite its id.
2. If no active claim supports it, your verdict is REVISE, and
required_evidence must name the missing claim precisely.
3. A claim whose source.kind is "inference" cannot by itself ground a
factual assertion. Either cite a source-backed claim that supports it,
or return REVISE. An honestly recorded guess is still a guess.
4. Never approve a factual claim on plausibility. "Sounds right" is
not a citation.
Return only JSON:
{ "verdict": "PASS|REVISE|FAIL",
"grounded_in": ["claim_0007", "claim_0012"],
"missing": [],
"rubric": "reviewer-rubric-v3" }
Rule 3 is the one people leave out, and leaving it out quietly undoes the whole build. The maker is allowed to record inference honestly, which is right: a marked guess is better than a laundered one. But if the reviewer only checks that a cited claim exists, then a marked guess can ground a shipped factual statement, and the graph has laundered it after all. Honest inference and grounding evidence are different jobs. The reviewer's task is to keep them apart.
The rubric field is invariant 3, and the grounded_in field is the auditable trail: months later, you can open any shipped PR and walk from its verdict to the claims to the sources. Every important output traced to an objective, an artifact, a source, a graph path, and an evaluator decision: the PDF's closing test, passed at the scale of three JSON files.
One beat, before and after
Before (spine only). Tuesday's triage beat fixes the flaky payments test and writes prose: "fixed flaky test, was a timezone thing." Thursday, a different loop, the changelog loop, mentions the payments fix, and its reviewer approves on plausibility. Three weeks later someone asks which timezone assumption it was, and the answer is an archaeology dig through transcripts.
After (graph). Tuesday's beat writes claim_0007 above, citing the pytest output it captured under evidence/. Thursday, the changelog loop's context builder pulls the two-hop subgraph around test_payments_flaky: the claim, its source ref, the run that produced it. Its reviewer approves the changelog entry because grounded_in: ["claim_0007"] resolves. Three weeks later, the question is answered by jq, in one line, with a receipt:
jq '[.[].supersedes] as $dead
| .[] | select(.subject == "test_payments_flaky")
| select(.id | IN($dead[]) | not)' graph/claims.json
Same loops. Same model. The only change is where the memory lives, and that changed what every later agent, and every later human, can know.
One more reading of the build, now that Part 5 has given you the second lens. This tiny system carries both graphs at once. The reviewer-to-maker relationship is a governance edge (a watching loop, whose counter-metric on the maker is "every claim resolves"). The pre-commit schema hook is a frozen node: the makers may never tune the rules they are checked against. And the source blocks are anchors, but only because of what they point at: a command, an exit code, and captured output from a test runner. Point the same field at a sentence the agent wrote about itself and the anchor evaporates while the schema still passes. Three JSON files, one hook, two prompts, and every idea in this course is present in miniature.
In a throwaway repo: create the three JSON files with one entity and zero claims. Run one maker beat headlessly, claude -p or opencode run, with the graph-writing skill above, on any small real task. Then run the reviewer prompt against the maker's report. Watch it REVISE with a missing field on the first pass, because the maker under-recorded. That failure is the lesson: the reviewer just taught the maker what the graph must contain.
Part 7: Staying Grounded
14. Choosing a level, and budgeting for it
Run this one: python3 concepts/14-choose-a-level.py in the companion lab. Reading it is half; watching it happen is the other half.
Before the case against graphs, the procedure for choosing one. Six questions, asked in order, decide how much structure a job actually needs. Each one you answer "no" to saves you a layer.
- Can success be verified? If not, do not begin with autonomy at all. Define a test, a rubric, a source requirement, or a human decision first. This is the loop course's first gate, and nothing later repairs a missing answer here.
- Are the steps stable? If yes, a chain is enough. If no, you need planning or an orchestrator.
- Are the subtasks independent? If yes, parallelize. If no, model the dependencies explicitly and limit how many workers may write at once.
- Must alternative lineages stay available? If yes, use a DAG rather than forcing every result into one branch. This is Concept 5's question.
- Must the facts survive the run? If yes, persist artifacts and graph state. Do not rely on a transcript summary to carry them.
- Can you afford the cost and the latency? Set budgets before adding workers, not after the invoice.
Answered together, they produce a level rather than a preference:
| Your situation | Start with | Why |
|---|---|---|
| Simple, low-risk question | Zero-shot | Lowest latency, no machinery to maintain |
| The output can be checked | A loop | Repeated feedback improves the artifact |
| The sequence is stable | A chain | Predictable, testable stages |
| The categories are clear | A router | Separates policies and models cleanly |
| The units are independent | Parallel workers | Cuts wall-clock time |
| The decomposition varies per task | Orchestrator-workers | Dynamic specialization |
| Alternatives must stay alive | A commit DAG | Preserves experiment branches |
| Facts must survive sessions | A knowledge graph | Persistent shared memory |
| Very large parallel work | A dynamic workflow | Automates the fan-out and the fan-in |
Notice that the graph is the eighth row, not the first. Most work stops earlier, and stopping earlier is a correct outcome, not a failure of ambition.

Whatever level you choose, declare its complexity budget before the run starts. Every run should state, in writing: maximum model calls, maximum sub-agents, maximum concurrent workers, maximum tool calls, maximum wall-clock time, maximum tokens, maximum financial cost, maximum retries, maximum graph writes, and the minimum evidence required before anything is called finished. That last item is the one people forget, and it is the one that makes the rest meaningful.
Then the rule for what happens when a budget runs out, which matters more than the numbers themselves: return the best current artifact, the work that completed, the issues left unresolved, and the reason for stopping. Do not hide partial failure behind a fluent final answer. A run that says "I stopped at 40 of 60 files because the token budget ended, and here is what the 40 showed" is worth more than a confident report that quietly covered two thirds of the job.
Ask the six questions before you build, and let them tell you the smallest structure that fits. Then write down your limits in advance, because a system that has never been told when to stop will stop at whatever moment its money runs out, and it will describe that moment as success.
A team wants a knowledge graph. Their answers: success is verifiable, steps are stable, subtasks are independent, alternative lineages do not matter, and facts do not need to survive the run. Which level do the six questions give them? Parallel workers over a stable chain, and no graph. Question 4 is no, so no DAG; question 5 is no, so no knowledge graph. They wanted the eighth row and the questions gave them the fifth. Building the graph anyway means paying for extraction errors and schema upkeep to answer questions nobody has asked yet.Show answer
15. When not to build a graph
Run this one: python3 concepts/14-choose-a-level.py in the companion lab. Reading it is half; watching it happen is the other half.
Concept 14 gave you the procedure. This concept gives the case against the level this course has spent fourteen concepts selling, in the book's tradition of honest grading. Do not introduce a knowledge graph merely because the system has agents. A graph is machinery with a real bill: extraction errors, resolution risk, schema maintenance, and a new thing that can silently rot. Skip it when:
- tasks are independent and no cross-session state is needed,
- answers come from one document at a time,
- relations are fixed and simple: a relational table already answers every query you actually ask,
- provenance is not required, or
- extraction errors would outweigh traversal value.
A graph earns its cost when connected queries, evolving relations, provenance, or shared world state are central. One loop with a spine needs no graph. The moment two loops exchange facts, or twenty workers need a synthesis, the bill flips. The Part 6 build is deliberately the smallest version that clears the bar.
Then two failure modes for the graphs you do build:
The graph amplifies builder judgment: including bad judgment. A loop amplifies its objective and evaluator; you learned that lesson twice. A graph amplifies its ontology and source policy. Choose the wrong entity types, admit the wrong sources, and automation scales the error: a biased corpus produces a biased graph that answers confidently in every direction. The graph inspects claims; it does not launder them into truth.
Metrics can be gamed here too. An extraction pipeline tuned only for entity recall will happily flood the graph; a resolution step tuned only for compression will merge strangers (Concept 7's trap). Every optimization needs its counter-metric: precision against recall, compression against false merges. This is Goodhart's law knocking a course early; the next course answers the door.
And keep Concept 13's audit running on the graphs you do build: follow ten random claims all the way down to their leaves, and count how many bottom out in reality rather than in another model's report. A memory graph without anchors is the circular graph of Part 5, rebuilt in JSON.
A graph is a bureaucracy for facts. A good bureaucracy makes everything findable and auditable. A bad one stamps rumors with official-looking seals and files them beautifully. The stamp is not the truth: the receipt at the bottom of the file is. Check the receipts, and only build the bureaucracy when the pile of facts is genuinely too big for a notebook.
16. What the graph cannot do, and where this goes next
Run this one: bash concepts/16-limits.sh in the companion lab. Reading it is half; watching it happen is the other half.
End on the honest boundary, in three statements the graph cannot make for you:
"The checker's PASS is now trustworthy." No. Grounding upgraded the verdict from impression to audit, but the auditor is still a model: it can cite an edge that does not support the claim, miss a path that exists, and drift when the model beneath it updates. A fluent answer citing irrelevant edges is a documented failure, not a theoretical one. Measuring the checker, golden sets, calibration, pass rates, drift, is the next course: Trusting the Checker. Everything it teaches applies doubled here, because a graph system has two checkable layers: the extraction that fills the memory, and the reviewer that reads it. Its evaluation harness will even look familiar: read the extraction prompt and score history, propose one change, run against a gold set, keep or revert. The ratchet, pointed at the graph itself.
"The memory is safe where it is." Only as safe as its home. A graph in a laptop repo dies with the laptop; a swarm's shared memory must live where every worker, local, scheduled, cloud, can reach it, survive machine failure, and enforce who may write what. Autoresearch runs on one GPU precisely because bounded is safe; AgentHub is one Go binary on one server precisely because it is a sketch. Moving proven loops and their graph to a runtime you do not babysit is the course after next: Leaving the Laptop.
"Better wiring means better judgment." The oldest boundary in this book, and it does not move. The graph places memory and evaluation outside the context window — that is real, and it is the single most important insight here: the bottleneck is usually not the next model call; it is the placement of memory and evaluation. But the ontology, the source policy, the anchors, and the answer to "what does better mean?" come from outside every graph, from you. Karpathy's README jokes about "autonomous swarms of AI agents running across compute cluster megastructures in the skies." The near-term work is less cinematic and more valuable: typed contracts, preserved lineage, grounded claims, and memory that outlives the session. Intent and accountability, Concept 1 of the first course, remain the two things no arrangement of nodes and edges can contain.
The graph moves memory and checking out of the agent's head, and that is a real win: it is why a thousand agents can work on one problem without each starting from nothing. But it cannot decide what the memory is for. Somebody has to choose which things are worth remembering, which sources count as evidence, and what "better" means. That somebody is you, and no amount of wiring takes the job.
Where this book picks the thread up: read a system of loops sharing a graph out loud and it becomes an organization — workers with a shared filing system, reviewers who demand receipts, an owner who sets the rules. That is the Human-Agent Teams crash course, and it is what a well-built graph of loops becomes when it earns a name on an org chart: a Digital FTE with institutional memory.
Using a graph on this book (dogfooding)
Does this book practice what this course preaches? Honestly: it runs the proto-graph, and it deliberately does not run the full one.
Look at the feedback loop from the loop course's dogfooding section with this course's eyes. Every reader note is a record in a database. Notes link to the GitHub issues they open. Issues link to the pull requests that fix them. PRs link to the lessons they change, and to the human approval that shipped them. Typed records, directed links, provenance end to end: you can walk from any shipped fix back to the exact reader note that caused it. That is a graph in everything but the diagram, and it is why the same note is never worked twice: the loops query the links instead of re-reading history.
What the book does not run is a knowledge graph with model-driven extraction and entity resolution over its own content. That is Concept 15 applied to ourselves: the book's cross-session questions are still answered by the issue links and Git history, the relations are simple, and an extraction pipeline would add error surface without a query that demands it. The day a question arrives that the links cannot answer — "which lessons make claims sourced to documents that have since changed?" is the likely first one — the Part 6 pattern is the plan on file. Build the graph when a real query earns it, and not a week before.
🚀 Projects
Reading about a graph is not the same as filling one. Here are eight builds, easy to hard. Do them in either tool: the graph is files and jq, so the only thing that changes is the headless command (claude -p or opencode run).
Two rules before you start, every time:
- Use a throwaway repo, and real documents. The graph is only interesting when the entities are ones you actually recognise, so use your own READMEs, notes, or logs rather than invented data.
- Write the schema before the first claim. A
graph/SCHEMA.mdyou wrote in five minutes beats one you reconstruct later from what the files happen to contain (Part 6).
Project 110-15 minDraw your systemFind the finding that dies in a transcript, and the number nobody audits.
Difficulty: easy · Uses: Concepts 2, 3, 11 (nodes and edges, two graphs, the wiring).
Build. On paper or in Mermaid, draw every loop, checker, human gate, anchor, and memory file you run today as typed nodes with labeled directed edges. Mark which nodes are loops and which are not. Then circle two things: any finding that currently exists only inside a transcript, and any optimizing loop with no watcher on its number.
Done when you can point at one circled item of each kind and say what it costs you. Almost nobody draws this and finds nothing, which is the point of doing it before you build anything.
Project 230-45 minSpine to claimsConvert ten real findings into typed records, and meet the ones you cannot source.
Difficulty: easy · Uses: Concepts 1, 8 and Part 6 (provenance, the schema).
Build. Take your real progress.md (or any loop's log) and convert its last ten durable findings into claims.json records under Part 6's schema. Fill in every field the invariants demand, including produced_by and a real source.
Done when every one of the ten either cites something a later agent could open, or is explicitly marked "source": {"kind": "inference"}. Count the inference ones. That count is the number of things you had been treating as facts on the strength of a model's fluency, and it is the most useful number this course will give you about your own system.
Project 345-60 minFirst extractionRun one schema-constrained prompt over three documents and meet your own duplicates.
Difficulty: medium · Uses: Concept 6 (extraction).
Build. Run the Concept 6 prompt headlessly over three related documents: three READMEs from one project, three meeting notes, three incident write-ups. Validate every reply with jq before you believe it. Then count how many distinct entities appear under more than one surface form.
Done when all three documents return schema-valid JSON, and you can name at least one entity that appeared under two or more names. If the count is zero, your documents are too similar: use three that were written by different people, which is when resolution becomes a real problem rather than a hypothetical one.
Project 430-45 minThe DAG speaksAnswer AgentHub's three questions using only Git, then leave the commands for your agents.
Difficulty: medium · Uses: Concepts 4 and 5 (the two memories, traversal).
Build. In a repo with real history, answer AgentHub's three questions with plain Git: what was tried on top of commit X, which tips are unexplored frontier, and what path produced the current state. Then write the three commands into a GRAPH.md so your future agents can ask them too.
Done when the three commands work and you can also state what the DAG cannot tell you: which experiments were tried and thrown away. That absence is Concept 4's correction, felt in your own repo rather than read about.
Project 545-60 minResolution drillMerge what should merge, keep apart what should not, and keep every receipt.
Difficulty: medium · Uses: Concept 7 (resolution).
Build. Take twenty surface forms from Project 3, grouped by type and carrying their descriptions, and ask a stronger model for canonical clusters with a rationale and a confidence for each, keeping every alias. Then plant a trap: add two genuinely different entities that share a name, and run it again.
Done when the real duplicates merged, the two same-named strangers stayed apart, and every canonical entity still lists the surface forms it came from. If the strangers merged, do not fix the resolution prompt first: go back and make the descriptions richer, because that is where the evidence has to come from.
Project 61-2 hrs, plus five beatsThe grounded reviewerMake a checker demand an edge instead of offering an opinion.
Difficulty: hard · Uses: Concept 10 and Part 6 (grounding, the reviewer).
Build. Wire Part 6's reviewer into one loop you already run. Verdicts must carry grounded_in with resolvable claim ids, a factual statement with no supporting claim forces REVISE with missing filled in, and a claim whose source is inference cannot ground anything on its own. Run five real beats.
Done when at least one beat came back REVISE with a named missing edge, and the maker's next attempt either produced the evidence or withdrew the claim. Read the five verdicts together afterwards: what the maker learned to record is the actual output of this project.
Project 72-3 hrsA gold set for extractionMeasure the pipeline that fills your memory, one course early.
Difficulty: hard · Uses: Concepts 6, 7 and the next course.
Build. Hand-label the entities and relations in five documents. That is the tedious part and there is no way around it. Then score Project 3's prompt against your labels: precision, recall, and the schema-valid rate. Change exactly one line of the prompt, re-score, and keep or revert on the number.
Done when you have run the ratchet at least three times on the prompt itself and kept a record of every attempt, including the reverted ones. You have just built graph autoresearch: same loop, different artifact. Trusting the Checker is where this becomes a discipline.
Project 8Capstone: a weekend, then a week of beatsTwo loops, one graphMake one loop report a fix it never witnessed, because the graph told it.
Difficulty: capstone · Uses: everything.
Build. Two loops over one graph. The triage loop writes claims with real tool-output sources. The changelog loop reads them through a two-hop context builder rather than a file dump. Both reviewers ground their verdicts. The pre-commit hook guards the schema and the append-only rule. And the triage loop's throughput number gets one counter-metric that the review loop watches (Concept 12).
Done when the changelog loop correctly reports a fix that it never witnessed, because the graph carried it, and you can walk from that changelog line back through the claim, the run, and the captured tool output to something no model wrote. When that walk succeeds, you have built shared memory with anchors, and this course has nothing further to teach you.
Sources & further reading
Inside this book
- Loop Engineering: the ratchet, the spine, the maker–checker split, the dreaming loop, and the two-routine gate — every governance edge in Part 5 was first built there, one loop at a time.
- Harness Engineering: typed output and the reversibility discipline, both promoted here to memory-scale.
- Trusting the Checker: the next course — how to know the grounded reviewer, and the extraction pipeline, are any good.
- Leaving the Laptop: where the graph and its loops go to live when the laptop closes.
- Human-Agent Teams: what a graph of loops becomes on an org chart.
Primary sources
- Andrej Karpathy, autoresearch (released March 7, 2026): https://github.com/karpathy/autoresearch. The three-file harness, the ratchet, and the reported results. Read
program.mditself before quoting the loop anywhere: it is the source for Concept 4's two-memories correction, specifying that experiments run on a dedicated branch, that the branch advances only on improvement, that anything equal or worse isgit resetaway, and thatresults.tsvrecords every attempt while being deliberately left untracked by Git. — the three-file harness, the ratchet loop, and the commit-DAG memory. Star counts and experiment numbers in this course reflect its first weeks; check the live repo. - Andrej Karpathy, AgentHub (published around March 9-10, 2026; no longer public): the agent-first collaboration layer, with a bare Git repo, SQLite, a message board, and the
children/leaves/lineageCLI. Explicitly "just a sketch. Thinking..." The original repository was taken private within weeks and carried no license file. Preserved community forks are the only way to read the code now: treat them as historical artifacts for study, not as software to depend on. The AgentHub integration thread in the autoresearch repository remains public and is the better primary reference. For the repository's removal, see the contemporaneous write-up by a developer who forked it before it went private: https://dev.to/alireza_rezvani/karpathys-agent-native-infrastructure-working-python-agent-template-2o9d (March 2026). - Anthropic, Knowledge Graph Construction with Claude (Cookbook, March 23, 2026): https://platform.claude.com/cookbook/capabilities-knowledge-graph-guide — extraction with structured outputs on Haiku, resolution as reasoning on Sonnet, NetworkX assembly, and subgraph querying with citations. The source for Part 3.
- Erik Schluntz and Barry Zhang, Building Effective Agents (Anthropic Engineering, December 2024): the five composable workflow patterns Concept 10 grounds.
- Anthropic, Introducing dynamic workflows in Claude Code (May 28, 2026; the page now carries an update noting general availability): https://claude.com/blog/introducing-dynamic-workflows-in-claude-code — the source for Part 2's deeper note: generated orchestration, tens to hundreds of parallel fresh-context sub-agents, checked results, resumable progress, the
ultracodesetting, the token warning, and the Bun port. Availability spans the Claude Code CLI, Desktop and the IDE extensions on all paid plans (on Pro you switch it on from the Dynamic workflows row in/config), plus the Anthropic API, Amazon Bedrock, Google Cloud's Agent Platform and Microsoft Foundry. The reference docs, not the announcement, carry the real limits: 16 concurrent agents and 1,000 agents per run. Reference docs: code.claude.com/docs/en/workflows. - Peter Steinberger, post of July 18, 2026 ("Are we still talking loops or did we shift to graphs yet?"), on X (x.com/steipete/status/2078277297791189132, posted 00:34 UTC): the twelve words that named the season. The obituary is not Steinberger's: Hamel Husain published "Loop Engineering Is Dead. Enter Graph Engineering" about four and a half hours later, and Santiago Valdarrama (@svpino) posted the widely-quoted "Loop Engineering is dead. Long live Graph Engineering!". Both read as jokes about the naming treadmill.
- Carlos E. Perez (Intuition Machine), From Loop Engineering to Graph Engineering? (July 19, 2026) — the source of Part 5: the support-bot story, the four failures of the single loop and their structural fixes, the circular-graph warning, anchors and frozen nodes, and the grounded-versus-ungrounded conclusion. https://medium.com/intuitionmachine/from-loop-engineering-to-graph-engineering-d3ebeb08511c
- Graph Engineering: The Karpathy Loop, Improved 1000x by Itself (independent synthesis PDF, July 2026): the staged build path, the two-graphs distinction, the four invariants, and the closing traceability test. By its own front page, not affiliated with or endorsed by Karpathy or Anthropic: read it as a useful study note, and read its primary sources first.
- Fortune, coverage of autoresearch ("the Karpathy Loop"), March 2026.
- TechCrunch and others, on Karpathy joining Anthropic's pretraining team, May 19, 2026, with a mandate to build a team using Claude to accelerate pretraining research: https://techcrunch.com/2026/05/19/openai-co-founder-andrej-karpathy-joins-anthropics-pre-training-team/
All links current as of late July 2026. Every repository, preview feature, and number here moves fast; confirm against the live source before you rely on it.
The one-line summary
The agent forgets; the graph does not. Keep two graphs of memory, a DAG for the work, a knowledge graph for the facts, fill the second by schema, merge names reversibly, staple a receipt to every edge, hand workers subgraphs instead of dumps, and make every checker cite the edge or demand it. Then wire the loops themselves: a watcher on every optimizing number, a slower loop owning every target, a gate between nodes, and anchors no loop can argue with. A graph stores claims, not truth: grounded versus ungrounded is the axis that outlives every rename.