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.
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.
| The box | What already fills it |
|---|---|
| Vector store, similarity | locate: 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 search | sniff: a sweep over node bodies returning line-level snippets with their section, not a similarity score. |
| Knowledge graph, multi-hop | look, move, scan, pick: typed edges carrying a scent of the neighbour, every hop token-budgeted and every truncation explicit. |
| SQL / tabular tool | query: one read-only statement against a dataset node, opened read-only, LIMIT 200 injected, killed at 2 seconds. |
| Fusion | harvest: 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 generation | answer, on a Station with a model bound to that role: the reply arrives with the node ids it stands on. |
| Evaluator | Budgets 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 write | plant, graft, tend: every write a git commit, plus pheromone on the trail that worked and a minted shortcut when the trail was long. |
| Orchestrator | Deliberately 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.
# 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 toolsThere 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
{"tool": "harvest",
"args": {"query": "what is the deadline to exchange a retail order?"}}~50 ms, no model call
{
"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
{"tool": "locate",
"args": {"query": "amended delivery window supplier", "k": 3}}Response
{
"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
{"tool": "look", "args": {"id": "contracts/amendment-04"}}Response
{
"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
{"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
{
"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:
- Adopt the corpus your current retriever is worst at, which is almost always the one whose questions span documents.
- Serve it over MCP next to the servers you already run, and leave the existing pipeline answering exactly what it answers well today.
- 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.
- 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.