Should You Rename Your Code for AI Agents
There is a good argument going around that if you want AI coding agents to work well, you should write code they can find. Agents navigate a repository by running ripgrep, so a function called create() costs an agent more than one called scheduleUpdateOnFiber(). The first returns hundreds of matches the agent has to open and discard. The second lands on the definition and its call sites. From there the conclusion follows cleanly: naming, typing, and file layout are now performance characteristics of your codebase, and you should treat them that way.
The mechanism is right. Agents really do retrieve by text search, and generic identifiers really do burn context. What deserves more attention is the bill, and who is in a position to pay it.
Every number below comes from facebook/react at commit 8384090, so you can clone that commit and check any of them.
Text search really is the retrieval layer
Give the argument its due first. Search React for the string update and the matches run past two hundred before you have looked at a single one, spread across the reconciler, the DOM bindings, the devtools backend, the release scripts, and the Flow type definitions. At roughly ten tokens a line before the agent has read a single surrounding function, that is a real slice of a context window spent confirming that most of the hits are irrelevant. React is a carefully maintained codebase with strong conventions, and the generic word still behaves this way.
Now search for scheduleUpdateOnFiber, the function React calls when something has told a fiber it needs to re-render. Thirty-two matching lines, six files, all of them inside packages/react-reconciler/src, and every one of them relevant. Three words, unambiguous, no synonyms competing for the same idea. This is the discoverable-code argument working exactly as advertised, on a name that was chosen well years before anyone was optimising for agents.
So the diagnosis holds. Where the argument gets interesting is the step from "generic names cost tokens" to "therefore rename your code."
What the retrieval papers actually measured
Two recent papers get cited in support of text search over structural retrieval, and both are worth reading closely because neither says quite what the summary says.
GrepRAG (January 2026) studies repository-level code completion on CrossCodeEval and RepoEval. The task is assembling cross-file context for a single cursor position. Naive grep matched sophisticated graph-based baselines, which is the finding people quote. The version that beat them, by 7.04 to 15.58 percent relative exact match, is the optimised one, and what it adds on top of lexical retrieval is identifier-weighted re-ranking and structure-aware deduplication. Structure earned the win. The paper's own stated failure modes are noisy matches from high-frequency ambiguous keywords and context fragmentation from rigid truncation boundaries.
Is Grep All You Need? tests lexical against dense retrieval on LongMemEval, a long-memory conversational QA benchmark. Inline grep beat vector search for every harness and model pair tested, 83.6 to 93.1 percent against 62.9 to 83.6 percent. Then the authors changed how results were delivered to the model, from inline to file-based, and vector won five of ten pairs. Their conclusion is that retrieval in practice means retrieval plus orchestration, and that swapping the agent harness moves accuracy about as much as swapping the retriever does.
Neither paper measured an agent trying to answer "who calls this" or "what breaks if I change this signature." Both measured find-the-relevant-span, which is the question text search is built for. The published discoverable-code experiments have the same shape: where is the retry backoff computed, where is the signature attached to the payload. Good questions, and single-hop ones.
The rename has a bill, and not everyone can pay it
Naming things well is free when the code does not exist yet. It costs almost nothing to call it scheduleUpdateOnFiber the first time, and React did. This is why the argument lands hardest with teams whose repositories are young, whose conventions are still soft, and in the strongest published case, whose code was largely machine-written from the start. That is the best case for the prescription, and it is a real one.
The bill arrives when the code already exists. Renaming a shared symbol touches every call site, needs review from people who did not ask for the change, and rewrites the file's git blame in a way that makes the next incident harder to investigate. If the symbol crosses a package boundary you own, you break consumers you do not control. And a rename only reaches code you are allowed to change: vendored dependencies, generated API clients, the service that arrived with an acquisition, and the subsystem whose author left in 2021 all keep their vocabulary regardless of what your style guide now says.
Partial coverage is where this shows up in practice. Refactoring a monolithic file into concept-named modules produces a measurable improvement on that file, and then the agent follows an import into the untouched helper module next door and is back where it started. The published experiments in this area report exactly that pattern, and it is the shape any incremental cleanup takes: the program works on the part you finished, and your agent keeps meeting the part you did not. A codebase-wide rename is one of those projects that is always eighty percent done.
A good name still does not tell you what breaks
Go back to scheduleUpdateOnFiber, the name that behaved perfectly a few paragraphs ago. Six files, thirty-two lines, no noise. Suppose an agent has been asked to change its signature and wants to know what it is about to break.
Notice something about those six files: every one of them lives in packages/react-reconciler/src. Resolved call edges give five files reaching it directly, which lines up closely with the text search. Naming did its job again, and inside the reconciler the two methods agree.
The disagreement starts one package over. Two functions in react-dom-bindings reach scheduleUpdateOnFiber through an intermediate hop, and neither file contains the string anywhere:
dispatchEvent ReactDOMEventListener.js:157
-> attemptSynchronousHydration ReactFiberReconciler.js:486
-> scheduleUpdateOnFiber ReactFiberWorkLoop.js:973
accumulateOrCreateContinuousQueuedReplayableEvent ReactDOMEventReplaying.js:184
-> attemptContinuousHydration ReactFiberReconciler.js:534
-> scheduleUpdateOnFiber ReactFiberWorkLoop.js:973
Both are resolved calls edges at high confidence, and both cross a package boundary. Change how scheduleUpdateOnFiber takes its arguments and browser event dispatch is in the affected set, but no search for the function's name will tell you that, because the connection runs through attemptSynchronousHydration and attemptContinuousHydration. The name is perfect. It is simply not written in the files that depend on it.
An agent working from text search alone sees the six and stops. To reach the DOM event layer it has to open the reconciler files, notice attemptSynchronousHydration, search for that name, find its callers, and only then arrive at dispatchEvent. That is the search-read-search loop the naming argument is trying to shorten. Better names make each round of the loop cheaper without reducing the number of rounds, because the number of rounds is set by how deep the call structure goes, and no naming convention flattens a two-hop path into a one-hop one.
No naming convention closes that gap, because the gap is not a vocabulary problem. Connectivity lives in the edges between symbols, and a text index does not store edges. We went through the mechanics of that walk in blast radius analysis, and the same limit applies to embeddings for a different reason, covered in code embeddings vs the code graph.
The read-side version of the same fix
Both approaches are aimed at one root cause. An agent starts every session with no model of your codebase and rebuilds one from string matches, and everything about your repository that makes those matches noisy makes the rebuild more expensive. That framing is correct and it is the useful contribution of the discoverable-code argument.
The write-side fix improves the strings. The read-side fix gives the agent something that was never a string: a prebuilt index of symbols and the resolved edges between them, so "who calls this" is a lookup that costs one tool call and returns a list, with no search-read-search loop and no dependence on what anyone named anything. It applies to the acquired service and the vendored client on the same terms as your newest module, because it reads structure out of the parse.
The usual objection to structural retrieval is operational, and it is a fair one: a language server per language, on every machine, for every checkout, is real setup cost for uncertain gain. That objection argues against running the analysis locally. It says nothing about whether the structure is useful, and a hosted index that computes edges once per commit and serves them over MCP removes the setup entirely. Symvanta is built that way for exactly this reason.
Do both, and know which one you are paying for
Write new code so it can be found. Distinctive names, precise types, modules named after concepts: all of it is free at authoring time and it genuinely lowers what an agent spends. The typing point in particular deserves more credit than it usually gets, because a type is checked and a comment is not, and a compiler error is feedback an agent can act on inside one turn.
What is worth resisting is treating a rename program as the path to agent performance on a codebase you inherited. That work is expensive, it lands unevenly, and even where it lands it leaves the connectivity questions unanswered. Those questions are the ones that turn a twenty-minute task into a two-hour one, and they are answerable today without touching a single identifier. We wrote up the broader set of failure modes in why AI coding agents fail on large codebases.
The fastest way to see which of your agent's questions are naming problems and which are structure problems is to point it at your own repository and watch where it starts guessing: book a 15-minute demo.