context engineering for code

Context Engineering for Codebases: What Changes When the Model Has a Graph, Not a Window

A codebase is too big for any context window and too tangled for hand-picked files. The scalable fix is a structural graph the agent can query for exactly what this turn needs.

Ask a coding agent to make a one-line change to a function that's called from forty places, and the useful work happens before it writes any code. It has to work out who calls that function, what each caller expects back, and which of the forty matches for the name are the real thing versus a test fixture or an unrelated method that happens to share a name. A developer who has spent a year in the codebase does most of this from memory. An agent starting cold has to reconstruct it every time, inside a context window that was never sized for the job.

The earlier post on this site made the general case for why that reconstruction is now the real work: what a model reads before it answers matters more than how you phrase the question, and deciding what goes into that reading list, turn by turn, is a genuine engineering discipline. That argument holds for any agent. Software engineering agents are where it gets tested hardest, because a codebase breaks the assumptions most context-engineering techniques were built on. There's too much of it to read in full. Its parts depend on each other in ways the text of any single file doesn't show. And it keeps changing while the agent works, so whatever context got assembled five minutes ago may already describe code that no longer exists. This is what changes when context engineering moves from chat transcripts and documents to source code, and why the approach that scales is not a bigger window or a more carefully hand-picked prompt, but a structural map of the code that the agent can query for exactly what it needs, on demand.

Origin: the properties that make code the hard case

Most of the examples that built the general discipline of context engineering came from conversations, retrieved documents, and tool output: text that is mostly linear and mostly stable while you're working with it. Source code takes the same problem, deciding what belongs in front of the model, and applies it to an object with three properties that push harder than anything else the discipline has had to handle. There is a lot of it. Its parts depend on each other in ways that aren't visible from reading any one part. And it does not hold still.

Start with volume. Google's main source repository, documented by Rachel Potvin and Josh Levenberg in a widely cited 2016 paper, held two billion lines of code across nine million files, 86 terabytes of data, with around 25,000 engineers making roughly 16,000 changes a day by hand and another 24,000 through automated systems. Almost nobody runs a codebase that large, but the direction matters more than the extreme case: a few hundred thousand lines, unremarkable for a mid-sized engineering team, still runs to millions of tokens once every file is counted, and that's before counting the commit history, the tests, or the generated code. The instinct is to reach for a bigger window. RULER, a 2024 benchmark built specifically to test what long-context models can do rather than what they claim to do, found that effective context length falls well short of the advertised figure on model after model, with performance degrading steadily as input grows rather than holding flat until the window is actually full. A bigger number on the spec sheet buys less reliability than it appears to.

Then interdependency. Code isn't read the way prose is, start to finish, meaning accumulating as you go. A function's correctness depends on a type defined in another file, an interface implemented three directories away, a config value set in a startup script nobody opens twice a year. Microsoft Research's CodePlan team put the problem plainly in their 2023 paper: repository-level coding tasks can't be handled by a single prompt, because the code is inter-dependent and the repository is too large to fit in that prompt regardless, so they built a planner that runs incremental dependency analysis and change-impact analysis before touching a line, and tested it on repositories needing correlated edits to between two and ninety-seven files; it passed validity checks, building cleanly and editing correctly, on five of six repositories, where prompting alone passed none. Two benchmarks exist specifically to measure this gap. CrossCodeEval, from a NeurIPS 2023 paper, was built because standard single-file tests like HumanEval miss the difficulty entirely; it found models struggle badly on completions that genuinely require cross-file context and improve sharply once that context is supplied. RepoBench, from the same year, split the problem into retrieving the right snippet from other files and completing code once you have it, because those turned out to be different failure modes worth measuring separately.

And then churn. Context mapped an hour ago may already describe a codebase that no longer exists, and that's getting truer faster. GitClear's 2025 analysis of 211 million changed lines across repositories at Google, Microsoft, Meta, and a set of enterprise codebases found copy-pasted code climbing from 8.3 percent of changes in 2020 to 12.3 percent in 2024, a 48 percent relative increase, while the share of code that gets refactored rather than simply added fell from 24.1 percent to 9.5 percent over the same years. Code is being added and duplicated faster than it's being cleaned up, which means the map of what depends on what is shifting under whatever system is trying to describe it.

None of these three properties is fatal by itself. Together they rule out the two easy answers. You can't read the whole thing in, because of volume. You can't describe it once and trust the description, because of interdependency and churn together: a static snapshot of what matters goes stale exactly where it counts, in the part of the code that just changed.

Present: the limits of picking files by hand

The most common way teams do context engineering for code today is the simplest version of the "select" step from the general discipline: a person decides which files matter and puts them in front of the model, by pasting them in or by using the file-mention shortcuts most AI coding tools now support. It's manual retrieval, with a human running the ranking function instead of a system.

At small scale this works well, because it's close to free. A developer who wrote a twenty-file project last month still holds its shape in their head. Pointing an agent at the three files that matter for today's change costs them nothing they weren't already spending thinking about the task, and because they understand the dependencies, the selection is usually right.

That stops holding well before a codebase gets large, for a structural reason rather than a patience reason: choosing which files are relevant correctly requires already knowing how the code connects, which is close to the knowledge the agent was asked to help supply in the first place. Past a few dozen files, the person selecting is guessing as much as reasoning, and a bad guess fails quietly. Leave out the file that defines a shared type, or the caller whose behavior depends on exactly what you're about to change, and nothing throws an error. The agent answers fluently from the incomplete picture it was handed, and the mistake surfaces later, in review or in production, far from the moment the context was assembled.

Teams compensate in both directions, and both backfire. Overcorrect, and the instinct is to paste in more: the whole file instead of the function, the whole module instead of the file, everything in the directory to be safe. That runs straight into the degradation Nelson Liu and coauthors documented: models are least reliable on information buried in the middle of a long context and most reliable on what sits at the start or end, so padding the window with marginally relevant code doesn't just cost tokens, it can bury the one file that actually mattered. Undercorrect, and the model fills the gap itself, confidently. A study of package recommendations from code-generating models, led by Joseph Spracklen and coauthors, found LLMs hallucinate package names that don't exist on PyPI or npm at rates from 5.2 percent for commercial models to 21.7 percent for open-source ones. That isn't the model being careless. It's the model doing what it was set up to do, completing a plausible pattern, with nothing grounding it in what the codebase actually imports.

The industry has already taken one step away from pure hand-selection, and it's worth naming because it points toward the real fix without quite being it. Anthropic built retrieval into early versions of Claude Code and then pulled it back out. Boris Cherny, who leads Claude Code, said in a Hacker News thread that Claude Code doesn't use retrieval-augmented generation, because in Anthropic's own testing, agentic search, the model issuing its own file-search and read calls turn by turn, outperformed it for the kinds of things people use the tool for. That's a real step, from push to pull: instead of a human or a similarity score deciding up front what goes in the window, the model asks for what it needs when it needs it. It's also still search. The model is rediscovering the same relationships, who calls this, what does this import, from scratch on every task, using tools that read text rather than structure.

Present: querying a graph instead of guessing

A structural graph of a codebase isn't a new idea. It's what a compiler builds to resolve a symbol, what a language server builds to answer go-to-definition: files, functions, classes, and types as nodes, and the calls, imports, inheritance, and references between them as edges. What's newer is treating that graph as a source an agent queries for context directly, rather than a byproduct locked inside one IDE feature. Meta's Glean, which the company began open-sourcing in 2021, is the clearest large-scale example: it stores typed, schema-defined facts about source code, definitions, references, call relationships, inheritance, in a database queried with a Datalog-style language, and it's what lets a developer at Meta get go-to-definition and find-references across the whole repository the moment they open it, with no per-project indexing wait.

The same idea shows up at a much smaller scale in tools built specifically to feed LLMs. Aider, an open-source AI pair-programming tool, builds what it calls a repo map: a concise summary of the important classes, functions, and call signatures across a repository. The interesting part isn't that it builds a map. It's how it decides what belongs in that map once the map itself has to fit inside a limited token budget. Aider computes it as a graph, one node per source file, with edges connecting files that depend on each other, then ranks that graph to surface the parts of the codebase most connected to everything else, the same family of algorithm that ranks web pages by how many other pages link to them, standing in for a human's judgment about what matters. Even a lightweight tool aimed at individual developers converged on graph structure the moment "what fits in the window" had to be decided automatically instead of by hand.

The reason this resolves the tension the last two sections described is specific. A window that holds everything is complete but doesn't fit, and degrades what does fit through the middle-of-context effect. A human picking files by hand fits the budget but is guessing at structure they may not fully know, and the model quietly fills any gap with something plausible instead of something real. A graph splits the difference by being queried rather than included wholesale: on a given turn, the agent asks a scoped question, what calls this function, what implements this interface, what sits on the dependency path between these two modules, and gets back exactly the nodes that answer it. That answer is small, so it doesn't drown in the middle of a long context, and it's grounded in an edge that was actually extracted from the code, not a similarity score or a developer's memory, which is a direct answer to the hallucinated-import problem from the last section: a model retrieving from a graph of what a file actually imports has a much harder time inventing a package that was never there, because the invented name simply isn't a node it can retrieve.

This also changes the churn problem instead of ignoring it. A vector index of embedded code chunks goes stale the way any cache does, silently, and you find out when a retrieved chunk describes code that no longer exists. A graph extracted from source has a cleaner failure mode: it's either rebuilt from the current state of the repository, or it's out of date in a way that's at least in principle detectable, because it's derived the same way every time from the same source the agent is editing, rather than from a separate embedding pass drifting on its own schedule. Keeping it fresh is still real engineering. It's at least the tractable kind, an indexing problem, rather than something you can only manage by hoping nobody touched the file you didn't re-read this turn.

None of this retires plain search. Lexical, agentic search of the kind Claude Code settled on is still the fast, cheap way to find an entry point, where is this string used, which file defines this name. The graph is what answers the question that comes after: what does touching this actually reach. The two are complementary tools an agent reaches for turn by turn, not competing philosophies, which is the same shape the field converged on for text retrieval once graph-based retrieval showed up next to vector search: use similarity to find where to start, use the graph to find what's actually connected from there.

Future and impact: the graph as one more tool call

The likely direction is that this stops being a separate pipeline and becomes one more tool call inside the agent's existing loop, the way MCP turned connecting a tool to a model from a bespoke integration into a standard one. Instead of choosing between a file search and nothing, the agent chooses between a file search, a file read, and a graph query, the way it already chooses among a handful of other tools mid-task. A blast-radius query becomes as ordinary a call as a text search, and which tool to reach for on a given turn becomes part of the model's own planning instead of something decided for it in advance.

One place this pattern already runs outside a research paper is Spiderbrain, built by Perform Digital. It parses a codebase locally, on the developer's own machine, and turns it into what it calls a brain: a deterministic, source-free graph of the project's structure. Deterministic means the same codebase produces the same graph every time, which the team checks with roughly 350 automated tests on every release, unlike an LLM-written summary that can vary between runs on the same input. Source-free means the graph of structure and scores, not the underlying source, is what gets sent to Spiderbrain's EU-based servers for scoring; the code itself never leaves the machine that parsed it. The graph is exposed to coding agents over MCP and currently works with ten or more clients, including Claude, Claude Code, Cursor, Continue, Cody, and Zed, supporting blast-radius queries, a dependency and call graph, and memory that persists across sessions instead of resetting with the context window, which the team says cuts down on agents inventing file paths or dependencies that were never there, the same failure the package-hallucination study measured earlier, because retrieval is grounded in a graph rather than a guess. For more on how that graph gets built, the deterministic context graph explainer goes through the construction in more detail.

The broader shift is the one the first post in this pair described, applied to a harder object. First the lever moved from the sentence you type to the context you assemble. For code specifically, the next move is from context you assemble to context you query, because assembly, whether done by a person with a file picker or a model running its own grep, still has to rediscover the shape of the codebase from scratch on every turn. A graph doesn't have to rediscover it. It already has the shape on file, and the only job left is answering the question the agent actually asked. That's a smaller problem than context engineering started out as, which is usually a sign that a discipline has found the right unit to work in.

Council summary

This post extends the site's earlier argument about context engineering to the case that strains it hardest: source code. It argues that codebases break the general playbook along three axes, sheer volume (Google's monorepo ran to two billion lines and 86 terabytes), deep cross-file interdependency (why Microsoft Research built CodePlan around dependency analysis, and why CrossCodeEval and RepoBench exist as benchmarks), and constant churn (GitClear's finding that duplicated code has grown 48 percent since 2020 while refactored code has fallen by more than half). It shows why the two conventional fixes each fail on their own: a bigger window still degrades on information buried in the middle, per RULER and the Lost in the Middle study, and hand-picked files require the chooser to already know what the agent is being asked to help discover, a gap models fill by inventing plausible package names at rates up to 21.7 percent. The alternative it argues for is a structural graph, in the tradition of tools like Meta's Glean and Aider's repo map, that an agent queries on demand for the relevant neighborhood rather than a blob it has to read whole or guess its way around, with Spiderbrain offered as one working example of the pattern already in production. The reader's takeaway: past a small project, context for code should be something the agent asks a graph for, not something a person or a window tries to hold all at once.

Comments

Leave a comment

Your email won't be published. Comments are reviewed before they appear.
★ Read next