Scoping Agent Context with the Node Graph
Why giving an agent the full dependency bundle is usually the wrong move, and how lazy subgraph scoping keeps codebase context focused, auditable, and useful.
If you've read Part 2, you've seen how to construct a bundle-grounded prompt for a chat model. You paste in the relevant PViz bundle, add a grounding instruction, and ask a codebase question. The judgment about what's relevant stays with you.
Agent pipelines are different.
When you're building an agent that needs to inspect, plan, or modify a codebase, that judgment has to become part of the system. The agent needs to decide — programmatically — what context it needs before it acts. That decision is not trivial. Give the agent too little context and it makes brittle assumptions. Give it too much context and the relevant structure gets buried.
The PViz bundle's node and edge structure gives you the raw material to make that decision well, but the way you use it matters.
The wrong move is to treat the bundle as a giant prompt attachment. The better move is to treat it as a navigable map.
That distinction is the core of this post.
Why the full bundle is usually the wrong input
The instinct when building codebase agents is to load everything upfront and let the model figure out what's relevant.
For very small projects, that can work. If a repository has 25 files and a simple dependency structure, the full graph is probably cheap enough to include. But once a project grows past a few hundred nodes, loading the full dependency bundle becomes the wrong tradeoff on two dimensions: cost and quality.
The cost problem is straightforward. A folder-level summary of a project might be small enough to keep in context all the time. A full node list is not the same thing. Depending on schema verbosity, a full-detail node list can carry paths, imports, exports, role classifications, symbols, transitive closure metrics, and other graph metadata for every module in the project. Even when the compact bundle format is manageable for a chat prompt, the unscoped node graph can still become expensive when repeated across agent steps.
That distinction matters. A one-time chat prompt can tolerate more context than a repeated agent workflow. Agents often plan, inspect, retrieve, revise, and act across multiple calls. If each step carries hundreds of kilobytes of mostly irrelevant graph data, the cost compounds quickly.
The quality problem is more important.
Even when a model can technically fit the full bundle into context, that does not mean the full bundle is the best input. The relevant modules are now competing with hundreds of irrelevant modules for attention. The issue is not only context length. It is context selectivity.
An agent reasoning over a near-megabyte node dump is in a worse position than one reasoning over a focused subgraph scoped to the task, even if the larger context technically contains the right answer. The signal is there, but it is buried in noise.
This is one of the common mistakes in LLM context design: treating "available to the model" as equivalent to "usable by the model." Those are not the same. Good context is not just large. Good context is selective, structured, and relevant to the next decision the model needs to make.
The alternative is lazy scoping: load the minimum structural context needed to orient the agent, then pull in node detail on demand based on what the task actually requires.
This is not a performance optimization after the fact. It is the right architecture from the start.
Two layers of information density
A PViz bundle contains different kinds of information, and they should not all be treated the same way.
The first layer is the folder index. This is the compact hierarchical summary of the project. It tells the agent how the codebase is organized without forcing it to reason about every file. For most agent workflows, this is the right thing to load first. It gives the agent enough structural orientation to make an initial routing decision.
The second layer is the node graph. This is higher-density information. Each node represents a module or file and may include imports, exports, role classifications, graph metrics, and other structural signals. The edge list describes relationships between those nodes.
Those two layers answer different questions.
The folder index answers:
How is this project organized?
The node graph answers:
What is structurally relevant to this specific task?
Keeping those layers separate is the core of the pattern.
The folder index belongs in the initial planning context because it is small, stable, and useful across tasks. It tells the agent that src/auth/ exists, that src/billing/ is separate from src/payments/, that src/core/ appears to contain shared infrastructure, or that packages/parser/ and packages/ui/ are separate workspaces.
The node graph should be retrieved selectively. It is where the agent goes once it has a hypothesis about what part of the system matters. Instead of loading every node upfront, the agent should request the structural neighborhood around the modules relevant to the task.
That distinction prevents the agent from starting every task with an undifferentiated wall of context.
A good agent does not need the whole map unfolded on the table. It needs a table of contents, a starting point, and the ability to zoom in.
Subgraph scoping
Given a task, the first step is identifying a starting node — the module most directly related to what the agent needs to understand or modify.
From there, subgraph extraction follows the edge list outward by some number of hops.
The depth choice matters.
A one-hop subgraph gives you the root node and its immediate graph neighborhood. If you are traversing dependency edges, that usually means the root and its direct imports. This is useful when the root is structurally central or when the task is narrow.
A two-hop subgraph gives you the root, its direct neighbors, and the neighbors of those neighbors. For many well-scoped implementation tasks, two hops is a reasonable default. It gives the agent enough surrounding structure to understand the local subsystem without pulling in the whole graph.
Three hops and beyond can expand quickly. In a mid-sized codebase, a three-hop traversal from a central module can return dozens of nodes. In a dense part of the graph, it can return much more. That may be appropriate for a task that genuinely spans multiple subsystem boundaries, but it is usually too broad as a default.
The implementation is conceptually straightforward: perform a breadth-first traversal over the edge list starting from the root node, stop at the selected depth, and include only the nodes and edges reached by that traversal.
That last part is important. Subgraph extraction should keep the graph coherent. Nodes and edges should stay aligned because the traversal is built from actual relationships. This is different from arbitrary filtering, where you might remove nodes but accidentally leave edges pointing to files that are no longer included.
For agent workflows, coherence matters. If the model sees an edge pointing to a missing node, it has to reason around a broken representation. A scoped graph should be smaller, but it should still be a graph.
Traversal direction matters
Subgraph scoping is not only about depth. It is also about direction.
If the task is to understand how a module works, outgoing dependency traversal is usually the right starting point. You want to know what the module imports, what those dependencies provide, and how the implementation is assembled.
If the task is to assess what could break if you change a module, incoming traversal matters more. You want to know what imports this file, what depends on those importers, and where the change could propagate.
If the task is to find where to add an extension point, you probably need both directions. You need to understand the implementation path, but you also need to understand how callers, registration points, or surrounding modules interact with it.
A two-hop dependency subgraph and a two-hop reverse-dependency subgraph answer different questions. They may contain different files. They may point the agent toward different risks.
This is why depth alone is not enough. Depth only makes sense once traversal direction is clear. A practical heuristic is to choose direction based on task type: understanding tasks follow outgoing dependencies; change-impact tasks follow incoming dependents; integration or extension tasks use bidirectional traversal; bug investigations start from the suspected module and traverse in the direction suggested by the failure path.
This does not need to be perfect. It just needs to be explicit. A mediocre traversal strategy that knows what question it is answering is usually better than a large generic context dump.
Finding the right root
The subgraph is only as good as the starting point.
One approach is to pass the folder index and the task description to a cheap, fast model call and ask it to identify candidate module paths. The folder index is small enough that this is a low-cost operation, and the task is simple enough that it usually does not require a large reasoning model. The goal is not a final answer. The goal is a shortlist.
Simpler keyword matching can also work well. If task descriptions reliably mention module names or domain terms that map to the repository's naming conventions, a deterministic search may be faster and more predictable than a model call. In many systems, the best root-selection strategy is hybrid: use keyword matching to find obvious path candidates, use folder index structure to eliminate poor matches, and use a small model call only when multiple plausible candidates remain.
The harder case is ambiguity. A vague task description could map to several places depending on how the project is organized. Forcing a single root too early in that situation can be dangerous — the agent scopes itself around the wrong part of the graph and then produces a confident but incomplete plan.
There are a few better strategies. The first is to extract small subgraphs from multiple candidates and compare them. If the subgraphs overlap heavily, merging them may give the agent a coherent view of the relevant subsystem. If they do not overlap, that is a signal that the task itself is ambiguous. The second is to rank candidates using multiple signals — path similarity, folder relevance, role labels, import relationships, and graph centrality can all contribute. The third is to surface the ambiguity. For interactive workflows, this is often the right move.
For fully automated pipelines, ambiguity handling should be conservative. If silent failures are expensive, the safest default is usually not "pick the highest-scoring root and continue." It is "scope multiple candidates, keep the initial action narrow, and require stronger evidence before modifying files."
Hotspots as a special case
Hotspots deserve special handling.
A hotspot is structurally important. Depending on the graph and role definition, it may have broad reach, high connectivity, high transitive closure, high in-degree, or some combination of those characteristics. Hotspots often surface as plausible starting points because they sit near important behavior.
But hotspots can also be scoping traps.
If you start a two-hop traversal from a structurally central module, the resulting subgraph may become almost as large as the full bundle. The task may be narrow, but the graph neighborhood is not. The agent ends up with too much context again, just through a different path.
That does not mean hotspots should be ignored. It means they should be treated differently. A reasonable default: start with one hop when the root is a hotspot, and let the agent expand from there on demand. For tasks that span multiple systems, merging several small scoped subgraphs is usually better than expanding one hotspot too deeply.
The goal is to give the agent enough context to understand why the hotspot matters without letting that hotspot swallow the graph.
This is especially relevant for modules like application entry points, central registries, framework adapters, or core orchestration files. They may touch many parts of the system, but they are not always the right files to modify. In many cases, the hotspot is a signpost. It tells the agent where behavior passes through. The actual change belongs in a narrower module connected to it.
On-demand expansion
The pattern that makes lazy scoping work in practice is giving the agent a tool it can call to retrieve additional node detail on demand.
Instead of loading the full node list upfront, you expose a simple lookup: given a module path, return the node's imports, exports, role, graph metrics, and adjacent edges. You can also expose more specialized graph tools — fetch dependents, fetch dependencies, extract a subgraph at a given depth and direction, find paths between two nodes. The exact tool surface depends on the agent and the workflow. The important point is that the model should not need all graph detail in its initial context. It should be able to request more when its reasoning justifies it.
This changes the agent's behavior in a meaningful way.
An agent working with a scoped subgraph and an expansion tool does not passively receive context. It navigates. When its reasoning leads to a module it has not seen yet, it asks for it. When it sees a dependency that may be important, it can inspect that node. When it needs to understand downstream impact, it can request dependents.
That is a more purposeful use of context than hoping the relevant node surfaces from a large undifferentiated dump.
It is also easier to debug.
Each expansion call is a traceable decision. The agent requested this module because its reasoning led there. If the agent makes a wrong turn, misses a relevant dependency, or draws an incorrect conclusion, you can reconstruct the navigation path: which root was selected, what the initial subgraph contained, which nodes were fetched during reasoning, and which files were ultimately touched. That trace is useful. With monolithic context loading, you know the information was present somewhere in the prompt, but you cannot tell whether the model used it, skipped it, or got distracted by something else.
Lazy expansion gives you observability. That matters as much as efficiency.
Why not just use embeddings?
A natural question is whether graph scoping is necessary if you already have embeddings or semantic search over the repository.
Embeddings are useful, but they solve a different problem.
Semantic search answers: what files sound related to this task? The node graph answers: what files are structurally related to this task? Those are not the same thing.
A semantic search might find files with matching names, comments, or function names. That is valuable. But it may miss a central rate-limiting utility that uses generic terminology. It may miss an event emitter, registry, or shared adapter that does not contain the task phrase. It may surface documentation or tests that mention the feature but are not structurally involved in the implementation path.
The graph complements semantic search by adding relationship structure. A useful sequence is: use semantic search to find candidate files based on language similarity, use the node graph to expand around those files and understand dependencies and integration points, then inspect actual source only after the candidate area is scoped.
Embeddings help locate. The graph helps scope. Source files provide implementation detail. Skipping the graph layer means the agent may jump from semantic match directly to source edits — which sometimes works, but on unfamiliar codebases increases the chance of editing a file without understanding how it fits into the system.
Why not let the agent read files directly?
Another reasonable question is why the agent needs a dependency graph at all. Why not give it file search and read tools and let it explore?
You can do that, and many agents do. The problem is that unguided file exploration is expensive and inconsistent.
The agent may open the first plausible file, follow a misleading import, inspect a test fixture, or chase a naming coincidence. It may eventually find the right place, but the path can be noisy. Worse, it may stop early after finding a plausible but incomplete answer.
The node graph gives the agent a structural prior before it starts reading source. It can see that one file is a leaf, another is imported by many modules, and another sits near the relevant subsystem boundary. That does not replace source reading, but it makes source reading more deliberate.
The right framing is not graph instead of source. It is graph before source. Use the graph to decide what to read. Then read the source to decide what to change.
Common failure modes
Lazy scoping is not magic. It fails when implemented carelessly.
The first failure mode is choosing the wrong root. If the initial root is wrong and the agent never questions it, the scoped subgraph creates a false sense of confidence. Ambiguous tasks should produce multiple candidate roots or an explicit uncertainty signal, not a silent best guess.
The second failure mode is using the wrong traversal direction. A dependency subgraph may explain how a module works but will not show everything affected by changing it. For impact analysis, reverse dependencies matter.
The third failure mode is expanding hotspots too aggressively. Central modules can pull in huge portions of the graph. If every task starts at an entry point with depth three, lazy scoping collapses back into full-bundle loading.
The fourth failure mode is hiding the retrieval path. If the agent can expand context but those expansions are not logged, you lose one of the main benefits of the pattern: auditability.
The fifth failure mode is treating graph context as a replacement for code. The graph tells you where to look and how files relate. It does not tell you every implementation detail. For actual modification tasks, the agent still needs to inspect source before editing.
These failure modes are manageable. The solution is not to abandon scoping. The solution is to make the scoping policy explicit.
The core idea
The underlying idea is the same one that runs through this series: the value of the bundle is not in giving the model everything. It is in giving the model structure.
Structure lets you scope. Scoping lets you control context. Controlled context lets an agent reason over a large codebase without pretending the entire repository belongs in every prompt.
That is the difference between a context window that technically contains the answer and a context window that helps the model find it.
For chat prompts, the developer can make those scoping decisions manually. For agents, the scoping decision has to become part of the pipeline. That is where the node graph becomes useful — not as a bigger prompt, but as a control surface.
What's next
Part 4 takes this further. Instead of scoping a task before the agent acts, we'll feed PViz output into a change impact analysis workflow. Given a diff or a PR description, an agent can reason about downstream effects using the edge list: which modules are in the blast radius, which are structurally insulated, where risk is concentrated, and where a small change may cross a subsystem boundary. That's where the graph structure really starts to pay off.
Try PViz on your own codebase
Get dependency graphs, coupling signals, and a compressed bundle ready for your LLM, for GitHub repositories, free with an account.