EmpryoEmpryo.beta
← Blog
architectureJuly 25, 2026 · 5 min read

My code map was wrong about my own repo. So I taught it to learn.

A formatter in a side app ranked #1 across my entire codebase. Static analysis said it was important. My git history said otherwise. Here's what happened when I let the graph learn from how I actually work: 86 to 3,239 co-change pairs, and that formatter fell to #103.

EProxySoul

A formatter buried in a side app was ranked the single most important symbol in my entire codebase.

Not top ten. Number one. Above the agent loop, above the session store, above everything that actually matters. Every time the agent went looking for context, that formatter was near the top of the pile.

Static analysis wasn't wrong, exactly. Lots of things referenced it. By reference count and PageRank it looked like a hub. But nobody who had worked in that repo for a week would have called it important.

The graph was reading the code. It wasn't reading *me*.

Full disclosure as always: I build Empryo. Alone, nights and weekends, no funding. Everything below shipped in 3.5.0-beta and the numbers come from running it against Empryo's own repo.

Static maps go stale the moment you start working

Most coding agents that index a repo do it the same way. Parse the files, build a symbol graph, rank it, feed the top slice into context. Aider pioneered the good version of this years ago and deserves the credit for making it the default expectation.

But that map is built fresh each run and thrown away. It knows what your code says. It has no idea what you do.

Three things it can't see:

Files that always change together but never import each other. The migration and the config. The route and the fixture. The type and the mock. Nothing in the syntax connects them and yet touching one without the other breaks your build every time.

The file you always open *before* editing another one. That's a real dependency living in your head.

Which reads turned out to be dead ends. Static analysis has no concept of "the agent read this file and it was useless."

So I let it learn

Three sources, all cheap, none of them a model call.

Git history, scored properly. Naive co-change counting is garbage because some files change with everything. Your lockfile isn't coupled to your whole repo. It's just noisy. So the scoring is PMI, pointwise mutual information, which asks whether two files change together *more than chance would predict*. A real coupling stands out. A hub file that changes with everything scores near zero, which is exactly right.

It's rename-aware, so a git mv doesn't orphan everything the graph learned about a file. It skips process commits, because a release bump touching 40 files is not evidence that those 40 files are coupled. And I pushed the mining window from 300 commits to 2,500, which turned out to matter more than anything else on this list. 300 commits was missing most of the history on any repo with real activity.

Behavioral trails. Every session leaves a tool walk: what got read, in what order, before which edit. That distils into trails. Files read together. The file opened before another. The reads that went nowhere.

The obvious failure mode is reinforcing noise, so at the end of each session a judge marks which trails actually paid off. Trails that keep proving out graduate into confirmed knowledge. Ones that don't, decay. A single lucky session doesn't teach the graph anything permanent, and it shouldn't.

Your model's own follow-through. This is the part I didn't expect to build. Different models respond to hints differently. If a model keeps ignoring a whole class of hint, the gate on those hints tightens automatically. When it starts acting on them, the gate opens back up. And when you click into a file from a search result, that click-through feeds back into ranking — your clicks become part of how things rank.

What it did to my repo

empryo's own repo · static graph vs learned genome
──────────────────────────────────────────────────
co-change coverage      86  →  3,239  file pairs
edit-advisory pool      14  →    444  entries
side-app formatter      #1  →   #103  rank

That formatter went from first to 103rd. Nothing about the code changed. The graph just stopped confusing "referenced a lot" with "matters."

The co-change jump from 86 to 3,239 pairs is mostly the 2,500-commit window. The graph had been looking at a keyhole.

The part that pays for itself

Fixing a bug is two jobs. Find where it lives, then change it. Changing it is cheap. Finding it is where the money goes, because every file the agent opens while hunting stays in context and gets re-billed on every step afterwards.

That's why this matters beyond tidiness. In a head-to-head on real post-cutoff bugs from hono, zod and ky, a mapped agent used 5.7× fewer input tokens than an unmapped one for a better fix rate. And on the cheap model tier it fixed 3/5 where the unmapped one managed 2/5.

Give a small model a map and it stops behaving like a small model. I've come to think a lot of what we read as model capability is really just search budget.

What I'd push back on

This is measured on one repo. Mine. It's the repo I know best and the one whose ranking failures I notice, which makes me exactly the wrong person to grade it objectively.

Learning systems can also learn wrong things confidently, which is why the judge and the graduation step exist, and why trails decay. I'd rather it learn slowly than be sure about nonsense. Whether that's tuned correctly on a repo with fifteen contributors instead of one, I genuinely don't know yet. If you run it on a big team repo I want to hear what it gets wrong.

The whole subsystem shipped behind a 13-finding adversarial review and a 3,831-test suite. That catches bugs. It doesn't catch being wrong about the premise.

Your keys, your machine, and the genome build costs zero model tokens because it's tree-sitter and SQLite all the way down. It's at empryo.com.