Two products can both promise your AI coding agent "a graph of your codebase" and mean two different things by it. One reads your files as text and asks a language model to guess at what connects to what. The other reads your files as code and resolves what connects to what, the same way a compiler has to before it will let the program run. Both call the result a graph. Only one of them is checking anything against reality.
The confusion is easy to fall into, because graph-based context has become a standard pitch across AI tooling, and the word graph is carrying more weight than it can support on its own. A companion post on this site covered GraphRAG, the technique of turning a document corpus into a knowledge graph so retrieval can answer questions a plain similarity search cannot reach. That graph is built from prose: contracts, wikis, transcripts, tickets, anything without a formal grammar behind it. This post is about the other kind of graph, the one built from source code, and about why code having a formal grammar and prose not having one changes what you're allowed to trust each graph to do. The short version of code graph vs knowledge graph is that they are answers to different questions asked of different material, and the process that built the graph, not the word graph itself, is what you are actually trusting when you use one.
Origin: two lineages that both landed on the word graph
Knowledge graphs and code graphs did not start from the same place, and the gap between their starting points still shapes how each one gets built today.
The knowledge graph lineage runs through the semantic web, formal ontologies, and the entity-relationship modeling Google put behind its search box in 2012. Building one meant a team of people deciding what kinds of things exist and how they relate, then populating that schema by hand. It was slow and expensive, and that constraint is exactly what GraphRAG removed in 2024: point a language model at a document, ask it to name the entities and the relationships between them, and a graph comes out the other end without a person populating anything by hand. That is a real advance on a real bottleneck. It is also, underneath the convenience, still a model reading a paragraph and forming an opinion about what the paragraph meant.
The code graph lineage runs through compilers, and it is decades older. A compiler cannot turn source into a running program without first knowing, exactly, what every name refers to and what depends on what. Before it emits a single instruction, a compiler tokenizes the source, parses it into a tree, resolves every identifier back to its declaration, and checks every type against the language's rules. That process produces a complete map of the program's structure as a side effect of doing its actual job: what calls what, what imports what, what implements which interface. Tooling has been pulling that map out of the compiler and putting it in front of people for a long time, from ctags indexing source files for quick lookup in Unix in 1979, to the Language Server Protocol that Microsoft opened up in 2016 so any editor could ask any language's own compiler backend what a symbol resolves to over one shared interface. A code graph for an AI agent is not a new idea grafted onto software. It is the graph the tooling already had, kept instead of discarded the moment the build finished.
Present: the same vocabulary is hiding two different processes
This is exactly where the confusion starts. Both kinds of graph use the same words: nodes, edges, relationships, traversal, community, neighborhood. A product page for a document-based GraphRAG tool and a product page for a code-intelligence tool can read almost identically at the level of vocabulary, because graph theory does not care what the nodes represent. Skim both pages and there is no obvious tell that one graph was inferred and the other was resolved.
Read enough product pages and the pattern repeats: we build a knowledge graph of your entire codebase, our agent understands the relationships in your code, graph-powered context for every repository. Every one of those sentences can be true of both approaches. None of them says which one is true of the product being described, and the marketing copy has no real incentive to volunteer the answer.
The tell is never in the vocabulary. It sits in the answer to one question: how the edge got into the graph. If the answer is "a model read some text and decided these two things were related," the edge is a claim, and claims can be wrong in ways that are hard to catch, because a wrong-but-plausible relationship reads exactly like a right one. If the answer is "a parser applied the language's grammar and a resolver applied its scoping rules," the edge is a fact about the source as it exists right now, checkable by opening the file. Everything downstream, what you can safely automate, what still needs a person to review it, what you would ever put behind a CI gate, follows from that one distinction and nothing else about the product.
Present: an LLM reading text versus a parser reading a grammar
Stated mechanically: a GraphRAG pipeline feeds a chunk of text to a language model and asks it to extract entities and relationships. The model reads a sentence about one drug inhibiting an enzyme and produces two nodes and a labeled edge. Run that over a large enough document set, cluster the result with community detection, and you get a searchable map of the corpus, a useful structure covered in more depth in the earlier GraphRAG post. It is also, at every edge, an inference. Change the model, the prompt, or the chunking, and the same documents can produce a different graph, because extraction is a judgment call the model is making sentence by sentence, and a recent survey of LLM-driven knowledge graph construction treats getting that judgment call verifiably right as one of the field's open problems, not a solved one. Is a knowledge graph built this way deterministic? No, and it was never trying to be. It was built to make a pile of text searchable by relationship instead of only by similarity, and it does that job well without needing to double as a fact generator.
Some tools run GraphRAG's own method on source code instead of prose: point a model at a repository, ask it to summarize what it thinks each file does and how the files relate, and call the result a code graph. Nothing stops that from happening, and the output can be a useful map of a codebase's apparent shape. But it inherits GraphRAG's core property along with GraphRAG's technique: the edges are the model's read of the code, not the code's actual behavior, and a model that misreads which function a call resolves to states the wrong answer as fluently as it states the right one.
Overloaded and identically named functions make the gap concrete. A large codebase might define a function called validate in a dozen different files: one for user input, one for a payment payload, one for a config schema. A model skimming a single file as text has to guess which validate a particular call site means, from context alone, the same way a person skimming quickly might guess. A resolver never guesses. The language's own scoping and import rules already say which validate a given name binds to at that exact line, so the resolver is reading the answer off those rules rather than inferring one. The same gap shows up when a file gets moved or renamed: a model can miss that an import now points somewhere else, because the surrounding file still reads the same way at a glance, while a parser re-resolves the import against the language's actual module system and gets it right by construction, not by noticing.
A parsing pipeline does not infer that one function calls another. It resolves it. Feed the same source file to a parser twice and you get the same abstract syntax tree twice, because the parser is applying a fixed, published grammar, not forming an impression of the text. Python's own ast module turns a .py file into the same tree on every machine that runs it, because the grammar is the specification. Tree-sitter, the incremental parser GitHub built for the Atom editor in 2018 and which now sits inside Neovim, Zed, Helix, and Emacs, does the same job fast enough to re-parse a file on every keystroke, and it still produces one tree, never a distribution over possible trees. Once you have the tree, resolving a call to its definition or an import to the module it points at is a lookup governed by the language's own scoping and type rules, the same lookup a compiler has to get right or the program does not run. Microsoft's Roslyn platform exposes exactly this as a public API, a syntax tree with full fidelity to the source plus a semantic model that answers what a symbol refers to and what type an expression has, built on the premise that tools should share in information the compiler already had instead of re-deriving it. GitHub's CodeQL makes the same move even more literally, building a queryable database by watching the real compiler process each file and extracting the abstract syntax tree, the name bindings, and the full data-flow and control-flow graphs into tables you can run queries against. None of that is a model's opinion about the code. It is the compiler's own account of the code, saved instead of thrown away.
Neither kind of code-facing graph is exempt from every limitation. Highly dynamic language features, reflection, eval, code generated at runtime, can leave even a correct static parse incomplete, because what a given line calls is not fully decided until the program runs. Static analysis has a name for the resulting trade-off: soundiness, the accepted practice of scoping a small set of highly dynamic constructs out of the analysis so the rest of it stays precise, because research has shown that chasing perfect soundness collapses either the precision or the scalability of the tool. A parsed graph is deterministic given its inputs and the rules it applies. It is not omniscient about what the program will do at runtime.
Present: why determinism is the property an agent actually needs
Determinism sounds like an abstract virtue until you ask what an agent is going to do with the graph, and the honest answer depends entirely on how expensive a wrong edge is.
Ask a document-based knowledge graph a question and a missing or wrong edge produces a worse answer. That is a real cost, but a soft one. The user reads the answer, it seems incomplete, and they go check the underlying document, which is still sitting there. The graph was a shortcut to the source, not a replacement for it.
Ask a coding agent what breaks if you change a function's signature, and a wrong or missing edge is a different kind of failure. If the graph says three call sites depend on that function and there are actually five, the agent, and the engineer trusting the agent, ships a change that compiles, passes the tests the agent thought to run, and breaks two call sites nobody was told about. There is no soft fallback here. The entire pitch of asking an agent to do this kind of blast-radius analysis is that nobody re-derives the dependency list by hand afterward to check it. For that one decision, the graph is not a shortcut to the ground truth. It is standing in for the ground truth, and that only works if its edges are facts rather than guesses, for the same reason a linker resolves symbols instead of asking a language model to guess which object file defines a given name.
This is also why you can put a test around a code graph in a way you cannot around a knowledge graph, in any comparable sense. You can assert, as a CI check, that after a refactor a given function is still called from exactly the set of places the graph claims, because that claim is falsifiable against the source and it terminates: either the call exists in the parsed program or it does not. There is no real equivalent for "the knowledge graph correctly extracted the relationship between these two clauses of a thousand-page contract," because the correct extraction is itself a judgment call, and the graph's job there was to surface a candidate for a person to weigh, not to settle the question on its own. Neither graph is lesser for this. They answer different kinds of questions, and only one of those kinds of questions has an answer a machine can check.
The same shape shows up anywhere a codebase's actual dependency structure has real cost attached to being wrong: finding every caller of a function before deprecating it, finding every consumer of a package before bumping a major version that changes its interface, finding every code path that touches a table before changing its schema. In each case the question is not "what does this code seem to be about," which is a fair question for a language model to answer well. It is "what, specifically, depends on this," a question the source already has one correct answer to, whether or not anything has bothered to compute it yet.
Future and impact: the question worth asking before the feature list
Agentic coding tools are being handed more autonomy with less human review at each step, not less, and that trend raises the cost of a wrong edge instead of lowering it. An agent that proposes a change and waits for a person to check every affected file can survive an incomplete graph, because the person is the backstop. An agent trusted to make the change, run what it believes are the relevant tests, and move to the next task is leaning on the graph directly, with no backstop in the loop, and a probabilistic graph presented with the confidence of a deterministic one is precisely the failure that does not show up until it is expensive.
None of this is an argument against ever using a language model near a code graph. A well-built agent can sit a model on top of a deterministic call graph to explain a dependency chain in plain language, rank which of forty affected call sites actually matters, or draft the fix itself, the same way a well-built GraphRAG system pairs vector search with graph traversal instead of picking one. The distinction that matters is narrower than for-or-against AI. It is whether the edges themselves, the actual claims about what depends on what, came from a resolution process that has to agree with the compiler, or from a model's read of the code as if it were a paragraph.
So the practical question, for anyone evaluating a tool that advertises graph-based context for a coding agent, is worth asking before anything else on the feature list: is this graph built by a model reading the code as text, or by a parser reading the code as code? The answer does not settle which product is better. It settles what the graph is for. Treat the first kind the way you would treat any language model output: useful, worth having, not something to assert a test against without checking. A deterministic code graph, the second kind, is closer to a build artifact than a guess, and it can carry weight the first should not be asked to carry, like gating a merge or answering what a change will break without a person re-deriving the answer by hand afterward.
Spiderbrain, built by Perform Digital, is one working example of the deterministic path. It parses a codebase's actual structure to build a real dependency and call graph, rather than having a model infer relationships from the source as text, and the project runs roughly 350 automated checks on every release specifically to verify that the same codebase produces the same graph every time. Parsing happens on the developer's own machine; only a source-free map is sent to Spiderbrain's EU-based servers for scoring, so the source code itself never leaves the device. The result is meant for exactly the kind of question this post has been about, blast-radius analysis and persistent memory an agent can draw on across sessions, reachable over MCP from clients including Claude, Claude Code, Cursor, Continue, Cody, and Zed. The parsing mechanics themselves, how the graph gets built deterministically, are covered in more detail separately.
Council summary
This post argues that graph-based context is not one technique under two names. It is two different construction processes that happen to produce the same-shaped data structure, and the process is the only thing worth checking before you trust either one. A GraphRAG knowledge graph is built by a language model extracting entities and relationships from unstructured text, which makes it a probabilistic artifact well suited to surfacing connections across material that never had a formal grammar to begin with, and poorly suited to being treated as settled fact. A code graph can instead be built by actually parsing source code, the same lexing, parsing, and symbol resolution a compiler already performs to do its own job, which makes it deterministic: the same input produces the same graph, and a wrong edge is checkable against the source in a way a mis-extracted contract clause is not. Neither approach is disqualified from using a language model elsewhere in the system, and the honest caveat runs in both directions: LLM extraction is improving, and even a correctly parsed code graph can go quiet around highly dynamic language features like reflection and eval. The question that actually matters when evaluating a tool is narrower than for-or-against AI. It is whether the edges came from resolution or from inference, because that is what decides what you are allowed to let the graph do on your behalf.
Comments