The default move, when you want an agent to understand a codebase, is to embed the whole thing. Chunk the files, turn them into vectors, store them, and at query time pull back the pieces that look most similar to the question. This is retrieval-augmented generation, and for code it is the thing almost everyone reaches for first.
It works more often than the skeptics admit. It also fails in a specific, repeatable way that no amount of better embeddings fixes, because the failure is not about quality. It is about what a vector can and cannot represent. So the real question is not “graph or RAG.” It is which one you reach for, for which question, and why.
Vector RAG retrieves by resemblance
An embedding turns a chunk of code into a point in a high-dimensional space, positioned so that things that mean similar things land near each other. Retrieval is then a proximity search. You embed the question, find the nearest chunks, hand them to the model.
What this is genuinely good at is fuzzy recall. You do not know the exact function name, you have a large unfamiliar codebase, and you want “the code that handles refund edge cases” or “anything related to rate limiting.” Resemblance is exactly the right tool there. It is cheap to build, tolerant of messy phrasing, and it shines precisely when you cannot name what you are looking for. And it keeps getting better. Techniques like contextual retrieval, which prepends explanatory context to each chunk before embedding it, close a lot of the gap that naive chunking leaves open.
What resemblance throws away
Here is the structural limit. When you chop a codebase into chunks and embed them, you delete the edges. The fact that chargeCard calls validatePayment, which is imported from a service that three other modules depend on, that entire web of relationships does not survive the trip into vector space. The chunks land near each other if they read similarly. They land far apart if they are wired together but worded differently.
So vector RAG is strong on “find me code like this” and weak on “find me code connected to this.” Ask it “what calls deleteAccount” and it returns things that look like deletion, not the actual callers. Ask “what breaks if I change this return type” and it cannot answer, because impact is a graph traversal and it kept no graph. The information was thrown away at indexing time, and no reranker recovers what was never stored.
This is not a knock on embeddings. It is a category fact. Similarity and connectivity are different relations, and a vector index encodes one of them.
A knowledge graph retrieves by relationship
A code knowledge graph stores the edges on purpose. Functions, files, services, and also the higher-level things: a decision, an acceptance criterion, the story a piece of code satisfies. The relations are explicit. Calls. Imports. Depends-on. Implements. Decided-by. Retrieval is traversal, not proximity.
That makes the previously impossible questions cheap. What calls this? Follow the calls edges inward. What breaks if I touch it? Walk the dependents. Why does this exist? Follow the edge from the code to the decision that produced it. These are exact, and they are multi-hop, and they are exactly the questions vectors cannot answer because the answer is the structure, not the wording.
The cost is real too. A graph has to be built and kept current as the code moves, and it only knows the relations you chose to model. It will not surprise you with a fuzzy semantic hit the way an embedding does. It answers what you asked, precisely, and nothing more.
When each one wins
No dogma. Two tools, two shapes of question.
Reach for vector search when the question is fuzzy and the codebase is unfamiliar. Onboarding onto a huge repo. “Where is anything about billing.” “Show me handlers similar to this one.” One-shot natural-language lookup where you cannot name the target. Vectors are cheaper to stand up and forgiving of vague input.
Reach for the graph when the answer is a relationship, and being wrong is expensive. Impact analysis before a refactor. “What are all the real callers.” Tracing a requirement to the code that implements it, or a piece of code back to the decision that justifies it. Anything an agent must get exactly right rather than approximately, because it is about to change code based on the answer.
The mistake is treating a preference as a principle. If someone tells you RAG is dead or graphs are overkill, they are describing their last project, not yours. The question decides.
A concrete miss
Here is the failure in one scene, because it is easy to nod at the theory and still ship the bug.
You are about to rename a database column and you ask your RAG-backed assistant what depends on it. It embeds the question, searches, and hands back three files that mention the column by name in a string. Confident, specific, incomplete. It missed the two modules that reach the column through an ORM relation and a computed property, because those files never spell the column name out, so they never landed near your query in vector space. You rename, the tests you knew about pass, and a report job breaks in production a week later.
A graph would have answered the same question by walking the reads and writes edges to that column, ORM indirection included, and returned the full set. The difference was not model quality or prompt phrasing. It was that one system stored the dependency and the other threw it away at indexing time.
The real answer is usually both
The strongest setup is not a contest. It is layered. Use vectors for recall, to find the candidate region of a large codebase from a vague prompt. Then use the graph for precision, to expand from those candidates along real edges and pull in the exact callers, dependents and decisions. Fuzzy to get in the neighborhood, structural to get it right.
This is roughly the intuition behind the Graph RAG line of work, which builds a graph over a corpus and traverses it to answer questions a flat vector search handles poorly.
For code specifically, the graph carries a thing vectors never will: provenance and the “why.” A vector store can tell you this chunk looks relevant. A graph can tell you this code exists because of that decision, and here is the story it satisfies. That is the layer that turns retrieval into reading your product’s shape instead of a feature list, and it is why a product brain that maintains itself is built on a graph and not on embeddings alone.
Where PaellaDoc fits
PaellaDoc builds the graph, on your machine, from your repo and its history: code related to the decisions and criteria that explain it, with the edges kept explicit so an agent can traverse instead of guess. It does not ask you to throw away vector search. It gives your agents the structural layer that vector search cannot represent, so the questions that depend on relationships stop returning things that merely look right.
Local-first and free, no cloud and no account.
What is the last question about your codebase that RAG answered confidently and wrongly? Tell me on the forum.
Frequently asked questions
What is the difference between a knowledge graph and vector RAG for code?
Vector RAG retrieves by resemblance: it embeds code into vectors and returns the chunks most similar to a query. A knowledge graph retrieves by relationship: it stores explicit edges (calls, imports, depends-on, decided-by) and answers by traversing them. One is good at fuzzy recall, the other at structural, multi-hop questions.
When does vector search beat a knowledge graph for code?
When you need fuzzy recall over a large, unfamiliar codebase and the question is roughly natural language: find code that does X, where is anything about billing, show me similar handlers. Vectors are cheap to build, tolerant of messy input, and strong when you do not know the exact names to look for.
When does a knowledge graph beat vector search for code?
When the answer depends on relationships the embedding throws away: what calls this, what breaks if I change it, why does this exist, trace this decision to the code that implements it. These are exact, multi-hop, structural questions, and a graph answers them by traversal instead of guessing from similarity.