Does a call-graph index actually help coding agents?

Paired A/B studies on real repositories. Short answer: yes, on structural code questions. And the more surprising result: with the index, a small model does work that otherwise needs a frontier model.

codeindex is a call-graph index for Go, TS/JS, Python, and PHP. It answers who-calls-X, what-X-calls, and blast-radius questions with complete, always fresh path:line output. This page answers a narrower and more honest question than "is it good": when you give a coding agent this tool, does it spend fewer tokens and get more answers right, and on which kinds of task?

Where the value is, in one paragraph. Coding agents spend a large share of their budget finding code, not reasoning about it. On the tasks that are genuinely graph-shaped (who calls this, what breaks if I change it), the index cuts cost by half to two thirds for a frontier model, and for small models it is the difference between failing and working: an 8B model with the index matched a frontier model without it in our hardest suite. On tasks grep already handles, the index adds nothing, and we say so below.

Method

Each task is run twice under identical conditions: arm A (control, the agent has grep and read only) and arm B (the agent additionally has codeindex). Same model, same prompts, paired and repeated. Tasks are mined from real open-source repositories (gin, flask, nest, laravel/framework, and peers), and ground truth comes from ripgrep and the call graph, never from the index under test. The control arm is hard-isolated from the index by an environment guard, so it cannot leak.

Result 1: the index wins big on structural tasks, and only on those

Not everything benefits. The value concentrates in tasks that are graph-shaped: caller attribution, occurrences, blast radius. For comprehension and locate tasks, plain grep is already the right tool and the index is roughly a wash, occasionally worse. That distinction matters: a good setup routes by task type instead of reaching for the index every time.

occurrences−70% cost
caller attribution−70% cost (13 turns → 2)
edit impact / blast radius−56% cost
vague symbol find−45% cost
comprehension / locate≈0%, grep is fine

Bars show the median paired reduction in total task cost when the agent has the index. Figures from the Go OSS navigation and concept benchmarks (gin, prometheus, and peers).

Result 2: the effect is structural, it survives the Go to PHP boundary

The obvious worry: maybe these wins are memorized quirks of the Go repos the tool was tuned on. To test that, the full task set was reproduced on laravel/framework (PHP), a different language where static call resolution is genuinely harder (duck-typed dispatch produces ambiguous edges). If the signal is structural, its direction should hold across the boundary. It did.

Task type (PHP, laravel)Cost changeSuccess A→BGo baseline
occurrences−62%67% → 92%−70%
edit impact n=30/arm−60%77% → 90%−26%
vague find−14%42% → 58%−45%
comprehension≈0%100% → 83%≈0%

All four task types point the same direction as Go. On the two hardest buckets the index does not just cut cost, it improves correctness: edit impact up 13 points, occurrences up 25 points of success rate. "Use the index for structural questions, skip it for comprehension" is a property of the task type, not the repository or the language.

Result 3: you do not even need a router, over-retrieval hits the ceiling

Result 1 says the setup should route by task type. Route with what? A trained classifier (local embeddings plus logistic regression) picking the right tool per task scored only 70% end to end, and its errors concentrated exactly in the caller-shaped tasks where the index matters most.

The measured answer is simpler: do not decide. Each retrieval costs milliseconds, so run all of them (callers, symbol search, word-boundary grep) and emit one union answer. That deterministic pipeline scores 100% success, F1 0.95 to 0.99, per-task identical to the best per-question tool, on all three languages tested. No agent in the loop, no model, no routing. It ships as a product verb, codeindex nav.

classifier router70%, F1 0.65
nav union, Go100%, F1 0.96
nav union, PHP100%, F1 0.95
nav union, TS100%, F1 0.99

Every piece of the shipped pipeline sits behind its own measurement, including the final detail: nav's grep only switched to word-boundary matching after a re-run showed it held 100% success and raised F1 on every task set (short anchors like File stop matching superstrings).

Result 4: with the index, small models replace big ones on structural work

This is the strongest result and the clearest picture of where the value sits. We ran a five-model ladder, from a frontier-class cloud model down to a local 7B, on the 34 hardest caller and blast-radius tasks. Every model ran each task twice: once with only a shell (grep and read), once with the index attached as an MCP server, the same surface any MCP-capable agent gets. 340 runs total. Success means the model returned the complete, correct set of locations.

with index (MCP) shell only
glm-5.2 frontier-class, cloud
94%
88%
qwen3 8B cloud
85%
18%
qwen3-coder 30B local
74%
29%
qwen 7B local
59%
18%
qwen-coder 7B local
32%
0%

Read the chart top to bottom:

Two honesty flags on this experiment. First, a harness confound found in the trace audit: the control arms could still see disabled index tools in their tool list (every call was refused and nothing executed, verified across all 340 runs), which wasted some control-arm turns. So treat the small-model lifts as upper bounds; the frontier-model ceiling result is unaffected. Second, 4 of 340 runs died on local infrastructure after two retries and are counted as control-arm failures, worth at most 3 points in two cells. Both are recorded in the benchmark findings, next to a pre-registered sister experiment where a 30B local model with the index beat the frontier model without it, 64.7% to 38.2%.

The trust problem the index also fixes

Success rates undersell one thing the traces show clearly. When a small model fails a callers question with only grep, it usually does not say "I could not find them all". It returns a partial list and claims it is complete. In the ladder experiment, cheap models without the index claimed complete coverage while being wrong in 50 to 80% of their failures. With the index the top models nearly stop doing this (glm: 8 false-confident runs down to 2, the 8B: 13 down to 1). A deterministic evidence source is the cheapest grounding mechanism available: the model stops guessing because looking is cheaper than guessing.

A note on honesty

The first PHP edit-impact run (small sample) showed the index making the agent faster but less correct, a tempting story about ambiguous PHP edges. A larger 30-per-arm re-run refuted it: the effect was noise from a handful of runs, and at scale edit impact is a clean win (60% cheaper, 13 points more correct). The lesson recorded in the findings: do not build a mechanism narrative on a 12-row cell. Negative results on this page stay published: comprehension tasks do not benefit, the classifier router lost to not routing, and the confounds above are flagged where they occur.

Why this matters

A large fraction of a coding agent's spend goes to navigation, locating the relevant code, not reasoning about it. This project set out expecting to train a small routing model for that layer. The measurements kept refusing: the classifier router lost to running everything, cheap query-formulation gates lost to deterministic retrieval, and at every gate where a model might have earned its way in, structure plus cheap glue matched or beat it. So the conclusion is stronger than "the index helps": for navigation-shaped questions you can keep the expensive model out of the loop entirely, or swap it for a model an order of magnitude smaller, and spend real reasoning capacity only where reasoning is required. What remains genuinely open for a model is narrow and named: query reformulation on the weakest semantic-search repos, and the trust-or-verify call on [ambiguous] edges.