How Aider's Repo Map Actually Reads Your Codebase — PageRank, Not Embeddings thumbnail
TeardownJul 2, 2026Developer tools

How Aider's Repo Map Actually Reads Your Codebase — PageRank, Not Embeddings

Aider is pitched as AI that reads your whole codebase. Read at v0.86.3.dev, the repo map is a ranking problem, not an AI one: tree-sitter pulls the symbols, a PageRank over the reference graph ranks them, and a binary search packs the top ones into a token budget. No embeddings, no vector store, no network call.

By ArcYou Editorial. Source read against the pinned repository states listed in Methodology.

Decision brief

What this source read clarifies

What it really is
A coding agent that compresses the codebase through symbol graphs and PageRank
Best for
Repositories where predictable context and low network dependence matter
Avoid if
Your workflow centers on semantic natural-language search or dynamic exploration during chat
Core mechanism
tree-sitter symbols, reference graph PageRank, token-budget packing

Read at: aider-v0.86.3.dev @ v0.86.3.dev

Aider gets described as “AI that reads your whole codebase.” I read the source at v0.86.3.dev, commit 7a1bd15, and the “reading” is a ranking problem, not an AI one. The model never sees your repo. It sees a repo map: a compressed, ranked table of contents of your code’s signatures — built by tree-sitter and PageRank, with no embeddings, no vector database, and no network call anywhere in the path. Every claim below points at a line.

What is it, and where does it sit?

A repo map is the block of context Aider prepends so the model knows what exists outside the handful of files you’re editing. Your codebase doesn’t fit in a context window, so Aider can’t just paste it. Instead it builds a budgeted outline: for the most important files, a few signature lines each — class/def headers with a little surrounding scope — and nothing else.

The whole subsystem lives in one file, aider/repomap.py (867 lines). “Important” is the word doing the marketing work, so that’s what I went to read. It turns out to mean something very specific and very mechanical.

The short version: Aider treats your repo as a graph of who-references-what, runs PageRank over it, and packs the top-ranked symbols into a token budget with a binary search. What surfaces isn’t what you edited recently or what’s semantically “similar” to your question — it’s what’s structurally central, tilted toward whatever files and names you’re currently talking about.

How does the map actually get built?

Four moves, in order:

Move What happens Where
1. Extract tree-sitter pulls definitions and references from each file; cached by mtime get_tags_raw :279
2. Rank build a reference graph, run PageRank with a personalization bias get_ranked_tags :365
3. Pack binary-search how many top tags fit the token budget get_ranked_tags_map_uncached :629
4. Render print the chosen signatures as a scoped tree to_tree :748

1. Extract — tree-sitter, not comprehension

Each file is parsed by tree-sitter via a per-language .scm query. A captured node becomes a definition or a reference by a pure string test on the capture name: name.definition.*def, name.reference.*ref (:319-322). There is no type resolution and no binding — a “reference” is just an identifier node the grammar labeled as one. For languages whose query only emits defs (C and C++ among them), Aider backfills refs by running pygments and keeping every Token.Name, with no line number (:338-363). Results are cached in a SQLite diskcache keyed on the file’s mtime, under .aider.tags.cache.v{3,4} (:43, :233-264), so unchanged files are never re-parsed.

2. Rank — PageRank over a reference graph

This is the heart. Aider builds a directed multigraph where an edge runs from the file that references a name to the file that defines it (:514), then runs networkx.pagerank over it (:525). The rank of each node is then redistributed across its out-edges so the output ranks (file, symbol) definitions, not just files (:533-545).

Two levers tilt that ranking toward you. First, a PageRank personalization vector: files in your chat, files whose name you mention, and files whose path matches an identifier you mention all get extra weight (:422-445). Second, per-edge weight multipliers, applied before the graph is built:

Signal Weight Where
Identifier you named in your message ×10 :492
Long (≥8-char) compound identifier (snake/kebab/camel) ×10 :494
Referencing file is open in the chat ×50 :508
Identifier starts with _ (private) ×0.1 :496
Identifier defined in >5 files (ubiquitous) ×0.1 :498

Reference counts are damped by a square root so a symbol used 100 times doesn’t swamp one used 4 (:512). Read the table as a priority: a private helper is worth a tenth of a public one; a name you typed is worth ten to fifty times more. The map re-weights itself around whatever you just said.

Aider turns definitions, references, user mentions, and chat files into a PageRank-scored repo map

Aider extracts symbols, builds a reference graph, scores it with PageRank plus user bias, then packs the highest-ranked signatures into the repo map.

3. Pack — a binary search against a token budget

The ranked list can be thousands of entries; the budget is small (default map_tokens=1024, :49). So Aider binary-searches the number of top tags to include (:666-706). It seeds the guess with a density estimate — min(max_map_tokens // 25, num_tags), i.e. ~25 tokens per tag (:676) — renders that prefix, counts tokens, and bisects. It keeps the largest rendering that fits and stops early once it’s within 15% of target (ok_err = 0.15, :689-696). The token count itself is an estimate for anything over ~200 characters — it samples every ~1/100th line and extrapolates (:89-101), which is exactly why a 15% slack exists. A handful of “important” files (READMEs, manifests, and the like) are always prepended regardless of rank (:657-662).

4. Render — signatures, not bodies

The winners are grouped by file and printed as path: followed by the relevant signature lines with a little scope context, via grep-ast’s TreeContext — no line numbers, no full bodies, and any line over 100 characters is truncated so a minified file can’t blow the budget (:748-784).

What’s not in here

For a feature marketed as “AI that reads your codebase,” the striking thing is how little AI is in the map. Its imports are os, math, sqlite3, tree-sitter, pygments, diskcache, tqdm, and — loaded inside the ranking function — networkx (:1-27, :368); nothing else. Grep the file for embed, openai, requests, vector, faiss, numpy and the only hit is a networkx docs URL in a comment. There is no embedding model, no vector store, no similarity search, and no network or LLM call in the map-building path. The one and only place a model is touched is token_count, and that’s a local tokenizer used to size the budget — not to rank or retrieve.

That single fact is the whole story. Aider’s relevance signal is graph centrality plus your explicit mentions, computed offline and deterministically. It is not semantic similarity.

So why does it behave the way it does?

Once you see the mechanism, the quirks stop being mysterious:

  • Add files to the chat and the map narrows; add none and it balloons. With no files in the chat, the budget is multiplied by 8 (map_mul_no_files) up to context_window − 4096 (:123-132) — a big orientation map when Aider knows nothing, a tight neighborhood once you’ve pointed at something.
  • Naming things steers it, hard. The ×10/×50 multipliers mean mentioning a function or file in your prompt yanks its neighborhood to the top. If Aider “can’t see” something, naming it is the intended lever — not a bigger budget.
  • It surfaces central code, not recent code. Nothing in the ranking looks at git recency or edit time; a file you touched an hour ago ranks by its place in the reference graph, unless you mention it.
  • Big repos pay once, then it’s cheap — the first scan warns it “can happen only once” (:391-395), thanks to the mtime cache; but a repo large enough to blow Python’s recursion limit disables the map entirely with “git repo too large?” (:143-146).
  • Same repo, same map. PageRank plus deterministic tie-breaks means the output is a function of your code and its mtimes, not a sampled or fuzzy retrieval.

Where does this fit?

The axis I’d use to place any coding agent is: how does it decide what code the model gets to see? There are two camps.

One camp is static-analysis-first: parse the code, build a graph or index of symbols, and choose by structure. Aider is the clean example — PageRank over a reference graph. The other camp is embedding-first: chunk the repo, embed the chunks, and pull context by vector similarity to your prompt. That single choice cascades. Static-analysis-first is deterministic, offline, cheap to re-run, and needs no index server — but it’s blind outside the languages it has tree-sitter tags for, and it finds code by name and centrality, so you have to name things. Embedding-first can find “the function that parses dates” when you don’t know its name, but it needs an index, an embedding model, and it drifts as the code changes.

Aider sits firmly in the first camp, and every behavior above falls out of that. This is also the cleaner half of the comparison to reason about, because there’s no hidden model deciding relevance — just a ranking you could recompute by hand.

When would you reach for it?

Aider’s approach is a genuinely good fit if you want context selection that’s deterministic, offline, and free of a vector-index service, and your code is in a language tree-sitter has tags for. It rewards a habit: name the files and symbols you care about, because mentions are the strongest lever in the whole ranking. Go in aware of the edges — if your repo is enormous, in an unsupported language, or you expect the agent to recall code by meaning without you naming it, a static reference-graph map is not going to find it for you. That’s the embedding camp’s job, and it’s a different set of trade-offs.

Methodology & scope

I read Aider v0.86.3.dev at commit 7a1bd15, focused on aider/repomap.py and its call sites in base_coder.py, models.py, and args.py. Three headline claims — PageRank ranking with the personalization/multiplier bias, the absence of any embedding/vector/network path, and the binary-search token packing — were each run through an adversarial claim-verifier against this commit and confirmed. Line numbers are exact at that commit; code quotes are kept to a line or two.

Scope caveats, stated plainly:

  • The visual shape of the rendered tree (how much scope context each signature gets) is produced by grep-ast’s TreeContext, an external dependency I did not read line-by-line. My claims are about what Aider feeds it, not its internal formatting.
  • Language coverage depends on which tree-sitter tag queries ship (roughly 30 languages here); the “must name things” caveat is strongest for languages that fall back to the pygments ref path.1
  • I did not benchmark runtime. The “scan once, then cheap” claim is from the mtime-cache code, not a stopwatch.

Footnotes

  1. The pygments fallback (:338-363) emits a reference for every name token with no line number and no dedup, so for def-only languages the graph edges are coarser than for a language with real reference captures. It still ranks; it just has a blunter view of who-uses-what.

Methodology

This post is read against the pinned repository states below. Directory names are convenience keys; the real pin is the commit SHA.

Repo keyVersionCommitUpstream
aider-v0.86.3.devv0.86.3.dev7a1bd15f0c78github.com/Aider-AI/aider

Related analysis

The decision axis is context ownership. Pre-compressing code, retrieving from a local index, or letting the model read files changes token cost and predictability.