ector similarity endpoint.
Architecture Decisions
- Tool-First Design: The model must have access to
glob, grep, readFile, and readTest tools. The model decides which tool to use based on the query.
- File-Granular Context: Retrieval should return complete files or logical blocks (classes, functions), not fixed-token chunks. This preserves imports and dependencies.
- Iterative Refinement: The agent should not stop after one retrieval. It must follow imports, inspect tests, and verify findings.
- Separation of Concerns: Code retrieval uses structural tools. Documentation retrieval uses semantic tools. The agent routes queries based on evidence type.
Implementation: Structural Retrieval Agent
The following TypeScript example demonstrates a CodeExplorer that provides structural tools to an agent. This replaces the vector store with direct filesystem operations and pattern matching.
import { execSync } from 'child_process';
import * as fs from 'fs/promises';
import * as path from 'path';
export interface RetrievalResult {
tool: string;
query: string;
content: string;
metadata: { file?: string; line?: number };
}
export class CodeExplorer {
private rootDir: string;
constructor(rootDir: string) {
this.rootDir = rootDir;
}
/**
* Finds files matching a glob pattern.
* Used to narrow search scope before reading content.
*/
async findFiles(pattern: string): Promise<string[]> {
const cmd = `find ${this.rootDir} -type f -name "${pattern}" | head -20`;
const output = execSync(cmd, { encoding: 'utf-8' });
return output.trim().split('\n').filter(Boolean);
}
/**
* Searches for exact string matches using ripgrep.
* Returns file paths and line numbers for verification.
*/
async searchPattern(pattern: string, fileFilter?: string): Promise<RetrievalResult[]> {
const filterArg = fileFilter ? `--glob "${fileFilter}"` : '';
const cmd = `rg -n --no-heading "${pattern}" ${filterArg} ${this.rootDir}`;
try {
const output = execSync(cmd, { encoding: 'utf-8' });
return output.trim().split('\n').map(line => {
const [filePath, lineNum, content] = line.split(':', 3);
return {
tool: 'grep',
query: pattern,
content: content.trim(),
metadata: { file: filePath, line: parseInt(lineNum, 10) }
};
});
} catch {
return [];
}
}
/**
* Reads a complete file.
* Preserves structure, imports, and context.
*/
async readFile(filePath: string): Promise<string> {
const fullPath = path.resolve(this.rootDir, filePath);
return fs.readFile(fullPath, 'utf-8');
}
/**
* Locates and reads test files related to a source file.
* Critical for understanding usage patterns and edge cases.
*/
async findRelatedTests(sourceFile: string): Promise<string[]> {
const baseName = path.basename(sourceFile, path.extname(sourceFile));
const testPattern = `*${baseName}*.test.*`;
const testFiles = await this.findFiles(testPattern);
const contents = await Promise.all(
testFiles.map(f => this.readFile(f))
);
return contents;
}
}
Rationale
findFiles vs. Semantic Search: findFiles uses glob patterns to restrict the search space. This is faster and more precise than embedding queries when the agent knows the file type or naming convention.
searchPattern with ripgrep: ripgrep provides exact matches with line numbers. This allows the agent to pinpoint definitions and usages. Vector search cannot guarantee line-level accuracy.
readFile Granularity: Reading the full file ensures the agent sees the complete class definition, including decorators, imports, and private methods. Chunking often splits these, causing the agent to miss critical context.
findRelatedTests: Tests are a goldmine of context. They show how functions are called, what edge cases exist, and what the expected behavior is. Structural retrieval explicitly includes test discovery, which vector RAG often deprioritizes.
Pitfall Guide
1. The Chunking Trap
Explanation: Splitting code into fixed-size tokens breaks logical boundaries. A function may start in one chunk and end in another, or a class may lose its import statements.
Fix: Retrieve by file or symbol. Use readFile to get complete units. If chunking is unavoidable, use AST-aware chunking that respects function and class boundaries.
2. Semantic Drift on Exact Symbols
Explanation: Using embeddings to search for API_KEY or error_code_404 often returns irrelevant results because the model prioritizes conceptual similarity over exact string matching.
Fix: Always use lexical search (grep) for exact symbols, error strings, and configuration keys. Reserve embeddings for natural language queries.
3. Ignoring Test Files
Explanation: Agents that only search source code miss the usage patterns defined in tests. This leads to implementations that compile but fail edge cases.
Fix: Include test discovery in the retrieval loop. When reading a source file, automatically fetch related test files to understand expected behavior.
4. Stale Vector Indexes
Explanation: Vector databases require indexing. If the codebase changes, the index becomes stale. An agent querying an outdated index may retrieve deleted functions or old logic.
Fix: Structural retrieval accesses the filesystem directly, so it is always current. If using RAG for docs, implement robust invalidation strategies or accept the latency of re-indexing.
5. One-Shot Retrieval
Explanation: Performing a single retrieval step and sending the result to the model limits the agent's ability to explore. Complex bugs require following a chain of references.
Fix: Implement an iterative loop. The agent should search, read, follow imports, and refine its query based on new information. This mimics the developer debugging workflow.
6. Context Bloat
Explanation: Reading too many files or entire repositories wastes context window and increases cost. Long context is powerful but not infinite.
Fix: Use narrow search tools (grep, glob) to identify relevant files first. Only read files that contain evidence. Summarize or extract relevant sections if the file is massive.
7. Mixing Evidence Types
Explanation: Treating code and documentation identically leads to poor results. Code requires exactness; docs benefit from semantic recall.
Fix: Route queries based on evidence type. Use structural tools for code symbols and lexical tools for docs. A hybrid agent should switch strategies dynamically.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Debugging Specific Error | Lexical/Grep | Exact match required for error strings and symbols. | Low |
| Understanding Architecture | Hybrid | Needs code structure + design docs. | Medium |
| Onboarding New Developer | Semantic/Vector | Broad conceptual questions benefit from semantic recall. | Medium/High |
| Refactoring Legacy Code | Structural + Tests | Requires exact references and test coverage verification. | Low |
| Answering Policy Questions | Vector RAG | Unstructured text benefits from semantic similarity. | Medium |
Configuration Template
Use this configuration to define retrieval strategies for different evidence types. This template supports a hybrid approach where the agent selects tools based on the query context.
retrieval:
strategies:
- name: structural_code
triggers:
- symbol_reference
- error_string
- file_path
tools:
- grep
- glob
- readFile
- readTest
params:
max_files: 10
include_tests: true
- name: semantic_docs
triggers:
- conceptual_question
- natural_language
tools:
- vectorSearch
params:
top_k: 5
index: documentation_index
- name: hybrid_repo
triggers:
- mixed_query
tools:
- grep
- vectorSearch
params:
code_weight: 0.7
doc_weight: 0.3
Quick Start Guide
- Install Ripgrep: Ensure
ripgrep is available in your environment. It is the standard for fast, exact code search.
- Define Tools: Create the
CodeExplorer class or equivalent tool definitions. Expose grep, glob, and readFile to your agent.
- Configure Agent Loop: Set up the agent to use tools iteratively. Prompt the model to search, read, and refine rather than retrieving once.
- Test with Code Query: Run a query like "Why is
checkout failing?" Verify the agent uses grep to find error strings and reads relevant files.
- Add Test Discovery: Enable test file search. Verify the agent reads tests to understand expected behavior.
Structural retrieval is not a rejection of RAG; it is a recognition that code requires different tools than prose. By treating the codebase as a structured graph and empowering agents with exact search tools, you build systems that are more precise, verifiable, and aligned with developer workflows. The future of AI coding agents lies in structural intelligence, not just semantic similarity.