4 MIN READ · 07 JUL 2026 · agents

8 milliseconds. Claude Code used to take almost three minutes to find a file.

I replaced a 69,000-line code tool with 4,090 lines of Rust in a day, because a stale code graph is worse than none.

SB
Steven Battilana Quant · Zürich · ex-ETH

The graph that lied

Give an AI coding agent a map of your codebase and it stops guessing. A code graph is that map: every function, every call, every import, indexed so the agent can ask “who calls this?” instead of grepping blind. The map is meant to save you tokens and minutes. Mine did the opposite.

CodeGraph, the open-source tool I was running, kept answering from an index one edit out of date. I would rename a function, ask where it was used, and get the old answer back under a small warning banner. Claude ignored the banner, trusted the data, and dug into files that no longer matched. Confidently wrong, every time. When a lookup missed entirely, it gave up and grepped anyway.

A stale map is worse than no map. With no map, the agent knows it is blind and searches carefully. With a wrong map, it walks off the cliff at full speed.

The two-second hole

I read the source to find out why. The answer was a debounce. CodeGraph’s file watcher waits two seconds after an edit before it even starts re-indexing, and any query inside that window is served from the old index. On a fast edit-then-ask loop, which is exactly how an agent works, nearly every query lands inside the stale window.

That is not a bug you patch. It is the architecture. The tool was built to be eventually fresh, and an agent needs it fresh now. 69,000 lines of TypeScript, all resting on a timing assumption that does not hold for the one workload I had.

A code graph an agent can trust has a single invariant: it never answers from stale data.

One day, one invariant

So I rewrote it. Atlas is 4,090 lines of Rust, built in a day with Claude Code doing the implementation against my direction, benchmarked and adversarially checked at every phase before the next one started.

The invariant is a freshness gate at query time. The daemon tracks which files are dirty, pushed the instant an edit lands by a Claude Code hook, with an operating-system file-watcher as a backstop. Any query re-indexes those dirty files synchronously before it answers. Native tree-sitter parsing, where CodeGraph used WASM (WebAssembly, a portable binary format that runs slower than native code), makes that affordable: re-indexing one edited file costs 20 milliseconds, a callers query 8. Edit to hook to a fresh answer is 34 milliseconds, and it is never stale. The whole graph lives in RAM inside a per-project daemon. One binary, bundled SQLite, no service to babysit.

The receipts from one working session: 57 queries served, 38 edit notifications. A symbol that a grep expedition had spent 2 minutes 45 seconds and 6,600 tokens hunting for came back in a single call.

The parts that earn their keep

Two choices matter more than the raw speed. The first is honest misses. When Atlas cannot find a symbol it says so, names the nearest matches, and says why a file might be unindexed, so the agent falls back on purpose instead of inventing a location. The second is DRY as a feature. Before Claude writes a new function, a hook parses the code and asks Atlas whether something similar already exists. atlas_similar scores names by stemmed word overlap, so config_parser finds parse_config, even across languages: fetch_proposals matches fetchProposals. The duplicate never gets written.

It is not full type inference, and I will not pretend otherwise. Resolution is name-based with lexical guards. Type-checker sidecars, Astral’s ty for Python, tsserver, rust-analyzer, run asynchronously and add the inference-only edges without ever slowing a query. Atlas indexes Python, TypeScript, JavaScript, and Rust. It indexes itself. The AI that wrote it now queries it to avoid duplicating its own code.

The Takeaways

  1. Read the source before you trust the banner. The two-second debounce was in no doc. It was findable in an afternoon, and it explained every confidently-wrong answer I had been getting.
  2. A stale cache is worse than no cache when a machine consumes it. A human sees the warning and slows down. An agent reads past it and acts. If the data feeds an agent, freshness stops being a feature and becomes the contract.
  3. The fix can be architectural, not incremental. No amount of tuning the debounce closes a stale window that exists by design. Moving the freshness check to query time removed the failure mode instead of shrinking it.
Posted 07 JUL 2026 · filed under agents