Graph engineering is having a moment.
The definition going round is a good one, and I am not going to try to redefine it. Complex agents increasingly look like graphs: nodes do the work, edges decide what happens next, state moves between them. That is real, it matches what production actually looks like, and the people saying it are right.
Almost nobody makes the next observation. Those agents are still fed context from flat vector chunks, a separate document store, a relational database and some ephemeral session memory. While the execution architecture became a graph, the data architecture underneath it did not.
Graph engineering is not just about graphing the agents. You also have to graph the context they operate on.
Or, less politely: you cannot graph-engineer the agent and leave its memory as a pile of chunks.
This is my attempt to say what graph engineering has to mean if it is going to outlive the meme.
The rename cycle
We have been renaming the same struggle for a few years. Prompt engineering optimised instructions. Context engineering optimised what goes into the window. Harness engineering, loop engineering, and now graph engineering optimise how agents work together.
I do not mind the labels. They appear when builders hit a wall. This wall is real. One agent loop in one chat window stops scaling the moment the work looks like a team: parallel reviewers, a fixer, a security check with veto power, a human gate before anything irreversible. That structure is a graph. It always was.
Every one of those renames moved up the stack, and every one of them left the data layer where it was.
Two graphs. Say which one you mean.
When someone says graph engineering, they could mean one of three things and rarely say which:
A graph of loops, which is really a claim about control flow: what you get when one loop stops being enough.
An execution graph.
A context graph.
The loop graph folds into the execution graph, so two are left standing, and most threads blur even those. Sometimes people mean both. They almost never say so
The execution graph
What runs next, in what order, with what rights. It determines what your agents do.
Nodes are work: a classifier, a tool call, a coding agent, a reviewer. Edges are transitions: success, fail, escalate, fan out. State is the scratchpad the system mutates. Verifiers sit on a separate path when the stakes are high. Stop rules exist so the thing does not burn tokens until the card declines.
This is a state machine with probabilistic nodes inside it. LangGraph made the case earliest and loudest, and Google's ADK, Mastra, CrewAI, Microsoft's agent frameworks, or a boring job runner you wrote yourself all live on the same plane. Useful, necessary for a lot of production agents, and on its own not sufficient.
The context graph
What is true, what is related, what was true when, and who said so. It determines what your agents know.
Here the nodes are entities, documents, events, users and assets, and the edges between them carry properties of their own: since, confidence, source. Provenance, time, permissions and hybrid retrieval all matter, because the world is not only triples and it is not only embeddings.
Classic knowledge graphs and GraphRAG both sit on this plane. It gets a paragraph in most threads and a whiteboard in almost none.
So:
The execution graph engineers the graph of work. The context graph engineers the graph of knowledge.
Most teams have engineered the first and are still running the second as a pile of chunks in a vector store.
Where they meet: the turn
Every serious agent turn is three moves: read, think, write. Read means traverse relations, pull vectors, load history and permissions. Think means the model reasons over a complete packet, not three partial dumps from three systems that disagree with each other. Write means decisions, new entities, and memories land somewhere the next turn can actually see them.
If your execution graph is elegant and your write path is "maybe the vector DB, maybe the graph DB, maybe a JSON blob in object storage," the whiteboard never became system truth. You choreographed forgetting.
That is the definition I use:
Graph engineering is how you design the execution graph of agent work and the context graph of durable knowledge, then keep them consistent across every turn.
The meme covers the execution graph. Production dies on the other two.
Loops, graphs, context. Stack them.
A loop is fine when one goal, one verifier, and one stop condition cover the job.
An execution graph earns its keep when you need parallel specialists, explicit escalations, or policy gates.
Context engineering is what you do on every node either way: put the right facts, tools, and state in front of the model at the right time. Widening the context window does not solve a wrong-context problem, it just raises the price of getting it wrong.
Harnesses and frameworks host all of this. I am not here to sell you an orchestrator. We did not build SurrealDB to replace your control plane.
We built it because the existing data planes under those nodes were a mess.
GraphRAG is one chapter, not the book
People swap the words, but they should not.
GraphRAG is a retrieval pattern: extract structure from a corpus, then use the graph, and often community summaries, to ground generation. It is a good technique, and one node in a larger system can run it just fine. You can also run GraphRAG with almost no multi-agent workflow at all. Microsoft's own documentation makes the honest version of the argument: vector-only retrieval struggles when answering a question requires connecting information across relationships.
If graph engineering is going to mean anything durable, it is how the whole system moves, remembers, and stays correct. Retrieval is necessary, but it is not the architecture.
Retrieval is not three searches and a re-ranker
Almost everyone ships the same pattern. Fire a vector search. Fire a keyword search. Maybe fire a graph query. Take fifty from each, throw the pile at a re-ranker, keep what floats, stuff it in the prompt.
That pipeline has a structural flaw, and it is not latency. Each leg searches blind to the constraints the other legs know about. The vector index does not know this question is about one customer's contracts; it knows only that a question-shaped vector is near some passage-shaped vectors. So it returns fifty candidates and hopes. The keyword leg does the same. The graph leg returns a neighbourhood with no idea which parts are relevant to the sentence that was asked. Then a re-ranker, which can see the text and none of the structure, is asked to reconcile three lists that were each built without the others' information.
You over-fetch because that is the only defence against three blind legs. Over-fetching grows your token bill. Then you pay a second time, because the pile you carried back is mostly padding, and padding in the context window is not free: it costs money on the way in and accuracy on the way out, since a longer prompt gives the model more chances to ground its answer in the wrong sentence.
Run the similarity inside the traversal rather than beside it.
If embeddings are a field on the entity, and full-text indexes sit on the same rows, and relations are edges between those same rows, then one query expresses the whole thing: start from what the question is about, walk the relations that matter, and score by similarity and text relevance as you go, with permissions and time bounds applied in the same pass. Roughly:
SELECT
id,
name,
->stated_by->source AS provenance
FROM entity
WHERE ->works_at->company:acme
AND valid_until = NONE
AND embedding <|10, 40|> $questionThe structure prunes before similarity scores, and similarity ranks within what the structure allows. You are not searching a corpus and hoping the answer's neighbourhood comes with it. You are searching a neighbourhood you already have reason to believe in.
That buys two things.
Fewer tokens. You stop over-fetching, because a constrained candidate set does not need a wide net to be safe. You carry back a small precise set instead of three large ones minus whatever the re-ranker threw away, and you stop paying for the same fact three times because three stores each returned their own copy of it.
Higher accuracy. Every filter that was inexpressible in the vector leg (this entity, this scope, this time window, this permission, currently true rather than superseded) becomes expressible, because it is evaluated in the same statement as the similarity. Constraints you could only apply after ranking are now applied before it.
Our own numbers say the same thing from the other direction. When we were first developing Spectron, our memory layer, we tried the obvious thing: reduce each extracted fact to a short sentence, embed it, and add it as another dense leg in the hybrid mix. It fixed roughly a hundred benchmark questions and broke roughly as many again, landing slightly negative and inside the noise. Meanwhile the two changes that clearly won were both structural: routing from a resolved entity into its typed facts, and expanding a hit into its conversational neighbours. Both worth a couple of points on their own.
The lesson we took: reducing a typed fact to a sentence and matching it by cosine throws away exactly the structure that made extracting it worthwhile. A similarity list also cannot abstain. It always returns its k, so when the right answer is not there it returns something else with total confidence, and something else lands in the prompt. Structure can return nothing, which is frequently the correct answer.
A context graph is not an extraction job
The obvious objection: fine, so I bolt a graph database onto my stack and run an extractor over my corpus.
You can. What you get is a pile of assertions in a vocabulary the model improvised one turn at a time, and the difference shows up months later as retrieval that cannot find things you know are in there.
Here are the numbers from one conversation we ingested end to end. Of the attribute keys the extractor produced, 85% were used exactly once: 236 distinct keys across 325 facts, an average of 1.4 uses per key. Action verbs were nearly as bad, 80% used once. The two most common verbs in the whole set were shared_image and shared, describing the same act, counted separately.
Now consider what that does to a query. WHERE key = 'residence' finds nothing if this turn's extraction called it lives_in, and the next one called it home_city, and a third called it current_residence. The structured path, the one that was supposed to make the graph worth building, stays dark. Retrieval falls through to similarity, and you are back to the pattern in the previous section, paying for a graph you cannot query.
The interesting part is the control case. One relation family in that same measurement was not fragmented: 4.3 uses per label instead of 1.4, only 40% singletons. It was the one family where the extraction prompt already showed the model the vocabulary the system had established, with an instruction to reuse an existing label and only mint a new one when nothing fits. Same model, same text, same run. The only difference was whether the graph told the extractor what it already knew.
A context graph is a loop with itself, not a function over text. Feed the established vocabulary back. Canonicalise predicates at write time so two spellings of one idea land in one bucket. Resolve mentions against entities that already exist, rather than minting a new node for every phrasing, because the same customer becoming five nodes means every traversal lies.
Some hard-won specifics, since this is where the work actually is.
There is no correct base form a priori. We tried folding inflections and the naive rules are landmines: strip a trailing e and care merges with car; undouble a consonant and roll merges with role; strip a trailing s and news becomes new. Any stemmer aggressive enough to bucket reliably produces non-words that then surface in your API responses. What worked was narrower and duller: only ever fold onto a spelling this graph already uses, never toward an invented lemma.
Semantic merging is a judgement about meaning, not a normalisation. home_city and city_of_residence probably mean the same thing. "Use metric units" and "always answer in metric" probably do too. Fold them wrongly and you have discarded something a user explicitly said. We left that undone deliberately, and said so, rather than pretending a string function had settled it.
Time is two axes and they are not the same axis. When a fact was stated is not when the thing happened. If you store one and render it as the other, "when did X happen" gets answered with when it was mentioned, confidently and wrongly. Relative phrases are worse: "last week" is a window, not an instant, and letting the answering model do calendar arithmetic at read time from whatever dates happened to be in its context is exactly the failure you are trying to remove. Resolve at write time, against the source's own date, and store what was actually stated. Store nothing when nothing was stated, so that "no event time" and "the event time equals the ingest time" stop being the same value.
Dates do not belong in the text you embed. We had resolved windows interpolated into the fact sentence that was both the keyword document and the embedded vector. Those date tokens say nothing about what the fact means, and they pollute the retrieval key for every query. Dates live in columns. Renderers read columns. Text and columns cannot disagree if there is only one copy.
Half of what extraction produces is not linked to anything. In our measurement, 52% of action objects were free text rather than a resolved entity: the graph recorded that somebody did something to a string. That string is invisible to traversal. Extraction reported success. The graph got nothing it can walk.
An edge nothing reads is not memory. We shipped an alias edge, wrote a careful writer for it, and then pulled it, because no retrieval path traversed it. It was structure with no consequence.
All of this decides whether the context graph means anything. Meaning cannot be extrapolated from a graph that was never normalised, because the meaning was discarded before the write. And none of it is a one-time import: entity resolution and temporal invalidation are standing jobs, running every turn, or "what is true" decays into "what was true in March."
What actually breaks
Skip the forty-agent overnight graph. That is how you max a bill and learn nothing.
Here is what fails.
State lives only in the transcript. You cannot debug it, audit it, or resume it cleanly if that is the only place it lives. IDs, permissions, and intermediate facts need a home outside the prompt.
Everything is agentic. Money movement, PII, deletes, external side effects: those edges should be code, not model judgement. Give the model freedom where exploration actually pays off, and encode the paths you already know are correct.
Verifiers share the same spoiled context. Agents on the same model, reading the same flawed packet, agree with each other at industrial scale, which is why verifier context needs to be separated and drawn from evidence outside the graph: tests that ran, money that moved, a human weighing in before the expensive push. Organised nonsense is still nonsense.
You drew a DAG and production needed cycles. Retries, clarification, a human pausing and resuming the flow: these are not a design smell, they are how work actually works.
Your context graph is a pile of triples with no ontology. Model the domain before you extract at scale. Schema is what stops you from lying to yourself about what the graph actually means, not overhead for its own sake.
Extraction is stateless. Every turn invents its own vocabulary because nothing showed it the vocabulary that already exists. This is the single cheapest thing to fix and the one most people have not fixed.
The graph is built once and never maintained. Extraction is the easy day. Entities need resolving, or the same customer becomes five nodes and every traversal lies. Facts need expiry, or superseded beliefs sit next to current ones and both look true.
Retrieval is three searches and a re-ranker. Each leg is blind to the others' constraints, so you over-fetch to compensate, pay for the same fact three times, and hand a re-ranker the job of guessing what the query planner should have known.
Write-back is optional. The agent learns a preference, a failed approach, a new link between entities. If that write is not durable and visible under the same permissions as the read, the next node is guessing again.
No stop rule. Parallel agents without token, time, and tool budgets will happily burn through a budget without producing anything you can point to.
The shape everyone ships
Most "graph engineered" demos still look like this:
Four taxes, every time.
Context leaks at the seams. Relationships, history, and metadata fragment whenever they cross a boundary.
The same fact enters the prompt three times, and you pay for it three times.
No query can be planned across the whole picture, so every retrieval is a wide guess narrowed after the fact.
Ops multiplies: five configs, five monitors, five failure modes, and the glue quietly becomes the product.
You want the context graph and the durable data to be one thing. A context graph that cannot hold your application records, your documents, your vectors and your temporal state is a copy of your real data that you will spend the next year syncing.
This is why I flinch when graph engineering is reduced to "use a graph database." A graph-only store solves a real slice, but agents need documents, vectors, time, session state, and above all the ability to ask one question that is simultaneously a traversal, a similarity search and a filter. Split those across stores and that question stops being expressible at any price.
What this looks like inside a bank
Most of the graph-engineering conversation right now is about routing and reliability: how do I get five agents to hand work to each other without falling over. That is a startup's version of the problem.
Ask the same question inside a bank, an insurer or a global enterprise and the requirements change shape entirely. Every enterprise conversation I have had this year has landed on some version of the same list.
Shared context, not per-agent context. Twenty agents across four teams need the same view of the same customer. If each one has its own vector store, you do not have twenty agents, you have twenty inconsistent opinions with an org chart.
Permissions that travel with the data. Not permissions checked at the API gateway and then discarded. If retrieval cannot enforce who may see which fact inside the query itself, then every prompt is a potential exfiltration path and every new agent is a new review.
Provenance carried on the facts themselves. Who said this, in which document, on what date, and is that source still authoritative. "The model said so" is not a defensible answer when the fact ends up in a regulatory filing.
Auditable retrieval. Not just what the agent answered, but what it read to get there, and why those rows and not others. A re-ranker's opinion over three fused lists is close to unexplainable. A query with predicates in it explains itself.
Underneath all of it sits temporal truth: what did we believe on the day the decision was made, not what do we believe now. In a regulated firm the record exists to answer that question, which means facts need validity intervals and reads need to resolve as of a point in time rather than as of now. And the graph needs a boundary in both directions, with control over what is allowed into it and over what is allowed out of it into a prompt.
None of that is served by a prettier flowchart, and none of it is served by a vector store with a metadata filter bolted on. It is served by the context graph being a real database: transactional, permissioned, temporal, and auditable. The current discourse is not covering this, and it is what enterprises are buying.
What we set out to build
I am biased. I will say that up front.
We built SurrealDB as a multi-model engine: documents, graphs, vectors, time-series, full-text, relational, and auth in one system with one query language over them. The reason has nothing to do with graphs being fashionable. It is that the useful query is almost never one shape. It is "things related to this, that resemble that, that were true then, that this user may see," and every system that stores those four facts separately can only answer it by approximation and reassembly.
On the context side that means schema as ontology. Edges carry confidence, time, and source as fields rather than afterthoughts. Vectors live on the entities they came from, so similarity is an operator inside a traversal rather than a separate service you join against afterwards. Traversal, similarity, full-text, and filters resolve in one statement, planned together, instead of three round trips and a sync cron you forgot about until it pages you at 2am. Write-back lands transactionally under the same permission model you read from, which matters, but it is the floor rather than the pitch.
Spectron is the memory layer we built on that engine: entity extraction, predicate normalisation, entity resolution, temporal facts, and hybrid retrieval that is one query rather than three. Most of the engineering effort there has gone into what happens between the text arriving and the graph existing, rather than into retrieval algorithms. When the session ends, the context window forgets, but the system should not.
We are also building the comparison, because I would not take my word for it either: the same multi-agent application twice, once on an orchestrator plus a vector store plus a relational database plus a session cache, and once on the same orchestrator with a single context graph underneath. Same agents, same tasks, same model. Measured on context accuracy, tokens consumed, latency, network hops and lines of glue.
The control plane stays yours. We are complementary on purpose. The question I would ask is not whose orchestrator you use, but whether your retrieval can express a traversal and a similarity search in the same breath, and whether anything understood your data before it became a graph.
A checklist I would actually use
Scope
One recurring job with a real success metric, not a general assistant.
Someone owns the ontology, someone owns the workflow, and early on that can be the same person.
Execution graph
Label nodes: code / single LLM call / tool / full agent.
Edges only where work actually flows.
A verifier path with fresh context for high-risk output.
A human gate on irreversible actions.
Explicit stops and budgets.
Cycles where retries and clarification are real.
Before the context graph exists
Entity and relation types written down before bulk extraction.
Extraction shown the vocabulary the graph already uses, with an instruction to reuse it.
Predicates canonicalised at write time, folding only onto spellings the graph already has.
Mentions resolved to existing entities, with the unresolved rate measured rather than assumed.
Event time and mention time stored as separate columns, resolved at write time, absent when nothing was stated.
Nothing in your embedded text that is not part of what the fact means.
Context graph
Provenance on facts and edges.
Temporal validity, and a job that actually retires superseded beliefs.
A retrieval path that constrains before it ranks, explainable in one sentence.
Write-back after every turn for memories, entities, decisions.
Permissions enforced inside retrieval, not at the gateway.
Graph statistics on a dashboard: predicates per fact, unresolved mentions, duplicate entities. If you cannot see fragmentation, you will not fix it.
Reality checks
Outside-the-graph evidence for ship decisions.
Evals that include multi-hop and time questions.
Cost and latency budgets per node type.
Replayable state when something fails.
Grow the graph when the work forces you, not when a meme does.
What I would ignore
Renaming your orchestrator and calling it strategy.
Forty agents overnight.
Treating GraphRAG, context graphs, and execution graphs as the same slide.
Bigger context windows as a substitute for structure.
A better re-ranker as a substitute for a query that could have been planned properly.
Holy wars about anyone's database. The architectural gap is enough, and the market does not need another feud.
Closing
Graph engineering became the new buzzword because one chatty loop is not enough for real work. Good. Draw the execution graph. Be honest about where the model decides and where code decides.
Then do the half most threads skip. Understand the data before you write it: one vocabulary, resolved entities, real event times, facts that expire. Put the vectors on those entities rather than in another system, so retrieval is one query that traverses and ranks together instead of three searches arguing through a re-ranker. Make permissions and provenance properties of the data rather than of the API in front of it.
The label will get renamed again. It always does, and I would not build a roadmap on the word. But the two graphs are not a naming fashion. Every serious agent system has an execution graph and a context graph whether or not anyone drew the second one, and the gap between them is where the accuracy goes, where the token bill comes from, and where the audit fails.
Frameworks draw the graph of work. Something has to hold the graph of knowledge, keep it true, and survive the write-back. That is the problem we set out to solve with SurrealDB, and with Spectron, the memory layer we built on top of it. If your agent topology is getting serious and your context layer is still five separate systems you are hoping stay in sync, that is the seam to fix.