A code index, measured against ripgrep
Agents search code with ripgrep. We built an index that returns exactly ripgrep’s lines from memory, kept current as files change, and measured it against ripgrep and four other tools on frozen query sets, then on 19 open source repositories in 13 languages. It is now Atrius, a command line and an MCP server for Windows, macOS and Linux.
- 140×ripgrep
- median query on Linux, held in the agent’s MCP process; 32× to 40× on three smaller repositories
- 2,200of 2,200
- queries on 19 open source repositories with ripgrep’s exact lines
- 0stale
- results after edits, renames, deletes, bursts of files and branch switches
- 0.95× tokens
- whole agent session, Sonnet, against a goal of 0.50
The index returns ripgrep’s exact lines 32 to 140 times faster, and stays correct through every kind of change. It leaves an agent’s accuracy and token cost where ripgrep leaves them: a capable model already uses ripgrep well enough that the cost of a session is mostly its instructions.
Can an index beat ripgrep for an agent?
Three goals were set before anything was measured: five hundred percent faster, half the tokens, and ten percent more accurate. Seven query families were frozen and hashed: names to find, grep patterns agents really ran, uses of a name, files by name, tickets to localise, questions in plain English, and questions the code cannot answer. Every timing was pinned to the same processor cores, and a run was refused when the machine was busier than a quiet reference.
Three goals, frozen before the first run
The goals and the query sets were hashed before any result and never changed. 30% of every query set was held out and used only for the final gate.
| Goal | Bar | Result | Verdict |
|---|---|---|---|
| Speed | at least 6.0× ripgrep, lower end of the 95% interval too | 26.7× to 36.1× on three repositories, 128× to 136× on Linux, three gate runs from cold hosts | Passes |
| Tokens | agent session at most 0.50× the ripgrep arm | 0.95× on Sonnet, 0.98× on Opus | Not reachable by construction |
| Accuracy | agent success at least 1.10× ripgrep, no family worse | 0.689 against 0.678 on Sonnet; exact families equal or better | Not met |
Fast and exact. No help with tokens.
- Held in the agent’s own MCP process, the index answered in a median 1.0 to 2.0 ms on three repositories against ripgrep’s 43 to 50 ms, and 6.1 ms against a second on the Linux kernel, with ripgrep’s exact lines on every one of 468 held out queries.
- Most of that lead on Windows is ripgrep’s Windows cost: a process start and file system calls on every search. In WSL ripgrep took 4 to 6 ms a query, and Zoekt, the trigram engine this design follows, 0.5 to 1.1 ms.
- Definitions are found at the first place about twice as often as a whole word ripgrep search finds them there: 0.50 to 0.83 against 0.21 to 0.36.
- Whole session tokens do not halve. Sonnet and Opus finish in five to seven turns with either tool, and each turn sends about 5,200 tokens of instructions and tool definitions again. Haiku, which takes three times as many turns, saves about a quarter.
- One plain English search written from a ticket finds the right file less often than the Grep calls an agent writes after reading the code. The index helps most when the agent already knows a name to look for.
- On 19 open source repositories in 13 languages, every one of 2,200 generated queries returned exactly ripgrep’s lines, and 54 of 54 hand checked definitions were found in the first three answers.
Against other tools, on the same machine
The 468 held out queries were grep patterns agents really ran, mined from 1,076 Claude Code sessions, plus names searched as whole words. Each ran once to warm up and three times timed. Zoekt does not build on Windows, so it ran in WSL against ripgrep there, which is ten times faster than ripgrep inside Claude Code on Windows: compare its absolute times.
| Tool | ai-service, median | Linux 6.16, median | Against ripgrep | Same lines as ripgrep |
|---|---|---|---|---|
| ripgrep in Claude Code | 50.11 ms | 999.84 ms | 1× | all |
| The index, in the agent’s MCP process | 1.96 ms | 6.11 ms | 32× to 40×, 140× on Linux | all 468 |
| The index, through a separate host | 4.91 ms | 75.87 ms | 9.5× to 10×, 15× on Linux | all 468 |
| ugrep with its own index | 22.22 ms | 661.60 ms | 1.7× to 1.9×, 1.4× | 92% to 97% |
| ugrep | 22.62 ms | 918.04 ms | 2.0× to 2.3×, 1.1× | 87% to 97% |
| git grep | 38.28 ms | 969.99 ms | 1.2× to 1.3×, 1.0× | 99% to 100% |
| Zoekt, in WSL | 0.58 ms | 1.06 ms | 5× to 6×, 51× against WSL ripgrep | 93% to 99% |
What it does for an agent
Agents ran with no settings loaded and a fixed tool list: Grep, Glob and Read in one arm, the index and Read in the other. 121 held out tasks: tickets from our task board (which files does this change touch) and questions about the code.
| Model | Arm | Success | Median session tokens | Median turns |
|---|---|---|---|---|
| Sonnet, 3 draws | ripgrep | 0.678 | 44,024 | 5 |
| Sonnet, 3 draws | index | 0.689 | 47,065 | 5 |
| Opus, 1 draw | ripgrep | 0.736 | 66,556 | 6 |
| Opus, 1 draw | index | 0.702 | 58,501 | 7 |
| Haiku, dev split, 2 draws | ripgrep | 0.475 | 577,684 | 15.5 |
| Haiku, dev split, 2 draws | index | 0.513 | 529,496 | 16 |
The same lines as ripgrep, in 13 languages
After the study the index became Atrius, and Atrius was tested on code other people wrote. Writing the test found two faults, both fixed: Lua writes its C functions with a space before the parameters, so they were not read as definitions, and vite’s createServer ranked behind eleven copies in its playground folder.
Nineteen repositories other people wrote
Each repository is pinned to a release tag. 120 queries per repository were made from its own text (40 on Linux): literals, case insensitive, whole words, calls, alternations, anchored regexes, phrases, and the same scoped by glob, type and path. Times are medians for one query: the index function in process, and ripgrep as a process with the Grep tool’s flags, which includes starting it.
| Repository | Language | Files | Equal to ripgrep | Index | ripgrep | Definitions found |
|---|---|---|---|---|---|---|
| spf13/cobra | Go | 67 | 120 / 120 | 0.50 ms | 15.95 ms | 3 / 3 |
| lua/lua | C | 111 | 120 / 120 | 0.50 ms | 16.78 ms | 3 / 3 |
| psf/requests | Python | 130 | 120 / 120 | 0.49 ms | 16.11 ms | 4 / 4 |
| BurntSushi/ripgrep | Rust | 213 | 120 / 120 | 0.56 ms | 17.25 ms | 4 / 4 |
| expressjs/express | JavaScript | 226 | 120 / 120 | 0.49 ms | 17.00 ms | 3 / 3 |
| sinatra/sinatra | Ruby | 293 | 120 / 120 | 0.47 ms | 16.87 ms | 2 / 2 |
| google/gson | Java | 304 | 120 / 120 | 0.62 ms | 18.10 ms | 3 / 3 |
| square/okhttp | Kotlin | 509 | 120 / 120 | 0.78 ms | 19.69 ms | 3 / 3 |
| Alamofire/Alamofire | Swift | 569 | 120 / 120 | 1.34 ms | 21.00 ms | 3 / 3 |
| nlohmann/json | C++ | 1,091 | 120 / 120 | 0.99 ms | 29.34 ms | 3 / 3 |
| JamesNK/Newtonsoft.Json | C# | 1,159 | 120 / 120 | 1.86 ms | 28.30 ms | 3 / 3 |
| redis/redis | C | 1,608 | 120 / 120 | 1.58 ms | 30.27 ms | 3 / 3 |
| vitejs/vite | TypeScript | 2,228 | 120 / 120 | 1.58 ms | 35.13 ms | 4 / 4 |
| laravel/framework | PHP | 2,544 | 120 / 120 | 2.14 ms | 35.69 ms | 2 / 2 |
| django/django | Python | 6,811 | 120 / 120 | 4.54 ms | 76.52 ms | 3 / 3 |
| golang/go | Go | 13,227 | 120 / 120 | 9.21 ms | 122.28 ms | 3 / 3 |
| ziglang/zig | Zig | 17,712 | 120 / 120 | 5.95 ms | 161.14 ms | 2 / 2 |
| scala/scala3 | Scala | 21,976 | 120 / 120 | 17.46 ms | 232.26 ms | 3 / 3 |
| torvalds/linux | C | 89,668 | 40 / 40 | 525.65 ms | 785.26 ms | none listed |
One change at a time
What we tried, and what we kept
One change at a time, kept only when it won on the development split with no query family worse.
| Experiment | Setting | Result | Kept |
|---|---|---|---|
| Verifying candidates | whole files, then 64 KB blocks searched in place | 3.5 ms to 1.3 ms on a query with no hits | blocks |
| Regex to trigram planner | fixed a join across a wildcard | 898 to 899 of 900 agent patterns equal; the last one ripgrep refuses too | fixed |
| Compaction | off the index lock, from a copy | longest wait after a 5,000 file burst: 19.1 s to 69 ms | yes |
| Writing vectors | one transaction per batch | from 4 to between 22 and 37 chunks a second per host | yes |
| Saying nothing matches | Inillucent confidence, then word coverage | confidence turned every ticket away on code; word coverage below 0.31 kept | coverage |
| Fusing word and vector rankings | weighted 0.3, 0.5, 0.7, RRF, adaptive | all within a query or two; weighted 0.5 kept | 0.5 |
| Ranking for plain English | code before documents in one search | F6 0.150 to 0.215 on one repository | yes |
| What an answer carries | small answers include the code around the hit | Haiku token ratio 0.79 to 0.55, one draw | yes |
What we would do next
- Use the index wherever an agent searches code on Windows, held in the agent’s own MCP process.
- Do not expect it to cut an agent’s tokens or raise its accuracy on its own; those depend on how many turns the agent takes.
- The weakest part is a single plain English search from a ticket’s words. That is the next piece of work.
- The passage table makes the index file larger than the code it indexes; exact search does not need it.
Atrius 0.1.0
A command line, atrius, and an MCP server, atrius-mcp, for Windows, macOS and Linux. Free under the MIT or Apache 2.0 licence.
- Windows
irm https://blackrainbowlabs.com/downloads/atrius-index/install.ps1 | iex- macOS and Linux
curl -fsSL https://blackrainbowlabs.com/downloads/atrius-index/install.sh | sh- Homebrew
brew install black-rainbow-labs/atrius/atrius-index- From source
cargo install atrius-cli- Connect an agent
claude mcp add atrius -- atrius-mcp
Every archive, the signed macOS installer, the .deb and .rpm packages and the signed checksums are in the downloads folder.