We shipped the search tool. The agent wouldn't call it.
The part of this story we did not expect is the middle. Building local semantic search over a docs tree is a solved problem, and the first working version of Polaris was not the hard part. The hard part was everything after it worked: getting it installed without a chore, keeping it fresh without a daemon, and then discovering that an agent handed a perfectly good search tool will look straight past it and answer from memory instead.
It started as an accounting problem
Watch a coding agent answer a question about your own project. It greps for a word, gets a page of hits, opens two or three files in full, and finds the one paragraph it needed. Ten to fifteen thousand tokens, gone, on a lookup worth a few hundred. Do that six times in a session and the context window is mostly documentation the agent skimmed once and can no longer see.
That is not a model problem. The model is doing the only thing available to it: grep matches strings, and questions are about meaning, so the fallback is always to read more. What was missing was a tool that could answer a question with the passage rather than the file. So we wrote one. Rust binary, local ONNX embedding model, SQLite for the vectors, vector similarity and BM25 fused into one ranking, exposed over MCP so the agent could call it. That version worked within a couple of weeks, and it taught us almost nothing.
Wrong turn one: we shipped a chore
To use that first version you hand-wrote an .mcp.json,
remembered which database and cache files to gitignore, and told your agent's instruction file that
a search tool now existed. Three manual steps between installing a thing and it doing anything.
Every one of them is small. Together they are the reason a tool gets tried once and abandoned.
polaris setup collapsed all three into one command that writes the MCP config, adds the gitignore entries, and
updates CLAUDE.md, AGENTS.md and GEMINI.md for you. Obvious in hindsight. It took us until May to
admit that the setup instructions in the README were a bug report about the product.
Wrong turn two: we did not believe our own number
By then the README claimed Polaris was dramatically cheaper than grep and read, on the strength of
one query run by hand. That is not evidence, that is a good day. So instead of running a benchmark
we would obviously design in our own favour, we built polaris savings:
every search, CLI or MCP, logs a row, and the baseline for that row is the actual byte size of the
files that search would have sent an agent off to open.
The design doc's non-goals put it more bluntly than any marketing page would: the metric is what actually happened, not what could happen. That framing had teeth, because it meant we shipped an instrument capable of embarrassing us. If real usage came in at 2x rather than 20x, the tool would have printed 2x, on our own machines, every day.
It came in around 20x, which is the number the rest of this site quotes. We would rather you ran the command on your own repo than took ours on faith.
Wrong turn three: freshness needed a babysitter
An index that lags behind the docs is worse than no index, because it answers confidently with last
week's text. Our first answer was polaris watch,
a file watcher you left running. It works. It is also a long-running process a user has to remember
to start, in a second terminal, forever, which in practice means an index that is fresh right up
until the day someone reboots.
The replacement was event-driven: a Claude Code PostToolUse hook that re-indexes a file the moment the agent finishes editing it. No daemon, no second terminal,
nothing to remember. The design doc set the bar explicitly: once installed, Polaris should be
invisible. No extra processes, no manual re-index, no warnings surfaced for transient hiccups. That
sentence ended up governing more decisions than any performance target we ever wrote down.
The one that actually humbled us
Setup was one command. The index was always fresh. Search was fast and measurably cheap. And the agent still would not use it.
Not always, but often enough to matter. MCP makes a tool available; it does not make a model reach for one. Asked a question about the project, the model would frequently answer from training data, plausibly and fluently and without ever calling the search tool sitting right there in its toolbelt. Our own Phase 2 design doc says it flatly: the LLM must decide to call it, and in practice it often does not.
That was the moment the project stopped being about retrieval quality. You can build the best local
index in the world and still lose to a model that is confident it already knows the answer. So the
fix moved upstream of the decision entirely: a UserPromptSubmit hook that searches the index on every message you send and injects the top result as context before
the model responds. Nothing to call, nothing to decide, no tool-choice coin flip.
If there is one thing we would tell anyone building agent tooling, it is this. A tool the agent may call is not a tool the agent does call, and the gap between those two is where most of your value quietly leaks out. Both hooks, and how to install them, are in the hooks post.
What we said no to, and why
The rejections shaped the tool more than the features did.
- No cloud vector store, no hosted service. A retrieval layer over your private repo is the last place you want a network dependency. Everything runs on your machine, and it stays a Rust binary with an embedded model rather than an API key
- No multi-user mode by default. A team server with namespace isolation is a real request and a planned v3, but building it early would have taxed every single-developer install to serve a case nobody had yet
- No giant reranker. When we added cross-encoder reranking, the three most accurate models available were roughly a gigabyte each, which is a non-starter on CPU for a tool meant to feel instant. We took the 150 MB model with the best quality-to-speed ratio and moved on
- No configurability we could not justify. Making the reranker model swappable was deferred, not because it is hard, but because adding a knob nobody has asked to turn is how a small tool becomes a big one
Where it landed
Polaris is what it is because of that middle stretch: the setup tax, the number we made falsifiable, the daemon we deleted, and the afternoon we realised the agent was ignoring us. The CLI and the MCP server are free and MIT licensed. Pro extends the same pipeline to source code, PDF and .docx, for when the answer lives in the code rather than the docs.
If you landed here first and want the plain description rather than the story, start with what Polaris is. If you want it running on your own repo, the install page is four commands. And if you want to see what all of this actually produced, the retrieval pipeline is documented stage by stage.