The code knowledge graph
A code knowledge graph is a structured map of a codebase where every declaration is a node and every real relationship, calls, imports, inheritance, cross-service links, is an edge.
A code knowledge graph is a structured map of a codebase in which every declaration is a node and every real relationship between declarations is an edge. Functions, classes, and modules become the nodes. The ways they actually connect, by calling, importing, inheriting, or talking to each other across services, become the edges. It stores the resolved structure of your code rather than the text of it.
What the nodes are
The nodes of a code knowledge graph are declarations. A declaration is any place in the source where the code introduces a named thing: a function definition, a class, a method inside a class, a module or file, a constant, sometimes a type. These are the nameable units a program is built from. When you say a codebase has tens of thousands of functions, you are counting declarations, and each one becomes a node in the graph.
This matters because a declaration is a precise thing, not a word. The function called save defined on line 40 of one particular file is a single, specific node. It is not the same node as a different function save defined in another file, even though they share a name. A node stands for one exact definition in one exact place. Holding that distinction is the whole reason the graph can be trusted, and it is the first thing a plain text search cannot do, because to search, a name is just a string that might appear anywhere.
What the edges are
The edges are the real relationships between those declarations. The most familiar is a call: one function invokes another, and that is a directed edge from the caller to the callee. Another is an import: one file or module pulls in another so it can use what that other defines. Another is inheritance: one class is built on top of another and takes on its behavior. And then there are the relationships that cross the boundary between separate running programs, which are called cross-service edges. When one service sends a network request that lands in a handler inside another service, that is a real relationship even though there is no direct call in the code. It is an edge too.
The defining rule of every edge is that it must be real. An edge means that this specific declaration truly relates to that specific declaration, in the way the edge claims. Not that they might. Not that their names look similar. The graph is only as useful as its edges are honest, and an honest edge is one that has been resolved, which is the idea the rest of this page keeps returning to.
Structure stored, not strings
The clearest way to understand a code knowledge graph is by what it is not. It is not a text index. A text index, which is what a search tool builds, records where each string appears in the files. Search for a word and the index hands back every location that string shows up. That is genuinely useful, and fast, and it is the right tool for many jobs. But an index stores strings. It has no idea what any of them mean.
A code knowledge graph stores resolved structure instead. It does not record that the word save appears in nineteen places. It records that this exact function is called by those three specific other functions, and no others, and that it in turn calls these two, and that it lives in a class that inherits from that one. The difference is the difference between a concordance of a book, which tells you every page a word appears on, and an understanding of the book’s plot, which tells you who did what to whom and why it mattered. One is a list of appearances. The other is a model of how things relate.
A text index stores where words appear. A code knowledge graph stores how declarations connect. One finds strings, the other answers questions about consequences.
The questions search cannot answer
Because the graph stores relationships, it can answer a whole class of questions that a search tool cannot, no matter how fast the search is. The clearest example is the question every engineer asks before touching anything: what breaks if I change this?
Think about what that question really requires. To know what breaks if you change a function, you need everything that depends on that function, then everything that depends on those things, following the chain of dependence outward until it stops. That is a walk over edges. You start at the node you plan to change, step to every node with an edge pointing at it, then to every node pointing at those, and the set you gather is the answer. A search tool cannot do this, because it has no edges to walk. It can only find the string, and finding the string is the beginning of the question, not the answer to it.
The same is true of many everyday questions. Where does this value actually come from, traced back through the calls that produced it. Which parts of the system does this module truly depend on, all the way down. What is the shortest chain of calls that connects this entry point to that database write. Every one of these is a walk over a graph of resolved relationships. None of them is a search over text. This is why a codebase that has only ever been searched still feels opaque: the search answered the questions it could, and they were never the questions that mattered most.
There is a second class of question the graph answers that search cannot touch at all: questions about paths. Not just what connects to a node, but how it connects, through which chain of intermediate declarations. How does a value that starts at this web request end up written to that table? The answer is a path through the graph, a specific sequence of calls and hand offs from one declaration to the next. A search tool has no notion of a path because it has no edges to string together. It can find the request handler and it can find the database write, as two separate results, but it can say nothing about the route between them, and the route is usually the thing you were actually trying to understand.
Why an edge must be resolved, not guessed
Here is the pitfall that decides whether a code knowledge graph is worth anything. It is tempting to build edges by matching names. If you see a call to save, and somewhere there is a function named save, draw an edge between them. This is fast, and it is wrong often enough to be dangerous.
Real code is full of repeated names. A large system might have a dozen different functions called save, on different classes, in different modules, doing genuinely different things. It might have several called handle, or run, or process. If you draw edges by name, a single call to one specific save sprouts edges to all twelve. Now the graph claims eleven relationships that do not exist. When you walk it to find what breaks, it tells you code will break that has nothing to do with your change, and it stays silent about the one place that will. A graph full of guessed edges is worse than no graph, because it is confidently wrong, and people act on it.
A resolved edge is the opposite. Resolving a call means working out which exact declaration a given call actually reaches, by following the same rules the language itself uses: what names are visible at that point in the code, which module a name was imported from, which class an object belongs to. It is more work than name matching, and it is the entire difference between a map you can trust and a rumor. When this page says an edge must be real, this is what real means: the edge was resolved to one specific declaration, not guessed from a name that happened to match.
How the graph gets built
Building the graph runs, at a high level, in two stages. First you find the nodes. That means reading every source file and recovering its declarations: every function, class, method, and module it defines, with the exact place each one lives. This step turns loose text into a clean list of the nameable things in the codebase.
Then you find the edges. For each declaration you look at what it does, and for every call, import, inheritance, and cross-service link you resolve it to the specific declaration it reaches, using the language’s own rules for how names bind to definitions. The output is the graph: a set of declaration nodes joined by resolved relationship edges. The two stages have names of their own, declaration extraction for the first and edge resolution for the second, and each is deep enough to be its own topic. What matters here is the shape of the result. You put in a directory of text files and you get out a walkable network of real relationships.
One live graph across every repo
A single project is rarely the whole story. Real systems are spread across several repositories that call each other over the network, and the most painful relationships are the ones that cross those boundaries, because no compiler and no search within one repository can see them. A code knowledge graph earns its keep precisely here, because a cross-service edge is just another edge to it, and the walk that finds what breaks does not care whether the next hop is in the same file or a different service entirely.
Koragraph is built around this. It builds one live code knowledge graph across every repository you point it at, resolving not only calls, imports, and inheritance but cross-service links including HTTP, gRPC, package imports, message topics, Docker Compose, framework routes, git co-change, and Python runtime tracing. It parses the source with tree-sitter across twelve languages, which are C, C++, C#, Go, Java, JavaScript, PHP, Python, Ruby, Rust, Swift, and TypeScript or TSX. The facts it derives are stored in a local SQLite database at ~/.koragraph/practice.db, so the graph persists across sessions and follows your files when they are renamed rather than losing track of them.
Two details are worth stating plainly because they follow from the design rather than from any claim. It runs entirely on the developer’s machine, with no LLM or cloud dependency, so no source code leaves the machine. And it is reached through the Model Context Protocol, an open standard introduced by Anthropic in November 2024 that lets any AI model reach external tools and data through one common interface. That is what lets an AI coding agent query the graph directly instead of guessing at the relationships from the handful of files it can read at once.
The graph is only as trustworthy as its edges, and an edge is trustworthy only when it has been resolved to one specific declaration instead of guessed from a matching name.
Where this connects
The code knowledge graph is the whole of which several narrower ideas are parts, and the easiest way to understand it fully is to look at those parts. The idea of nodes and edges underneath all of it is the subject of what a graph is. The slice whose edges are function calls is the call graph, and it is the backbone of answering what breaks if I change this. The slice whose edges are imports and packages, at the coarser grain of files and modules, is the dependency graph. The edges that reach across separate running programs are cross-service edges. The reason this whole approach beats searching or matching by meaning is drawn out in graph versus grep versus embeddings. And the first stage of building any of it, recovering the declarations that become the nodes, is declaration extraction. Each of those is a short walk from here, and each one is this same map seen from a different angle.
Connected concepts
Where this sits
