AI Coding Agent Context: Less Source, Higher Accuracy ⚡13 min read2,656 words

AI Coding Agent Context: Less Source, Higher Accuracy ⚡

Learn how AI coding agent context improves coding agent accuracy—ditch full dependency context, use Tree-sitter AST interface skeletons, and build safer filters.

AI coding agent contextdependency contextTree-sitterAST interface skeletoncoding agent accuracy
AI Coding Agent Context: Less Source, Higher Accuracy ⚡

Why Your Coding Agent May Do Better With Less Source Code

If you're editing one module, it can feel safer to give a coding agent the full source for every dependency. More context, fewer assumptions - right? In practice, dumping a large dependency tree can bury the details the agent actually needs under a mountain of noise: legacy helpers, implementation quirks, and outdated comments competing with the real contracts.

A benchmark that compared full dependency source vs compact interface skeletons turned up a clear result: the skeleton-based run passed all 20 hidden tests in one turn, while the full-source run passed 17 after three turns. It also used far fewer input tokens. Still, this isn't a universal law - one constrained task and one model setup can only tell you what works in that slice of reality.

So the real question isn't "more code or less code?" It's "what information helps the agent make the change correctly?" This article walks through what the experiment tested, why interface-first context can work, how to build a basic Python filter, and when you should still include implementation details.

Why full dependency context can hurt accuracy

A dependency file has more than what a caller needs. It often includes private helpers, compatibility shims, old comments, internal algorithms, and code that's irrelevant to the specific change you're making. Hand all of that to an agent and you raise the amount it has to sift through - without necessarily increasing what it understands about the task.

This is a signal-to-noise problem, not just a context-window problem. A model might be able to read a big prompt, but still give less attention to a small boundary rule hiding in thousands of lines. In real repos, the thing that usually breaks a change is often small and specific: whether 0 is allowed, whether output order is stable, or whether a function returns a new object vs mutating its input.

More tokens do not guarantee more useful context

In the experiment, the full-source run included verbose implementation bodies from peripheral modules. Some had deprecated behavior or helper logic that didn't reflect the current public contract. The agent then had to reconcile that noise with the task and the intended behavior of the modules it was editing.

That setup creates a handful of common failure modes:

  • Boundary conditions get buried. A rule like "zero is allowed" can get overlooked while the agent follows a nearby but wrong pattern.
  • Old behavior looks authoritative. A legacy comment or helper may seem relevant even if the supported interface has already changed.
  • Implementation details encourage copying the wrong approach. The agent may copy how the dependency works internally when it only needed to know how to call it.
  • Longer prompts make iteration expensive. Each repair turn keeps re-sending a lot of the same dependency text and diagnostic output.

The benchmark called this "attention dilution." That's a useful label, but it's not proof that every long prompt makes every model less accurate. The effect depends on the model, the prompt shape, the task, and how clearly relevant facts are presented. But if a dependency's implementation isn't needed to make a safe change, including it by default does carry real cost - and a plausible downside.

The target is sufficient context, not minimal context

This isn't about starving the agent. A signature alone can be too thin when behavior depends on ordering, error handling, mutation, rounding, or boundary cases. What you want is sufficient context: the smallest representation that still preserves the facts needed to make (and verify) the change.

For the caller side, that often means public signatures, type information, and explicit behavioral contracts. If you're changing the dependency itself, then the needed context might include the full implementation, relevant tests, and nearby code. The guiding idea is simple: context should follow the work you're doing, not be a blanket preference for "short" or "complete."

What the benchmark actually tested

The source describes experiment E21. It compared two harness modes on a three-module payment settlement pipeline: validator.py, settlement.py, and statement.py.

The task ran in an isolated Proxmox virtual machine with a hidden suite of 20 unit and end-to-end cases. Both runs used the same model configuration, gemini-3.8-flash, with a low thinking setting and temperature set to zero.

  • Mode A supplied full implementations of peripheral dependencies plus raw diagnostics across multiple turns.
  • Mode B supplied a compact interface skeleton generated from the modules' syntax structure, then submitted changes for all three modules in one turn.

Reported results say:

  • Mode A passed 17 / 20 tests.
  • Mode B passed 20 / 20 tests.

The contracts mattered more than the internal helper code

The skeleton wasn't just signatures. It preserved specific behavioral constraints that the hidden tests checked, including:

  • Validation must reject booleans before treating values as integers.
  • unit_cents == 0 is valid; negative values are not.
  • Normalized results must be new dictionaries with exactly five keys (no arbitrary extra input fields).
  • Settlement must preserve first-seen merchant and currency order instead of sorting.
  • A chargeback reserve applies only above 300 basis points (not at 300).
  • Discount thresholds are inclusive, and statement discount and rebate lines require a minus sign even when the amount is zero.

That's why "interface skeleton" can be misleading if you read it as "a list of function names." The useful artifact was an interface plus a precise contract. Removing implementation bodies reduced noise while keeping the information needed to satisfy the requirements.

Reported results, with the right caveat

Metric Full dependency context Interface skeleton Reported difference
Hidden tests passed 17 / 20 20 / 20 3 additional cases
Model calls 3 1 2 fewer calls
Cumulative input tokens 45,270 2,316 94.9% fewer
Tool output 78,719 characters 105 characters About 99.9% less
Elapsed time 29.99 seconds 12.84 seconds 57.2% less
Estimated API cost $0.0446 $0.0078 82.6% less

These are the experiment's reported measurements. They're not a guaranteed outcome for another repository, another task, or another model. The tested task, harness behavior, model settings, and hidden suite all matter. A single benchmark can suggest a pattern worth trying, but it can't prove skeletons will always beat full source.

In this specific case, the narrower takeaway is straightforward: for this task, a compact representation that made contracts explicit was more effective and cheaper than the tested full-source workflow.

How interface skeletons keep the useful context

An interface skeleton keeps what a module promises to callers and removes details meant for the module's internal mechanics. Depending on the language and project, that can include function and class signatures, type annotations, constants, and docstrings that describe behavior. It can also include selected contracts extracted from tests or maintained separately.

For example, a useful validator summary isn't just validate_txns(raw_txns). It should spell out what inputs are accepted, which values are rejected, whether the function mutates input, and what shape it returns. A concise contract can be more helpful than hundreds of lines of implementation when what you're really trying to do is rely on stable behavior.

Preserve behavior, not just syntax

A parser can identify function definitions and replace bodies with .... That's a useful compression step, but it can't reliably infer every behavioral guarantee from syntax alone. Type annotations can omit constraints. Docstrings can drift over time. Tests often encode edge cases that documentation never mentions.

Before you use a skeleton as agent context, check that it preserves the details most likely to affect integration:

  1. Input and output shape: required keys, optional values, return structure, and error behavior.
  2. Boundary rules: inclusive thresholds, zero values, rounding, and type-specific exceptions.
  3. State and mutation: whether inputs are changed, results are copied, or calls have side effects.
  4. Ordering: whether results are sorted, insertion-ordered, or otherwise stable.
  5. Compatibility expectations: supported versions, deprecated paths, and behavior callers still depend on.

If the contract is missing or stale, compression can shorten the prompt while increasing the odds the agent guesses. Treat contracts like engineering material, not "nice-to-have" commentary.

Why the reported code defects are instructive

In the full-context run, the submitted validator used {**item, ...}, which let unvalidated extra keys flow into the result. It also rejected unit_cents == 0 by checking cents <= 0 instead of cents < 0.

Those mistakes are small, easy to miss in review, and they map directly to the output-shape and boundary requirements the tests cared about.

The skeleton-based run returned only the intended five fields and allowed zero. That doesn't mean the model became magically more capable with fewer tokens. It suggests that, in this run, the compact prompt made the relevant constraints easier to act on. The operational lesson is simple: put the decisive constraints where the agent can see them, and don't bury them in unrelated implementation detail.

How to build a practical context filter

For Python, the standard-library ast module can parse source code and let you replace function bodies as a first pass. For multi-language repos, Tree-sitter can parse different grammars and support a similar projection. Neither tool automatically decides what information matters - it only gives you structure to build a filter on.

A minimal Python prototype might look like this:

import ast

def extract_interface_skeleton(source_code: str) -> str:

tree = ast.parse(source_code)

for node in ast.walk(tree):

if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):

continue

has_docstring = (

node.body

and isinstance(node.body[0], ast.Expr)

and isinstance(node.body[0].value, ast.Constant)

and isinstance(node.body[0].value.value, str)

)

ellipsis = ast.Expr(value=ast.Constant(value=...))

node.body = [node.body[0], ellipsis] if has_docstring else [ellipsis]

return ast.unparse(tree)

This follows the benchmark's basic idea: keep function definitions and docstrings, replace bodies with .... It's a starting point, not a production summarizer.

Add safeguards before using it in an agent harness

Test the transformation on representative files first. Nested functions, decorators, overloads, class methods, async functions, and module-level executable code need explicit rules. The example above only replaces function bodies; it doesn't ensure module initialization or class attributes stay safe, relevant, or concise.

Also don't assume ast.unparse preserves comments or formatting. Python's AST doesn't retain ordinary comments, and unparsing can change how code looks. If your codebase uses comments for contracts, preserve selected comments separately (or move critical behavior into docstrings or structured metadata). Tree-sitter can help, but you still need tests against your actual repository conventions.

Keep the transformation deterministic and inspectable. Log what files were summarized, what got removed, and how many tokens/characters were saved. Add tests that confirm public signatures and contract text survive. A filter that quietly drops a decorator, type alias, constant, or exception rule can make debugging worse than the original prompt length.

Finally, build a simple escalation rule. Start with the target module, its direct dependency interfaces, and the relevant contracts. If the agent hits a concrete ambiguity, reveal the specific implementation or test needed to resolve it. That keeps context focused without making the system rigid.

Treat the filter as a product feature

A context filter changes what the agent can know. It needs the same care you'd give other production tooling: tests, version-aware behavior, logging, and a clear failure path.

If parsing fails, don't "best effort" your way forward and hope for the best. Either pass the original file, stop with a clear error, or use a deliberate fallback. Also measure more than token reduction. Track completion rate, test failures, repair turns, latency, and situations where the agent asks for missing implementation details. The useful metric isn't "fewer tokens" - it's reliable work per unit of cost and time.

When you should include full implementations

You usually want full source when the agent is changing the dependency itself, diagnosing a bug in its algorithm, or tracing behavior that interfaces don't capture. A signature won't tell you about incorrect rounding, concurrency races, unexpected cache interactions, or side effects tucked inside an implementation.

It also makes sense to expose implementation when the public contract is weak, the code is unfamiliar, or the task explicitly depends on internal behavior. In those cases, hiding too much context encourages the agent to invent assumptions. The goal isn't "hide everything" - it's to include the implementation you have a reason to trust.

Use staged context instead of an all-or-nothing switch

A practical sequence for an agent harness:

  1. Start with the task and target files. Include the exact behavior requested and the files the agent is allowed to change.
  2. Add interface skeletons for direct dependencies. Preserve signatures, types, constants, and meaningful contracts.
  3. Include relevant tests or examples. Prefer tests that spell out edge cases over unrelated helpers.
  4. Escalate selectively. If a dependency's behavior is still ambiguous, provide that function's implementation or a focused excerpt.
  5. Run the test suite and review the diff. Context doesn't replace verification - especially for boundary-sensitive changes.

This keeps your context budget responsive to evidence. The agent starts with a compact system model and gets more detail only when it needs it. You avoid sending every internal helper to every task, but you still have a route to full source when it matters.

Decide based on what the task actually needs

Use interface-first context when the agent is acting as an integrator and correctness mainly depends on documented behavior. Use full implementation when the task is about internal mechanics or when the contract can't answer a concrete question.

In short:

  • If the agent is calling into the code, start with interfaces and contracts.
  • If the agent is changing or debugging internals, expect to provide implementation (at least for the relevant parts).
  • When the caller constraints aren't obvious from the interface, include what those constraints require - even if that means implementation.

Conclusion

The useful distinction isn't "full source vs no source." It's relevant context vs undifferentiated context.

In the reported benchmark, a compact skeleton that preserved contracts helped the agent avoid three edge-case failures while using fewer tokens, calls, and seconds. Those numbers are worth testing in your own environment, but they shouldn't be treated as a promise that skeletons always outperform source code.

Build context around what the agent must know to make a correct change. Keep contracts explicit, start with interfaces for peripheral dependencies, and include implementation when the task genuinely depends on it. Then measure results with tests, repair turns, latency, and cost. That gives you a context strategy grounded in your codebase, not a blanket rule about prompt size.

Frequently Asked Questions

No. The benchmark supports a focused approach for one integration task, not a universal rule. If the agent must debug or change a dependency’s internal algorithm, hiding its implementation removes relevant evidence. Provide the smallest context that answers the task, then expand it when the agent needs more detail.
At minimum, preserve public signatures, types, relevant constants, and behavioral contracts. Include rules for input validation, output shape, ordering, mutation, errors, and important thresholds when they affect callers. A signature without these guarantees can leave the agent guessing about the behavior that tests actually enforce.
It can support a basic filter, but the simple example is only a prototype. AST parsing does not preserve ordinary comments, and unparsing can change formatting. You also need policies for decorators, nested functions, module-level code, and parse failures, plus tests that confirm essential contracts remain visible.
Provide it when the task changes or diagnoses that dependency’s internal behavior, or when an interface and tests do not resolve a specific ambiguity. For routine integration work, start with the interface and contract, then add only the implementation excerpt needed to answer a concrete question.

Related articles