A coding agent cannot place an entire large repository in every model request. It needs retrieval: a way to select the small set of files, symbols, tests, and configuration that bear on the current task.
Code retrieval is not ordinary document search. Exact identifiers matter, but developers also ask conceptual questions. File boundaries, imports, generated artifacts, and version state affect whether a result is useful. A practical system therefore combines lexical retrieval, vector retrieval, metadata filters, and repository relationships, then measures the combination on real tasks.
Index structural units
Do not index only fixed-size text windows. Parse supported languages and create records for meaningful units such as functions, classes, declarations, and modules. Keep enough surrounding text to preserve imports, comments, and enclosing scope.
Each record should carry provenance:
- repository and commit identifier;
- path, language, and byte or line range;
- symbol name and structural node type;
- enclosing symbol or module;
- visibility and generated-file status when known; and
- the indexer, parser, embedding, and schema versions.
Tree-sitter can supply syntax boundaries. A language server or compiler can add symbol and reference facts. Neither should be silently substituted for the other.
Retrieve with complementary signals
Lexical search such as BM25 is valuable for exact names, error strings, configuration keys, and domain vocabulary. Vector search is useful when the query describes behavior without using the repository's exact words. Run both when the query warrants it.
The result lists need a defined fusion rule. Reciprocal rank fusion (RRF) combines rankings without assuming that lexical and vector scores share a scale. A learned reranker can be useful later, but only when there is enough representative relevance data to train and evaluate it.
Apply hard filters before ranking when they express real constraints: tenant, repository, commit, language, path, visibility, or generated-code policy. Do not use author identity as a relevance shortcut.
Expand through repository relationships
Search returns entry points, not necessarily complete context. After initial retrieval, expand selectively:
- declaration to callers or references;
- implementation to interface;
- source to nearby tests;
- route to schema and authorization policy; and
- changed symbol to build or configuration files that control it.
Expansion must have a budget. Unbounded graph traversal recreates the original context problem. Prefer typed edges, shallow depth, and task-specific rules.
Pack evidence, not a dump
Context packing should preserve provenance and make omissions visible. A compact item can include the file path, symbol, range, reason retrieved, and relevant excerpt. Deduplicate overlapping chunks and keep related declarations together when possible.
The model should be able to cite the repository locations behind a claim. If the retriever cannot find required evidence, report the gap instead of filling it with a plausible guess.
Invalidate by version
Repository indexes are derived artifacts. Key them by content or commit plus the versions of the parser, chunker, embedding model, and schema. A change to any of those inputs creates a new index version. Delete or age out older versions according to retention policy; do not mix records from different commits without labeling them.
Evaluate the retrieval system
There is no universal percentage improvement for hybrid retrieval. Measure it on the repository and tasks that matter.
Useful offline metrics include Recall@k, mean reciprocal rank, and nDCG over a labeled query set. End-to-end evaluation should also ask whether the agent found the right edit locations, changed unnecessary files, passed tests, and cited supporting code. Record latency and cost separately so a faster system cannot hide worse relevance.
Start with a reproducible baseline: lexical retrieval, vector retrieval, and RRF. Add dependency expansion or reranking only when an ablation shows that it improves the target workload.