UNIT 01 · Hybrid code search
trusty-search
Three retrieval lanes over one corpus, fused into a single ranking, served by one daemon for every project on the machine.
- Package
- trusty-search
- Default port
- 7878
- Languages parsed
- 14 tree-sitter grammars
- MCP tools
- 21
Three lanes, one ranking
A grep knows the token you typed. An embedding knows what you meant. A symbol graph knows what calls what. trusty-search runs all three over the same corpus and merges them with Reciprocal Rank Fusion, so a query lands whether you spelled the identifier right or only described it.
- Lexical. A code-aware BM25 that splits
CodeIndexerintocodeandindexer, so a half-remembered identifier still matches. - Vector. An HNSW index over usearch, holding 384-dimension embeddings
produced locally — no text leaves the machine to be indexed. Vectors are
stored as f16 by default, half the bytes of f32 at the same measured recall;
trusty-search quantizere-encodes an existing index in place for anyone who indexed before the switch. - Graph. A petgraph symbol graph built from tree-sitter parses, walked one or two hops to pull in the callers and callees around a hit.
Fusion uses a fixed damping constant of 60 — there is no relevance dial to tune wrong.
The query picks the weighting
Asking for a definition and asking a conceptual question want opposite rankings. A sub-millisecond regex classifier sorts every query into one of five intents and sets the vector/lexical weights accordingly, before any search runs.
| Intent | Vector | Lexical | Graph-first |
|---|---|---|---|
| Definition | 0.3 | 0.7 | — |
| Usage | 0.5 | 0.5 | yes |
| Conceptual | 0.8 | 0.2 | — |
| Bug / debt | 0.1 | 0.9 | — |
| Unknown | 0.6 | 0.4 | — |
Graph expansion is gated to Usage, where caller and callee chains are what you actually asked for. Everywhere else it would just add noise.
A query naming a literal — a quoted string, a filename, an identifier with a
clear word boundary, or an issue reference like #1234 — skips the weighting
table above: every chunk carrying that literal floors above every chunk that
does not, so a search for session_mcp_scope.rs cannot lose to a chunk that
only resembles it semantically. An unquoted phrase earns no floor.
One daemon for the whole machine
Install once, run one process, register as many named indexes as you have projects. Nothing is per-project except the index itself, and re-running an index is cheap: content fingerprints skip files that have not changed, so only the diff pays for embedding.
- Working on a branch? Pass it, and chunks from the files it touched get a 1.5× score multiplier — every result reports whether it was boosted.
- Don't need semantics?
--lexical-onlyskips embedding entirely and leaves you a daemonised BM25. - Don't need call chains?
--no-kgskips the symbol-graph rebuild on every reindex. - Memory limits — chunk caps, batch sizes, cache sizes — are computed from detected system RAM at startup rather than guessed at compile time. Below 16 GB the daemon warns once and runs on a reduced tier rather than refusing to start.
Nothing is indexed until you say so
A fresh daemon accepts zero indexes. A path has to be added to the allowlist before it can be registered, whether the request arrives over HTTP, from the CLI, or from an MCP tool call.
On top of that sits a denylist that the allowlist cannot override: credential
directories such as .ssh, .aws, .gnupg and .kube, paths carrying secret
markers, and the top level of your home directory. Those are refused with the
matched pattern named in the error, not silently skipped.
21 tools over MCP
The MCP server speaks stdio and HTTP/SSE and exposes each retrieval lane separately, so an agent can pick the one that fits the question instead of always paying for the fused search.
Every result-returning tool folds its response to a 48 KiB ceiling by
default — a hit is never cut mid-record, and the response reports what it
withheld and how to fetch the rest. A compact flag on the five search tools
trims each hit to the fields a caller who is about to open the file actually
needs, about 63% fewer bytes.
search · search_lexical · search_semantic · search_kg · search_all ·
search_similar · get_call_chain · grep · typeahead · index_file · remove_file ·
list_indexes · create_index · delete_index · reindex · index_status ·
list_chunks · search_health · chat · console_metrics · upgrade
Install
tctl resolves whatever else this crate needs at runtime and
keeps macOS signing grants stable across upgrades. The other install paths — Homebrew, or cargo install from source — are on the home page.
curl -sSf https://raw.githubusercontent.com/bobmatnyc/trusty-tools/main/install.sh | sh tctl install trusty-search
Build and test this crate from a checkout with cargo test -p trusty-search.
Go deeper
Reference documentation for trusty-search, plus every other crate in the workspace.