From Full-Text Search to a Project Knowledge System
When AI coding assistants work with large codebases, the real challenge is not how much code they can read, but how they decide what to search next. This article explains why agentic, multi-turn search—anchored on exact symbol matching and structured navigation—outperforms semantic vector retrieval alone for code, how a controlled tool loop gathers the right context, and why repositories should be organized as a layered, searchable knowledge system that serves both humans and assistants.
1. The Real Question: What to Search Next
When you hand a requirement to an AI coding assistant, the expectation is usually that it can locate the relevant implementation, understand module boundaries, and add changes in line with the existing style. Repositories routinely span tens of thousands of lines—too much for any context window, and stuffing unrelated files into the prompt is counterproductive anyway. The real question becomes: how does the system surface only the fragments that matter right now, within a limited number of turns?
One classic approach is to pre-chunk, embed, and store code in a vector database, then retrieve semantically similar fragments at query time and feed them to the model—retrieval-augmented generation (RAG). A different route delegates the decisions—what to search, which method to use, whether to keep searching—to the model itself: regex or keyword matching on content, file lookup by path patterns, reading local ranges by line number, spawning sub-tasks for parallel exploration, and, when language services are available, structured operations like go-to-definition and find-references. In industry practice, many coding scenarios end up relying more on the second route than on vector recall as the sole entry point. This is not a rejection of semantic search; it is a recognition that code as text has its own retrieval patterns.
2. Why Code Search Naturally Favors Exact Matching
Code is not prose. Identifiers, paths, config keys, error codes, and interface names must often appear verbatim—off by one character means a different symbol. Semantic vectors excel at “close in meaning,” but they can blend similar comments, similar naming, or near-identical implementations across different modules. The top-ranked fragments may look relevant without being the actual spot on the call chain.
The advantages of exact matching come down to three points.
First, anchors are stable. User questions often contain searchable anchors for free: function names, class names, protocol fields, log messages, config keys. Querying with these literal strings yields hits that are either real references or quickly ruled out. The cost of false recall is lower than with vector neighbors that merely “look alike.”
Second, structure is recursive. A hit usually reveals the next hop: a header file, an interface definition, a registry, a routing table, a test fixture. The model reads the local context, then constructs the next query—retrieval depth grows with the problem, instead of betting on all relevant chunks in the first step.
Third, evidence is verifiable. Results come back as file paths and line ranges, which both humans and models can read back against the source. Vector retrieval that only provides similarity scores lacks an explainable “why this matched,” making it easier to patch the wrong place later.
Semantic retrieval still has its place: when naming is wildly inconsistent, when comments and implementation use different languages, or when the user describes symptoms in plain natural language with no symbols at all, vectors or keyword expansion can provide candidate entry points. The sound division of labor is: semantic methods find the door; exact retrieval and structured jumps handle every hop after entering.
3. How a Multi-Turn Tool Loop Assembles the Full Picture
Think of the coding assistant’s retrieval as a controlled loop rather than a single query:
- Understand intent and extract anchors. Is the question about behavior, a defect, an interface, or configuration? Symbols, path fragments, and error strings should be used as queries first—not the whole natural-language sentence dumped into fuzzy search.
- Choose the method. If you don’t know where the file is, narrow the scope by filename or directory pattern; if you know the symbol, search content exactly; if you need call relationships, use definition and reference lookup rather than reading directories top to bottom.
- Control granularity. During the sweep phase, only paths and brief context are needed; read full functions or type definitions only after confirming the module. Reading whole files wastes context and can pull the model’s attention off track.
- Judge sufficiency. If you can see only a declaration but not the implementation, or a wrapper but not the registration point, take the next hop. Once the modification point, callers, and tests have all appeared, stop searching and move to editing and self-checking.
- Divide and conquer when needed. Questions spanning multiple subsystems can be split into non-blocking explorations, then reconciled where findings conflict—avoiding single-threaded wandering in the wrong directory.
Take a question like “how does a certain bridge layer record tool invocation for retrieval?” A reasonable process: match the tool name or log field across the repo to get a handful of files; inspect the mapping tables and summary logic around the hits; finally read the registration and call sites to confirm whether tracking happens before tool execution or after the result returns. Four steps or fewer is usually enough—provided those names are stable and mappings are centralized in the repo, not scattered across dynamically built strings that string matching can never hit.
4. Why “Brute-Force” Full-Text Search Is Still Fast Enough
Many assume that a million-line codebase requires a vector index, or search becomes unusable. For interactive coding, the bottleneck is often not disk scanning but the number of model round trips and the volume of context read in.
Modern source trees have favorable properties: text is highly repetitive but individual files are small; dependency directories, build artifacts, and generated code can be excluded; queries are short patterns, not full documents; and both operating systems and common search implementations cache directories in memory. After excluding irrelevant directories, a keyword scan over the workspace often has lower latency than one round trip to a remote vector service—and far lower than the cost of pushing large batches of candidate chunks through the model.
So the architecture worth optimizing is: default exclusion rules, result count limits, aggregating results by file instead of flooding by line, and two-stage filtering that narrows by path before searching content. Meanwhile, vector indexing brings its own maintenance burden—index builds, incremental updates, chunking strategies, and embedding model versions all become liabilities during branch switches and large-scale code generation. If exact retrieval can produce verifiable evidence within seconds, there is no reason to pay for an approximate index on top of every symbol.
5. Retrieval Is Necessary but Not Sufficient: Prepare the Repo to Be Found
An assistant understanding a project mainly through search does not mean the repository can be structureless. Search quality depends on whether query terms appear verbatim in the right files, and whether a hit leads via short paths to definitions and constraints. Maintain “entries for humans” and “entries for assistants” as the same set of artifacts:
- Stable public symbols. Use readable, unambiguous names for core modules, public interfaces, and error codes. Avoid key strings that only appear through runtime concatenation; if dynamic generation is unavoidable, keep a static registry or constant table nearby so search has a landing point.
- Predictable directory layout. Organize directories by domain or layer, with filenames reflecting responsibilities. Like a new hire, the assistant will first guess “where’s the config, where’s the adapter, where are the tests” from path patterns. The more cryptic the directories, the more likely early searches land in the wrong subsystem.
- Short, concrete entry documentation. In the repo root and each subsystem, keep brief notes: what this directory is responsible for, what it is not, where the key types live, how to run the relevant tests locally. Include real symbol names, not slogans. A few hundred words of map often reduces wasted retrieval more than another vector database layer.
- Register extension points centrally. Register plugins, tools, routes, and permission points in table-driven form, with names and implementation paths written in the same place—multi-turn search then converges naturally. Scattered decorators or framework magic with no static index forces the assistant to guess framework conventions, and error rates climb noticeably.
- Let tests serve as specifications. Failure cases and test names for critical paths should describe behavior and modules. When asking “how does feature X track invocations?” the test names themselves are high-quality query terms—and they provide expectations and counterexamples.
- Write constraints closest to the code. Style rules, prohibitions, and boundary conditions belong in short notes in the corresponding directories or in type annotations—not only in chat history or external documentation sites. The assistant reads the repository, not the team’s past conversations.
6. A Maintainable, Layered Project Knowledge System
There is no need to vectorize all business knowledge. The practical approach is layering—and making every layer hit-able by exact search:
- Symbol layer: source code, interface definitions, error codes, config keys. This is the source of truth; any summary that conflicts with it loses.
- Navigation layer: directory notes, architecture sketches, textual descriptions of module dependencies, extension-point registries. Keep it short, and explicitly reference files and names in the symbol layer so summaries link back to implementations.
- Constraint layer: which directories must not be hand-edited, the boundaries of generated code, compatibility commitments, security-related prohibitions. Constraints should be findable by keywords—e.g., a fixed heading like “Do not access storage directly here.”
- Verification layer: how to build, how to run the minimal tests, which log fields indicate success. If the assistant cannot verify its changes after editing, no amount of retrieval precision does more than raise the probability that results merely “look plausible.”
If you keep a semantic index at all, it fits best as a supplement to the navigation layer: recall over documentation and issue tickets to discover vocabulary mappings between “how users describe it” and “code symbols,” then immediately verify back at the symbol layer. Never let approximate fragments directly drive patches.
7. Asking Better Questions Shortens Retrieval
Developers can also reduce search branching. Including module names, verbatim errors, relevant paths, or recently changed interfaces in a question effectively pre-completes the first round of anchor extraction. Saying only “optimize performance” or “the bridge is broken” forces the assistant to start from high-frequency words across the whole repo—more turns, and a higher chance of editing a similar but wrong module.
Accept the limits of retrieval as well: higher-order calls in dynamic languages, heavy macros, cross-repo protocols, and switches that exist only in runtime configuration centers cannot be closed by workspace text alone. In these cases, sync key config samples and protocol field tables into the repo’s navigation layer, or attach a minimal reproduction and logs to the question—rather than expecting the assistant to semantically “guess” the one true value in production.
8. Conclusion
An AI coding assistant understands large projects through multi-turn, explainable, symbol-anchored retrieval, supplemented by structured jumps when needed. Vector recall is good for discovering entry points, but it cannot replace exact verification of identifiers. Full-text search, with build artifacts excluded, is usually fast enough—and the complexity saved should be reinvested in the repository itself: stable naming, predictable directories, short notes containing real symbols, centrally registered extension points, and tests that double as specifications. These artifacts serve humans and assistants alike, forming a maintainable project knowledge system—one that determines whether generated code fits your repository far more than any secret index does.
