Similar Code Is Not Connected Code


Ask an embedding-based system which code relates to
PaymentService and you will get back things that look like PaymentService.

Other payment classes. Functions with payment in the name. A test file. Possibly a payment integration in a completely different service that has nothing to do with yours.

What you will not reliably get is InvoiceWorker, which consumes the event PaymentService publishes and is the reason your invoices are wrong.

InvoiceWorker is not similar to PaymentService. Different vocabulary, different domain language, different file, probably a different repository. It is connected to it, which is a completely different relation, and the retrieval system was never measuring that one.

Two different questions

This distinction is worth being precise about, because both are legitimate and they get conflated constantly.

Similarity asks what resembles this. It is the right question for prose. If you want the section of a policy document that discusses refunds, semantic search is excellent, because the answer genuinely is the text that most resembles the query.

Connection asks what reaches this. Which function calls it, what it writes to, which service consumes its output, what breaks if it changes. These are edges in a graph, and no amount of resemblance between two nodes tells you whether an edge exists between them.

A 2026 write-up on how coding agents actually search puts the technical version of this well: a function calling another function is related in a way embeddings model poorly. Code has references, scope, type information and execution semantics, and flattening it into semantically similar paragraphs discards exactly the structure that makes it comprehensible.

Better embeddings do not fix it

This is the part that surprised me, and it is the reason I think the argument holds rather than being a temporary state of the art complaint.

The obvious response to any of this is that the retrieval just needs to be better. Denser embeddings, smarter chunking, a model trained on code rather than text.

Researchers testing graph-augmented retrieval against standard RAG checked this directly. They ran their failure analysis with sparse retrieval, then repeated it with a dense embedding baseline. Better embeddings improved recall on multi-hop queries. Every structurally dependent category still failed.

Their conclusion is that the failures come from the absence of graph topology in the index rather than from insufficient semantic similarity. The information about what connects to what is not weakly represented in a flat text index. It is not represented at all, and you cannot improve your way to a relation you never stored.

The same paper names three failure modes that transfer directly to code.

Structural opacity. A topology gets dissolved into text fragments, which makes multi-hop traversal impossible. Their example is a supply chain. Yours is API to controller to service to table.

Temporal blindness. Chunks carry no validity window. A retrieved fragment describing how something worked in 2023 sits alongside the change that replaced it, with nothing to indicate which is current.

Absence blindness. This is the one that should worry you most. Retrieval can only find what matches. It cannot represent or reason about what is missing.

Absence blindness is the dangerous one

Run that third failure against a question you actually ask.

You are about to remove a function. You want to know if anything still calls it. You ask, and nothing comes back.

With a graph, nothing coming back is a finding. The indexed code contains no callers, and you can check what is indexed.

With similarity retrieval, nothing coming back means the top-K chunks by cosine distance did not include a caller. That could be because there are no callers. It could be because the caller lives in a repository outside the index, or uses the symbol in a way that does not resemble your query, or was ranked seventh when you asked for five.

Both look identical. One is evidence, the other is a shrug, and they are being presented in the same voice.

When exactness matters, cleverness hurts

There is a practical version of this that is worth holding on to.

If a bug report mentions STRIPE_WEBHOOK_SECRET, you want every occurrence of that exact string. Not things semantically near it. The same 2026 analysis makes the point that boring retrieval beats clever retrieval whenever the answer depends on exact symbols, and code is full of exact symbols.

Embeddings earn their place when the words are fuzzy. Code mostly is not fuzzy. Identifiers are exact, call relationships are exact, and a table name is either in the query or it is not.

The failure mode when you get this wrong is documented too. Retrieval work on repository-level code notes that similarity-based retrieval can select code that is textually similar and semantically irrelevant, returning a variable that merely resembles the one you meant. The model then reasons confidently over the wrong thing, which is worse than an empty result because there is nothing to notice.

What this means for how you build context

This is the reasoning behind how Vorr AI approaches engineering context, and it is a deliberate architectural position rather than a preference.

Rather than treating a codebase as a corpus to embed and a cloud account as another corpus, Vorr AI builds relationships between engineering entities and keeps them. Which API is handled by which controller, which function queries which table, which service runs on which workload, which commit was in which deployment, which repository produced the artifact currently running. The retrieval is a traversal of those relationships, not a similarity ranking over fragments.

That is what makes a two-hop question answerable. Going from a slow endpoint to the function behind it to the table it queries is three edges in a graph. In a flat index it is three separate searches, each hoping the previous result contained enough vocabulary to make the next one work.

The property that follows is the one I actually care about. Answers arrive with a repository, a file, a line. If a claim is wrong, you can see that it is wrong in a few seconds. If a search returns nothing, you can ask what was indexed rather than wondering whether the embedding was unlucky.

None of that makes semantic search bad. It makes it the wrong instrument for a structural question, in the same way a thermometer is a fine instrument that will not tell you the pressure.

What I would ask of any tool doing this

Ask what happens when nothing is found. If the answer is the same regardless of whether nothing exists or nothing was indexed, you have a confidence problem rather than a coverage problem.

Ask whether it can answer a two-hop question. Which service consumes the events this service publishes. If it can only answer one hop, it is doing lookup rather than traversal.

Ask what it does with exact identifiers. An environment variable key, a table name, a route path. Fuzzy matching on those is a downgrade, not a feature.

Then ask what it stores about relationships, as opposed to what it can query. Access to your systems and understanding of how they connect are separate things, and only one of them survives the question of what depends on what.

The reason this matters is not retrieval quality in the abstract. It is that the questions worth asking during an incident are almost all structural, and similarity was never measuring structure.


Author note

The thing that shifted this for me was the empirical bit rather than the argument. I had assumed this was a quality problem that better models would eventually absorb.

Finding that improved embeddings raised recall while leaving every structurally dependent case failing was more convincing than any amount of reasoning about it. That is not a gap that closes. It is a different kind of question.

Mohan Gopi, Associate DevOps Engineer at Frigga Cloud, working on Vorr AI across AWS, GCP and Azure. LinkedIn.

Post a Comment

Previous Post Next Post