Multi-Modal Fusion Search: Picking the Right Retriever For Every Query
> A query like 'where is parseConfig defined' wants a different search than 'how does auth work'. Maguyva classifies the intent, weights four retrieval modalities accordingly, and fuses the results with weighted Reciprocal Rank Fusion.
A search query is not one thing.
“where is parseConfig defined” wants an exact symbol — one precise location, fast. “how does authentication work” wants meaning — the spread of related code that explains a concept. “what breaks if I change this function” wants the dependency graph. “find the string ECONNREFUSED” wants a literal match, nothing clever.
Grep is excellent for literal matches and useful for some reference hunts, but it is not a dependency graph and it does not understand meaning. Embeddings cover the semantic side, but they are the wrong tool for exact strings and impact analysis. Most code search tools pick one engine and make every query live with that choice. Maguyva doesn’t pick. It works out what kind of question you asked, then blends four retrievers in the proportion that question deserves.
Four modalities
Under the hood there are four independent ways to find code:
- semantic — vector search over Voyage binary embeddings; finds code by meaning.
- text — trigram matching; finds literals, error strings, exact identifiers.
- structural — AST queries; finds definitions, signatures, and language constructs.
- graph — the dependency graph; finds callers, callees, and blast radius.
Each is strong on a different class of question. The trick is deciding how much to trust each one for the query in front of you.
Intent classification
Before any retrieval runs, a lightweight classifier sorts the query into one of six intents, with a confidence score. It’s deliberately cheap — ordered heuristics, first match wins — because it runs on the hot path and adds only a millisecond or two:
- starts with
def,class,func,import… → find_definition (confidence 0.95) - “who calls”, “usages of”, “references to” → find_references (0.90)
- “impact”, “blast radius”, “what depends on” → impact_analysis (0.90)
- a quoted
"string"or an error token liketraceback→ exact_match (0.85–0.90) - a
CamelCaseorsnake_caseidentifier → find_definition (0.60–0.80) - “how”, “why”, “explain”, “architecture” → understand_code (0.75)
- nothing matches → understand_code, low confidence (0.40)
Each intent carries a weight profile across the four modalities. These are the real numbers:
| Intent | semantic | text | structural (AST) | graph |
|---|---|---|---|---|
| find_definition | 0.2 | 0.1 | 0.6 | 0.1 |
| find_references | 0.1 | 0.2 | 0.2 | 0.5 |
| understand_code | 0.5 | 0.2 | 0.2 | 0.1 |
| find_similar | 0.4 | 0.3 | 0.2 | 0.1 |
| impact_analysis | 0.1 | 0.1 | 0.1 | 0.7 |
| exact_match | 0.0 | 0.9 | 0.1 | 0.0 |
So “where is parseConfig defined” leans hard on AST (0.6). “how does auth work” leans on semantic vectors (0.5). “what depends on this” is almost all graph (0.7). “find ECONNREFUSED” is almost all trigram (0.9), with the embedding model switched off entirely — because semantic similarity is exactly the wrong tool for an exact string.
The fast path, and the fused path
When the classifier is confident — score ≥ 0.85 — and the query is a normal one, Maguyva skips fusion entirely and routes straight to the single dominant modality. “where is X defined” doesn’t need four retrievers; it needs the AST index, now. That direct path is reported back as fusion_strategy: "direct".
Everything ambiguous goes through fusion. The four (or three, on the default preset) modalities run in parallel, each returning its own ranked list, and we combine them.
Weighted Reciprocal Rank Fusion
Fusing heterogeneous retrievers is harder than it sounds: a cosine similarity of 0.82 and a trigram score of 137 and a graph centrality of 0.004 are not on the same scale, so you cannot just add them. Reciprocal Rank Fusion sidesteps the problem by throwing away the raw scores and keeping only the rank each engine assigned. A result’s contribution from one modality is:
contribution = weight × 1 / (k + rank + 1)
where rank is its position in that modality’s list and k is a smoothing constant. Contributions are summed across modalities for any result that more than one engine found — agreement between retrievers naturally floats to the top. We use k = 40 on the default preset and 60 on thorough (quick runs semantic-only, so fusion never kicks in there). The original RRF work landed on k = 60 for general-purpose retrieval; we default slightly sharper, which gives top-ranked agreement between modalities a bit more weight — and we don’t recommend hand-tuning it.
On top of that, results carry a graph-importance boost. A hub — a function the whole codebase leans on — should outrank an obscure leaf even at the same textual relevance, so we multiply each contribution by:
boost = min(1 + 0.3 × ln(1 + centrality), 1.5)
Centrality comes from the pipeline’s precomputed PageRank/degree metrics, and the boost is clamped at 1.5× so a popular function can’t completely bury a more relevant obscure one. Finally we demote results from vendor, build, and archive paths, and dedupe to the best chunk per file.
What’s still imperfect
The intent classifier is a stack of regexes, not a learned model. It covers the common shapes of a query well — the decision that introduced it recorded the zero-result rate dropping from roughly 15% to under 5% — but it is heuristic, and a genuinely ambiguous query falls through to understand_code and a semantic-leaning blend. That’s a safe default, not a clever one. We haven’t replaced it with a trained classifier because the cheap version is fast and good enough, and because a wrong-but-confident classifier is worse than an honest fallback. The weights themselves are hand-chosen priors, not learned from click data we don’t collect.
Ask the Question, Not the Tool
An agent shouldn’t have to know whether to reach for grep or embeddings or the call graph — it should ask its question in plain terms and get the right answer. Multi-modal fusion is what lets find_symbol, semantic search, and dependency analysis sit behind one query surface: the system reads the shape of the question and quietly assembles the right retriever for it. The model that scores the embeddings matters, but so does knowing when not to use them. Picking the right tool for each query is its own kind of quality, and it’s one we’d rather own than push onto the caller.
// you bring the question. it brings the tools.
Related reading
More from the Maguyva build log
Why We Upgraded Code Search to voyage-4-large_
We moved our code embeddings to voyage-4-large — currently top of the public RTEB code retrieval leaderboard. The honest version: the trade we make, what we actually index, and why we pay for premium embeddings.
Language Recursive Self-Improvement: Grinding Code Intelligence Across ~280 Languages_
We support code intelligence for ~280 languages. No human can hand-audit that. So we built a language recursive self-improvement loop — spot-check, LLM-as-judge, fix one thing, re-validate — and run it with a fleet of isolated agents until extraction is actually right, not just green.
Agent Observability: Hooks, Alloy, and Grafana_
We wired Claude Code and Codex into one Grafana stack with OpenTelemetry and Alloy, then used traces and logs to find and fix agent behavior issues at the source.