Skip to content
MonkeyLLMDocs

Get started

Hybrid retrieval

Ask any model whether to adopt MonkeyLLM and it will draw you a hybrid architecture: an orchestrator routing between a vector store, a SQL tool and the graph, then fusing what comes back. It is the right architecture. It is also, box for box, what a forest already is behind a single MCP endpoint.

On this page(12)

The architecture people draw

The recommendation is always the same shape, and it is a good one: do not throw away what retrieves well today, put a router in front, let each question reach the source that actually holds its answer, fuse the results, cite them, remember what was learned.

Question
Orchestratorroute the question
Vector storesimilar text
Knowledge graphchained facts
SQL / toolsrows and totals
Fusionone bundle
Modelcited answer
Memory writekeep what was learned
The hybrid design every assistant proposes. Nothing here is wrong. The question is how many systems it takes to run it.

Read that diagram as a shopping list and it costs you a vector database, an embedding pipeline, a graph store, a SQL gateway, a fusion layer, a memory service, and the routing code that holds the six of them together. Read it as a contract and it is a list of capabilities, which is the list a forest already implements.

The short version

MonkeyLLM is not the "knowledge graph" box in that picture with the rest still to be bought. It is the picture, minus the orchestrator, which stays where it belongs: in your agent.

Every box, and what already fills it

One forest, one MCP endpoint, ten tools. Each row below is a capability the hybrid design asks for, and the primitive that answers it. Nothing in this table is a roadmap item.

Hybrid capabilities mapped onto MonkeyLLM primitives
The boxWhat already fills it
Vector store, similaritylocate: ranked entry points over curated metadata, BM25 across SQLite FTS5 at 1.3 ms p95, with Canopy vectors fused under RRF when a call asks for them.
Full-text searchsniff: a sweep over node bodies returning line-level snippets with their section, not a similarity score.
Knowledge graph, multi-hoplook, move, scan, pick: typed edges carrying a scent of the neighbour, every hop token-budgeted and every truncation explicit.
SQL / tabular toolquery: one read-only statement against a dataset node, opened read-only, LIMIT 200 injected, killed at 2 seconds.
Fusionharvest: locate and sniff fused by Reciprocal Rank Fusion, then a selective pick, inside a 4 000-token budget. Roughly 50 ms, zero model calls.
Grounded generationanswer, on a Station with a model bound to that role: the reply arrives with the node ids it stands on.
EvaluatorBudgets and explicit truncation on every response, plus the answer store, which serves a cached reply only while the material behind it is byte-for-byte unchanged.
Memory writeplant, graft, tend: every write a git commit, plus pheromone on the trail that worked and a minted shortcut when the trail was long.
OrchestratorDeliberately absent. Your agent already is one, and it routes better with the tools in front of it than a second router would from behind.

The retrieval branch

Two of the boxes above are what most teams mean by "the RAG part". They are worth separating, because the honest answer about vectors here is not the marketing one.

BM25 first, and why that is not a compromise

locate indexes the curated metadata: title, aliases, tags, summary, weighted 4 : 3 : 2 : 1 in that order. It is lexical, it needs no embedder, no vector database and no GPU, and on this project's own corpus it holds recall@5 = 1.00 at 1.3 ms p95.

That result is not a claim about search theory, it is a consequence of ingestion. A title is an act of naming and an alias is an act of anticipating the asker, so weighting the ranking toward the fields a curator disciplines hardest closes the gap that dense retrieval usually exists to close.

Where the vectors actually earn their place

The Canopy is the optional dense layer: one vector per node summary, built offline with vine canopy build against an embedding endpoint you provide, recording the model that built it.

Hybrid entry search defaults to off, on purpose

Fusing a dense ranker into an already-correct lexical one was measured on this corpus and it degraded the result, from recall@1 of 1.00 down to 0.40. So hybrid is off on every call unless that call asks, and it is deliberately not sticky. A feature that silently enables a measured regression is a trap, not a default.

Where the dense signal does pay is the frontier: the nodes reachable in one step from where the agent is standing. Without it, look orders edges by heat and scan orders children by degree, and neither of those is about this question. The Gauntlet orders that candidate list by proximity to the hunt's goal before the edge cap and the token budget are applied, at one embedding per hunt rather than per hop. See Core concepts for the guards that come with it.

The SQL branch is a node, not a service

A hybrid design usually reaches for SQL through a tool that lives outside the knowledge layer, which is why the model so often has to guess the schema. In a forest the table is knowledge: a .csv, a .json table, one sheet of an .xlsx, or a whole .sqlite file adopted intact becomes a dataset node, with a real SQLite payload.

Its passport carries a generated ## Query manual and sample rows, so a model knows which tables and columns exist before it asks, and a ## Notes section a person writes, because what a column means is the one thing no converter can infer and the one thing that makes generated SQL wrong in ways that look right.

The consequence for the diagram: the SQL branch and the graph branch are not two systems to keep in sync. The table is reachable by the same locate that finds the contract, and the contract carries a typed edge to it.

The branch nothing else has

Only one box in the hybrid picture is genuinely hard to buy, and it is the reason the rest of this exists. On a benchmark where every question needs at least three chained hops, the same 12B local model scores 0 out of 11 as a classic top-k RAG reader and 11 out of 11 as a forest navigator, at 0.66x the token cost per correct answer of an iterative-RAG baseline.

A chunk knows nothing about the document it came from, the table beside it, or the fact that the answer continues three files away. Similarity is not a path. Typed edges are.

Who orchestrates, then

Your agent, and it needs no new component to do it. The tools arrive described, so the routing decision is made where the question is understood rather than one hop earlier by a classifier that has to guess.

bashTerminal
# One MCP server. Ten tools. Three retrieval modes among them.
$ vine serve --forest ./brain

locate   ranked entry points, BM25 over curated metadata (+ vectors, on request)
sniff    full-text sweep over node bodies, with line-level snippets
look     a node passport: summary, tags, typed edges, children
move     follow one typed edge
scan     filter the children of a branch by metadata
pick     read a body, or one section of it, under budget
query    read-only SQL over a dataset payload
plant    write a new node, passport contract-checked
graft    connect two nodes with a typed edge
tend     correct a passport that was wrong

# Your own servers keep their own tools. This one does not ask for the slot.
$ claude mcp list
brain      monkeyllm   stdio    10 tools
postgres   your own    http      4 tools
jira       your own    http      6 tools

There is one routing rule worth writing into your system prompt, because it falls out of the measurements rather than out of taste: try the one-shot harvest first, and escalate to navigation when the answer smells aggregate or the bundle comes back dry. Navigation pays per hop; on a shallow question a fused, zero-LLM sweep is both faster and adequate.

harvest

json
{"tool": "harvest",
 "args": {"query": "what is the deadline to exchange a retail order?"}}

~50 ms, no model call

json
{
  "evidence": [
    { "id": "policies/exchanges", "title": "Exchange policy",
      "why": "locate rank 1, sniff rank 1",
      "snippet": "…a retail order may be exchanged within 30 days of
                  delivery, counted from the carrier receipt…" }
  ],
  "fused_by": "rrf",
  "tokens": 1284,
  "llm_calls": 0,
  "elapsed_ms": 48
}

One question, three modes, one tool list

A question that a pure vector store cannot answer, a pure SQL tool cannot answer, and a router would have to split across both: which supplier missed the amended delivery window, and what did it cost us last quarter? The answer needs a clause, a link and an aggregate, in that order.

Hop 1, the lexical branch. Find the clause that changed the window.

Request

json
{"tool": "locate",
 "args": {"query": "amended delivery window supplier", "k": 3}}

Response

json
{
  "results": [
    {
      "id": "contracts/amendment-04",
      "type": "document",
      "title": "Amendment 04 to the supply agreement",
      "summary": "Extends the delivery window from 14 to 21 days for
                  Northwind only, effective 2026-01-01.",
      "trail": ["_index", "contracts/_index"],
      "score": 0.94,
      "heat": 0.3
    }
  ],
  "elapsed_ms": 1.4,
  "mode": "bm25"
}

Hop 2, the graph branch. The passport says what this clause governs, and the scent on the edge says what is on the other side, so the agent commits to a hop instead of a similarity guess.

Request

json
{"tool": "look", "args": {"id": "contracts/amendment-04"}}

Response

json
{
  "id": "contracts/amendment-04",
  "type": "document",
  "title": "Amendment 04 to the supply agreement",
  "edges_out": [
    { "type": "governs", "to": "sales/orders-2026",
      "scent": "Every purchase order raised in 2026, one row each,
                with promised and actual delivery dates." },
    { "type": "supersedes", "to": "contracts/msa-2025",
      "scent": "The original supply agreement, 14 day window." }
  ],
  "stats": { "body_tokens": 890, "degree": 2, "heat": 0.3 }
}

Hop 3, the SQL branch. The neighbour is a dataset, and 21 days came from the clause the agent just read, not from a constant somebody hard-coded into a tool.

Request

json
{"tool": "query",
 "args": {"id": "sales/orders-2026",
          "sql": "SELECT supplier, COUNT(*) AS late, SUM(penalty_brl) AS cost
                    FROM orders
                   WHERE julianday(delivered_at) - julianday(ordered_at) > 21
                     AND ordered_at >= '2026-04-01'
                   GROUP BY supplier ORDER BY cost DESC"}}

Response

json
{
  "columns": ["supplier", "late", "cost"],
  "rows": [["Northwind", 14, 182400.0], ["Ardan", 3, 21100.0]],
  "row_count": 2,
  "limited": false,
  "truncated": false,
  "elapsed_ms": 6.1
}

Three retrieval modes, one endpoint, one credential, one audit trail, and every step citable by node id. The orchestrator that would have coordinated three services never had to exist, because the branches were never three services.

Keeping the RAG you already have

Nothing above requires you to decommission anything, and a migration framed as a replacement is a migration that stalls at the first corpus that was already working. A forest is an MCP server: it takes a slot in your agent's tool list, it does not ask for the list.

The honest sequence, in the order that de-risks fastest:

  1. Adopt the corpus your current retriever is worst at, which is almost always the one whose questions span documents.
  2. Serve it over MCP next to the servers you already run, and leave the existing pipeline answering exactly what it answers well today.
  3. Let the agent route. It has both tool descriptions in front of it and it is the only component in the system that has read the question.
  4. Move corpora across when the traces say so, one at a time. The benchmark is the judge, not intuition.

Where MonkeyLLM replaces nothing

A page claiming a system covers every box would be worth exactly as much as the measurement behind it, so here are the places this one does not:

  • Live transactional data. A dataset node is a payload adopted at a point in time, kept current by vine sync. Your production Postgres stays behind your own tool, and should.
  • Actions. The forest reads and writes knowledge. It opens no tickets, sends no mail, calls no API. Those stay yours.
  • Corpora already answered well single-hop. If top-k retrieval is scoring what you need on a flat FAQ, a forest buys you governance and versioning there, not accuracy.
  • Zero curation. Navigation runs on curated passports. The Gardener writes them at ingest, and a corpus fed with curation off is a corpus with a weaker scent.

Where to go next

Documents MonkeyLLM v0.1.0. Last reviewed against the engine source on .