All posts
AI Research / Engineering

Why context windows alone cannot understand your codebase

Abstract repository graph indexing visualization with radial node structure

Every few months there is a new announcement about a model with a longer context window. 128K tokens. 1 million tokens. 2 million tokens. Each announcement comes with a demo where someone stuffs an entire small codebase into a prompt and the model answers a question about it. The demos work. The conclusion that gets drawn from them is often wrong.

A long context window is a useful primitive. It is not an architecture for understanding a codebase. There is a real difference between those two things, and getting it right matters a lot for whether an agent can do any useful work on a real repository.

What fits in a context window and what does not

Take a Java-based platform with 900,000 lines of code across 1,800 files, the kind of codebase a mid-size financial services company might have accumulated over eight years. At a generous 50 tokens per line, that is 45 million tokens. No current model can fit that in context. More importantly, even if one could, fitting all the text in a window does not give you structural understanding.

When a human senior engineer reads a codebase, they do not read every file in sequence. They build a mental graph: which packages depend on which, where the domain boundaries are, which modules are touched frequently, which functions are entry points versus pure internal utilities. That mental graph lets them answer questions like "if I change the return type of this method, what else breaks?" without reading every file that might call it.

A context window is a sequence. It does not natively represent a graph. An LLM reasoning over a sequence of files can approximate graph-like reasoning, but it is expensive (you are paying for every token of every file you include), brittle (quality degrades significantly toward the end of long contexts), and limited (you cannot include all 1,800 files anyway, so you need a selection strategy, which requires you to already know what is relevant).

How Pylon indexes a repository

Before Pylon runs any agent job, it builds a directed dependency graph of your repository. This is a concrete data structure: nodes are modules and files, edges are import relationships with directionality (A imports B is a different edge from B imports A). We annotate nodes with test coverage data from your CI, change frequency from git history, and type information extracted via language-specific static analysis (AST parsing for Python, TypeScript compiler API for TS, javac symbol resolution for Java).

The graph is built incrementally. After the initial index, we watch for commits and update only the subgraph affected by each commit. A commit that changes a utility function in one package triggers a re-evaluation of all edges that reference that package, but does not require re-indexing the parts of the codebase that have no import relationship with it. For a large repo, this makes the index fast to keep current.

When an agent job starts, we use the graph to select context. If the job is "add error handling to the billing API," we start from the billing API module and do a bounded-depth traversal: direct imports, the test file for billing, the interfaces the billing module implements, and the callers of the billing API in the service layer. That is the relevant context for this job, and it is likely 20 to 50 files rather than 1,800.

This is not magic. It is standard graph traversal applied to a problem that a lot of tools treat purely as a text retrieval problem. The difference between graph-first and retrieval-first shows up in practice when the relevant context is structurally connected but not textually similar. The caller of a function and the function itself may use completely different vocabulary. A vector similarity search would not reliably surface the caller when you ask about the function. A graph traversal does.

The problem with token budget strategies

An alternative approach, used by several tools we have studied, is to rank all files by BM25 or embedding similarity to the task description, then fill the context window with the top-k ranked files. This approach is sensible and works well for tasks that map cleanly to a single localized change. It has failure modes worth understanding.

First: important context is often in files that are structurally adjacent but textually unlike. An error handling policy in a shared util that is imported by 40 modules may be the most relevant thing for a given task, but its content might not score highly against a task description about the billing module if it uses generic utility language. Graph-based context selection finds it; retrieval-based selection often does not.

Second: retrieval-based selection has no concept of task scope. If you ask for a context window filled with the 60 most similar files, you might get 20 files from the billing module, 15 from the payments module, 12 from the auth module, and 13 from completely unrelated modules that happened to use some of the same terminology. That context is noisy. The agent working in that context may attempt changes that touch files outside the intended scope of the job.

We are not saying retrieval is bad. We use embedding similarity as an input to our ranking within the subgraph we have already selected via structural traversal. The graph gives us structural precision; retrieval gives us relevance ordering within that boundary. Neither is sufficient alone.

Graph coverage edge cases

There are things our graph does not capture well, and we want to be honest about them.

Dynamic dispatch is the hard one. Python code that calls a method on an object without static type information, or Java code that uses reflection, or JavaScript that builds method names at runtime: those relationships do not appear in the static import graph. We handle some of these via type inference and heuristic pattern matching, but we will miss relationships that are fully runtime-determined. For those cases we fall back to runtime call graph data from profiling if you have it configured, or we widen our traversal depth to include more potential callers as a conservative measure.

Configuration-driven wiring is another one. Dependency injection frameworks, Django URL routing, Spring bean configuration: the actual call relationships are defined in config files or annotations, not in import statements. We have parsers for common patterns in Django, Spring, and NestJS, but every codebase uses these frameworks a little differently, and we sometimes miss a connection. When we do, the agent may produce a diff that is locally correct but misses a caller that the config wires up.

The honest answer is that static analysis is an approximation of runtime behavior. Our index is better than a context window over raw text, but it is not a complete model of your program's execution. That is why every Pylon agent job runs with sandboxed test execution as a final verification step. The graph tells us what to look at; the tests tell us whether we got it right.

What this means for agent reliability

The quality of an agent's output is bounded by the quality of its context. An agent working with an accurate structural model of your codebase will make different changes than an agent working with a text search result set. The first knows the boundaries of what it should touch. The second is guessing.

For Pylon, the investment in graph indexing is what lets us make a credible claim about scope containment: when we say a Pylon job will not touch your authentication module when patching the billing API, we mean that as a property of our context selection architecture, not a hope. The graph boundary defines the job boundary, and work outside that boundary requires you to explicitly expand the scope.

Context window size will keep increasing. The models will keep getting better at reasoning over long sequences. We do not think that makes graph indexing irrelevant. It makes it more important, because the richer the context you can provide within a structural boundary, the more accurately the agent can reason about what it is actually working on.