Tuning a search pool: content beat weights
On a Trawl pool of 18 public repos, every search-time setting I tried scored exactly the same, because the reranker sets the final order. What moved the scores was fixing what was in the index, measured with 15 questions whose answers I knew.
If every weight setting gives the same score, stop tuning weights and fix what’s in the index. Three rounds of tuning a Trawl pool showed exactly that: none of the search-time settings I tried changed the score at all, and changes to the content took MRR from 0.489 to 0.542 and hit@10 from 0.733 to 0.867.
Trawl is Mullet’s search. It grew out of Trove, and a pool is a collection of documents and code it indexes and searches by keyword and by meaning together.
The pool and the test
The pool holds 18 public repos of tools for running several Claude Code accounts, plus Mullet’s own code. To score it I wrote 15 questions with known answers. Each answer is a piece of text that only appears where a tool actually implements the thing asked about, so a hit means the search found the code, not a page that mentions it.
Each round gets four figures. MRR is the average of 1 divided by the rank of the first right answer, so 1.0 means every answer came first. hit@1, hit@5 and hit@10 are the share of questions with a right answer in the top 1, 5 or 10.
| Round | Change | MRR | hit@1 | hit@5 | hit@10 |
|---|---|---|---|---|---|
| 1 | Wizard config plus exclusions | 0.489 | 0.333 | 0.733 | 0.733 |
| 2 | Missing languages indexed; UI, CSS, tests, site pages and design notes out | 0.533 | 0.400 | 0.667 | 0.800 |
| 3 | Synonyms; two test questions corrected | 0.542 | 0.400 | 0.667 | 0.867 |
Round 1: the weights changed nothing
The first round used the config Mullet’s setup wizard writes, plus exclusions. Then I tried these search-time variants, and every one scored exactly the same as round 1:
- reciprocal rank fusion (RRF) instead of weighted merging
- BM25-heavy, keyword weighted above vector
- vector-heavy, vector weighted above keyword
- code priority off
- MMR off
The reranker sets the final order. The weights only shape the list it reads, so on this pool they made no difference to the score.
Round 2: fix what’s in the index
About half the tools had none of their code in the index. Trawl’s code indexer only parses TypeScript, Python, JavaScript, CSS, Rust, YAML, JSON and HTML, and ignores the code.extensions setting, so only the Go, shell and Swift tools’ docs were in the index until those extensions were listed under documents instead. Three other changes went in the same round:
- Two repos made up 45% of all the chunks, 17,844 of 39,666, mostly UI code, styles and tests. Their UI code, CSS, tests and site pages came out.
- The pool’s own design notes came out too. They matched the questions’ wording too closely and pushed real code down the results.
- One tool’s main script has no file extension, so the pool links to it under a
.shname.
MRR rose to 0.533 and hit@10 to 0.800. hit@5 fell, from 0.733 to 0.667: one answer fewer in the top 5, with more of the tools’ code now competing for those places. The index was 36,924 chunks after this round.
Round 3: synonyms, and the test itself
Search synonyms for handoff, workstate, fallback and login. And two test questions were corrected, because the code they pointed at had changed since they were written.
That gave MRR 0.542 and hit@10 0.867. Two questions still miss the top 10. I accepted that: for one, the right files are indexed but outranked, and for the other, another tool’s docs answer the question instead.
What I’d do again
- Write the questions first, with answers you can check as plain text, and re-score after every change.
- When every weight setting scores the same, stop there. Look at what’s in the index: what’s missing, what’s noise, and what echoes your own questions.
- Check that every language in the pool is actually being indexed.
More on Mullet
-
Mullet’s code is private now
I’ve made Mullet’s code private while I work on a new idea. Everything I’ve written about it stays here, and if you’d like to know more or ask about…
-
Checking third-party repos for prompt injection before an AI reads them
Before 18 public repos went into a search pool that Claude sessions read, I scanned every text file for hidden text and instruction-shaped strings…
-
Why a forked worker can wait for ever
A Trawl reindex started through Mullet’s MCP server hung at 0% CPU, because its forked workers inherited a lock the main thread was holding. Pools…
-
Where Mullet goes next
Mullet 2.5.0 is out. What I plan next: a guard that checks a step fits a model’s window, a price ceiling instead of a free switch, more settings and…
-
Eight rules from a game framework’s agent prompts, for web and print design
On 1 November I read the agent prompts of VibeGame, a framework that builds 2D web games, and kept the eight techniques that carry over to web and…
-
What I tried and binned, and what each one cost
Three local-model options I stopped using, with what each cost and the verdict: a 25 GB model that timed out in a job, a Blackwell-only fork ruled…
-
When your plan has no five-hour limit
Mullet 2.5.0 copes with a Claude plan whose usage data has no five-hour window: the HUD drops the five-hour figures and the budget guard paces on…
-
Very cheap is basically free: pricing a coding model per step
A free model id in my config silently had no hosts, and the real price of a cheap coding model depends on which hosts you will accept. The per-step…
Featured projects
-
PrintMD
A Markdown editor that exports print-quality PDF, Word and PowerPoint files, with a small PDF service that runs on your own computer.
-
Solar Calendar
A calendar of 13 identical 28-day months, with each year starting on the first Monday on or after the spring equinox.
-
Where Should I Live
Rank cities around the world by what you’d keep after tax, what a month costs, which visas you can get and how each place fits the way you live.
-
Apple News API
A PHP client for publishing articles to Apple News, with a document builder for the whole Apple News Format.
More writing
-
A PDF service that only runs on your machine
PrintMD’s PDF service turns Markdown into a PDF with one request, and it refuses to be hosted. Why that’s on purpose, and how it stays locked down…
-
Custom sizes, one long page and fit to width
Not every PDF is an A4 page. PrintMD’s page options cover a custom size, one continuous page, scaling wide tables to fit and your own margin, and…
-
Print-quality PDFs from a browser
A browser can print any page to PDF, but a document you send someone needs more. How PrintMD gets fonts, page breaks and images right, and what its…
-
Why I built PrintMD
I write in Markdown, and getting from there to a PDF I’d happily send someone was more work than it should be. So I built an editor that shows the…