The forest
A forest is a directory of markdown files under git. That is the whole storage layer. There is no server in the definition, no database process, and no index you cannot delete.
The organising rule is that every folder is self-describing. Each one holds an _index.md that says what lives there and where to go if the answer is not there. An agent dropped into any folder knows where it is without loading anything else, which is exactly the property a flat vector store does not have.
Three layers live side by side, and they are not equally precious:
- The nodes. Markdown files with YAML frontmatter. This is the product. Git versions them.
- The payloads. The binary a node stands for: a SQLite database for a dataset, the original file in
_assets/. Referenced by name and sha256, never committed. - The derived layer (
_derived/). The catalog, the full-text index, the pheromone table, the traces, the optional vectors. Gitignored, disposable, rebuildable. Delete it and you lose speed and memory of past hunts, never knowledge.
Why the derived layer is allowed to be thrown away
A retrieval system whose vector index is the knowledge dies when the index dies. Here the index is a cache over files that already make sense on their own. Without it a forest is a slower wiki that still navigates. That asymmetry is the reason the whole design can treat embeddings as optional rather than foundational.
Two structural conventions keep a forest navigable as it grows. Depth is kept to about four levels, which keeps any trail within roughly five hops. And a branch that exceeds 150 entries or 3,000 tokens is flagged needs_split: an index too big to read cheaply has stopped being an index.
Nodes and passports
A node is one markdown file. Its id is its path relative to the forest root without the extension (policies/expense-policy), and the id is immutable: titles change in the frontmatter, filenames do not. Everything that points at a node points at that string.
Two words from the project vocabulary appear in the API, so learn them here:
- A branch is the
_index.mdof a folder: a map, not a leaf. - A banana is any leaf node: the atomic unit of knowledge, the thing a hunt is actually after. Entry search labels every result
kind: "banana"orkind: "branch", because landing in the right region and walking two steps beats landing on the wrong leaf.
A node's frontmatter is called its passport. The frontmatter is the machine interface; the body is the human and model interface. The word is literal for non-markdown files: a spreadsheet or an image does not enter the forest on its own, it enters with a markdown node that stands for it in the graph and explains how to consume it. No file enters without a passport.
---
id: policies/expense-policy # stable slug, = path without .md, immutable
type: note # a type declared in _meta/schema.md
title: Expense policy # mutable; the id never changes
summary: > # THE SCENT: at most 60 tokens
Reimbursement rules for travel, meals and equipment. Receipts are
required above 50 USD and approvals go through the team lead.
Does not cover contractor invoices (see finance/contractors).
tags: [finance, policy]
links:
- rel: related-to
target: finance/contractors
- rel: author
target: people/dana-reis
created: 2026-04-02
updated: 2026-08-11
confidence: 1.0 # below 1.0 means unconfirmed knowledge
source: manual # manual | ingest | agent
---
# Expense policy
## Reimbursable
...Six fields are required on every node: id, type, title, summary, created and updated. Everything else is optional: tags, links, confidence, source, aliases, entity_kind, and the payload triple payload / payload_type / payload_hash.
The branch beside it is the map of the same folder:
---
id: policies/_index
type: branch
coverage: "3 bananas, 0 sub-branches"
updated: 2026-08-11
---
# Policies
> Expense, travel and equipment rules. For contracts and invoices,
> see [[finance/_index]].
## Sub-branches
## Direct bananas
- [[policies/expense-policy]] Reimbursement rules for travel, meals and
equipment. Receipts are required above 50 USD...
- [[policies/travel]] Booking limits, per diem and approval thresholds...
- [[policies/equipment]] What the company buys, what it lends...
## Cross trails
- Contractor invoices are not expenses -> [[finance/contractors]]A branch has three fixed sections: sub-branches (descend), direct bananas (leaves), and cross trails (lateral shortcuts). That third section is what turns a tree into a graph, and it is where most of the hop savings come from. The entries replicate each child's summary verbatim, and the engine keeps them in sync: a summary edited on a node propagates to every index that repeats it, in the same transaction.
The master index at the forest root adds a Landmarks section: the ten to twenty most connected nodes, so a broad question can jump straight to a hub instead of descending the hierarchy.
The summary is the scent
Scent is the metadata that lets an agent decide the next hop without opening the content. In practice, scent is the summary field, and it is the single most important thing in the system.
It is capped at 60 tokens and validated when the node is written. The normative shape is three sentences: what this is (category and subject); the key content, with concrete numbers, names and time scope; and optionally what is not here and where the complement lives. Openings like "This document describes..." are forbidden, because they spend tokens without carrying scent.
Why so strict? Because the summary is what a branch listing replicates, what entry search indexes, and the only thing a model reads before deciding whether to pay for a body. A bad summary is not a cosmetic problem: it is a wrong scent, and a wrong scent is a lost agent. This is also why ingestion spends its optional LLM budget on writing summaries rather than on answering, and why the paper calls the principle spend intelligence on the environment so you can spend less on the model.
Node types
The legal types live in the forest's own _meta/schema.md, its dialect. A type not declared there is rejected at write time with E_SCHEMA, and an agent can learn the whole dialect in one hop by reading that node.
type | What it holds | Read with |
|---|---|---|
branch | The index of a folder | look |
note | Free text: the default leaf | pick |
document | A converted document, original kept in _assets/ | pick |
dataset | Tabular data, in a sibling SQLite payload | query |
entity | A person, organization, product or place (entity_kind is required) | pick |
concept | A definition or technical term | pick |
event | A dated fact: a meeting, a decision, a release | pick |
media | An image or audio file, with a written description | pick, plus view for the pixels |
The dataset type carries the design's sharpest opinion: tables do not become text, tables become queryable databases. A spreadsheet becomes a real SQLite file beside its passport, and the passport body carries a generated query manual (tables, columns, sample rows) so a model knows what exists before it writes SQL. An agent never loads 14,000 rows into context: it reads the manual for a few hundred tokens and asks a question. Chunking that spreadsheet into prose, which is what a retrieval pipeline must do, destroys exactly the structure that made it answerable.
The one thing a machine cannot infer
A dataset passport may also carry a ## Notes section, and it belongs to the operator: nothing generated ever rewrites it. It is where a person records that total_invoice is TEXT holding USD 54.607,56, so summing it returns 0.0 and looks like an answer. SQL that runs and answers wrongly is the worst failure this system can produce, because it is indistinguishable from success. Those notes therefore travel with the dataset on every path that hands material to a model, not only when the agent thinks to ask.
Typed edges
Edges are directed, typed, and declared in the source node's frontmatter under links. The derived layer materialises the inverse automatically, so you write one line and both directions are navigable. A rel outside the dialect is E_SCHEMA, and a node above 50 links is flagged as a candidate to become a branch.
rel | Inverse | Meaning |
|---|---|---|
part-of | contains | Logical hierarchy, which is not the folder hierarchy |
related-to | related-to | Generic association, symmetric |
mentioned-in | mentions | An entity cited in a document |
author | author-of | Authorship |
compared-with | compared-with | Technical contrast, symmetric |
derived-from | origin-of | Provenance: a note distilled from a document, and so on |
same-as | same-as | Soft merge of duplicate entities. It never deletes a node |
succeeds | precedes | Temporal order between events or versions |
discovered-shortcut | none | A lateral link an agent minted after a long trail |
One subtlety that trips people up: the folder tree is not an edge. Where a file sits is expressed through its trail and through its branch listing. part-of means logical membership, which often crosses folders. That is deliberate, and it has a visible consequence: the degree used to pick landmarks and to flag over-connected nodes counts typed edges only. It measures how woven a node is, not how filed.
The navigation loop
A hop is one navigation call. A trail is the sequence of node ids from the root down to the answer. A hunt is a session of hops that ends in an answer, and it has a shape:
Each call earns its place by answering a different question:
locateanswers where do I start. It reads curated metadata only.lookanswers what is this, cheaply: summary, tags, edges (capped at twelve each way, hottest first), the outline of a leaf or the children of a branch, and stats includingbody_tokens. That last field is the point: an agent can price apickbefore making it.moveanswers what is next to this, along typed edges.move(id, "children")is sugar for a branch's physical children.scananswers which of these children match, as a metadata filter served from the catalog without opening a single file.pickanswers what does it actually say, whole or one section.sniffanswers where does this exact string appear, as a literal grep over bodies. It exists because entry search reads summaries, and a ticket number or an SKU buried in a paragraph is invisible to a summary by construction. The two searches are complements, not competitors.harvestcollapses the whole loop into one call when you would rather reason over ranked evidence yourself: entry search plus sniff, fused, with the matched sections attached. It calls no model on either side.
Budgets and explicit truncation
Every read answers within a declared token budget, and a result that had to be cut says so with truncated: true. Nothing is ever silently shortened, because an agent that cannot tell a complete answer from a clipped one will confidently report the clipped one.
| Call | Budget (tokens) |
|---|---|
look | 500 |
move | 600 |
locate, scan, sniff | 800 each |
query | 2,000 |
pick, harvest | 4,000 |
A body over pick's budget does not come back trimmed: it comes back as its outline plus a hint to ask for a section. The budget is not a limitation the agent works around, it is the mechanism that keeps a small local model able to navigate a corpus of any size.
Entry search, and the optional vectors
By default locate is BM25 only, over SQLite FTS5. The indexed columns are the curated metadata (title, aliases, tags, summary) with title weighted heaviest and summary lightest. No embeddings are involved anywhere, which is why the quickstart needs no model and no GPU.
The Canopy is the optional vector layer: one vector per node summary, built offline with vine canopy build against an embedding endpoint you provide. It records the model that built it.
Having vectors does not mean using them for entry search
Measurement on this project's own corpus showed that fusing a dense ranker into an already-correct lexical one degrades it, from recall-at-1 of 1.00 down to 0.40. So hybrid entry search defaults to off on every single call and is deliberately not sticky: a request that does not ask for it is a BM25 request, whatever the previous one asked for. A feature that silently enables a measured regression is a trap, not a default.
What the vectors are actually for
The place a dense signal genuinely helps is the frontier: the set of nodes reachable in one step from where the agent is standing. Ordering there is otherwise blind to the question, because look sorts edges by heat and scan sorts children by degree. Heat is the memory of past hunts and degree is the shape of the graph, and neither is about this question.
The Gauntlet closes that gap. When it is active, the candidate list of look, move and scan is ordered by proximity to the hunt's goal before the edge cap and the token budget are applied, because reordering after the cut cannot recover what the cut hid. Ordering changes; shapes, budgets and every other field do not.
It costs one embedding per hunt, not per hop. The goal vector is the embedding of the most recent locate or harvest query, carried for the rest of the session; each subsequent hop is a dot product against vectors already stored. And it is never silent: a response whose order was conditioned reports frontier: { "ranked": true, "toward": ... }, because a reordering the reader cannot see is a reordering the reader cannot audit.
Two guards come with it. If the embedder's model differs from the one recorded in the index, the dense layer is treated as absent: comparing vectors from two spaces is not worse ranking, it is meaningless ranking, and it fails silently because a dot product always returns a number. And the read path embeds queries only, never nodes, so a freshly written node is missing from the dense half until a refresh runs. That debt is reported as a stale count rather than paid by whoever happens to call next.
Pheromone: a corpus that learns from use
Stigmergy is coordination through the environment rather than through messages: an ant does not tell the next ant where to go, it leaves a trace on the ground and the next ant reads the ground. MonkeyLLM applies that literally. Successful hunts leave pheromone, called heat, and later hunts read it.
The deposit happens at the end of a hunt. The orchestrator closes the session with the outcome, and the engine does two things: it adds heat along the winning trail, and it evaluates whether the trail was long enough to be worth a shortcut.
{
"ts": 1786800000.412,
"session": "8f2c1e4a9b07",
"outcome": {
"success": true,
"answer_nodes": ["policies/expense-policy"]
},
"metrics": {
"hops_to_banana": 3,
"trail_len": 4,
"tokens_to_banana": 1840,
"calls": 5,
"answer_nodes": ["policies/expense-policy"]
},
"suggest_shortcuts": ["policies/expense-policy"]
}Heat is a number between 0 and 1, stored per node in _derived/trails.db. A successful close adds 0.1 to every node on the trail, saturating at 1.0. It never enters git: heat is written on nearly every read, and a memory of traffic is not a fact about the world.
Ranking then blends it in. Entry search scores a candidate as strength x (1 + alpha x heat) with alpha defaulting to 0.3, and alpha = 0 turns pheromone off entirely. look orders a node's edges hottest first. The practical effect is that a corpus which is used becomes cheaper to navigate, and the paths people actually take surface without anyone curating them.
Why it has to forget
Evaporation is not housekeeping, it is what makes the mechanism work. Heat decays exponentially, heat' = heat x 0.5 ^ (elapsed / half_life), with a default half-life of 30 days. Rows that fall below 0.01 are deleted, so the table stays proportional to what is actually warm.
Without decay, every trail eventually saturates at 1.0 and heat stops discriminating between them: the system becomes addicted to the paths it happened to take first, and a genuinely better node planted last week can never compete with a mediocre one from last year. Forgetting is what keeps the ranking a live signal instead of a fossil.
Session-scoped heat, used by parallel hunts, expires separately after 24 hours by default. Crash leftovers must not become permanent memory.
Shortcuts, and reinforce before create
Heat is volatile and lives outside git. The permanent half of the mechanism is the shortcut (the project calls it a shout): a lateral wikilink an agent mints when it discovers a valuable node at the end of a long walk, so the next hunt gets there in one hop.
The policy is a cascade, and it is ordered to prevent link spam:
- If a shortcut already covers the connection, fortify it. Heat and confidence rise, nothing new is created, no commit happens. This is the common path, and it is why the graph converges on a stable mesh instead of accumulating duplicates. Grafting a link that already exists is fortification by definition, never an error.
- If none exists and the trail was long (four or more read calls before the answer was harvested), the engine returns the answer nodes in
suggest_shortcutsand the orchestrator may create adiscovered-shortcutedge atconfidence: 0.5. - Lateral connections the agent merely noticed enter as
related-toproposals atconfidence: 0.3, which the Ranger later confirms or removes.
Shortcuts are ordinary edges in frontmatter, so they are git commits: auditable, reviewable, revertible, and visible to a human opening the folder in Obsidian.
The confidence lifecycle
confidence exists in two places and they mean different things. On a node it rates the content: anything below 1.0 is unconfirmed knowledge, and documents planted by ingestion start at 0.7 because nothing has reviewed them. On a link it rates the claim that the edge is real, and that is the population the maintenance job manages.
The lifecycle, end to end:
- The Gardener proposes at 0.3. Curation may suggest a
related-toedge, but only toward nodes the catalog offered it as candidates, never from the model's memory. Anything outside that closed list is dropped, so a fabricated link is structurally impossible rather than discouraged by a prompt. - Agents mint shortcuts at 0.5 when a hunt was long, per the cascade above.
- Usage heats both endpoints. A link nobody walks stays cold.
- The Ranger promotes to 0.8 when both endpoints still hold heat after evaporation: confirmed by use.
- The Ranger prunes a low-confidence link whose endpoints have fully evaporated. A proposal nobody ever walked cost one line of frontmatter and then dies.
What the Ranger will never touch
Maintenance manages only links that carry a link-level confidence below 1.0, which is to say edges that were born as guesses. Structural edges, links with no confidence field, and links at 1.0 are never modified. A machine that quietly rewrites the graph a human curated is not maintenance, it is drift.
The Ranger
The Ranger is the maintenance pass: vine ranger for one cycle, vine ranger --every N to run as a service. One cycle is evaporate, then tend links, then report health. It is trusted infrastructure running with the operator's authority, and it holds two lines it does not cross: evaporation lives entirely in the derived layer and never commits, and every node edit goes through the same audited markdown-only commit path everything else uses.
Beyond evaporation and link tending, it also:
- refreshes the master index's Landmarks mechanically, from node degree over the typed-edge table, rebuilding the section in full and writing nothing when the graph has not changed;
- reports health: branches that
needs_split, over-connected nodes, lint errors, passports whose source file has disappeared, an inventory of uncertain links, and the size and shape of the heat table; - evicts the least recently used cached remote payloads over a size cap.
Everything it does is configured in the forest, beside the forest, in plain YAML:
# brain/_meta/ranger.yaml
half_life_days: 30
session_ttl_hours: 24
promote_floor: 0.2
promoted_confidence: 0.8
prune_below: 0.5
payload_cache_gb: 5The Ranger never deletes a node. A passport whose source file vanished is reported and left standing; deciding that knowledge should stop existing is a human's call.
The Troop
A Troop is several navigator instances hunting the same question in parallel. They never exchange messages. They coordinate the way ants do, by reading traces the others left, this time in a heat namespace scoped to the shared session.
- Frontier partition. One entry search with
kequal to the number of foragers gives each one a distinct starting point. Without that, everyone walks the same trail and the parallelism buys nothing. - Session pheromone. Harvesting a node deposits heat on its trail inside the session scope, and every read in the session ranks with that blended in, so foragers drift toward regions where somebody found signal.
- A shared visited cache. An identical call another forager already made is served from cache at zero cost and zero duplicated trace events.
- A stop discipline. Configurable: the first confident answer, a quorum, or keep hunting while harvests keep contributing new nodes and stop after two that add nothing.
- A judge. One model call synthesises the final answer from the harvests, merging them when they are complementary. Only the winning trail is promoted to persistent heat; the rest evaporates with the session, so a swarm does not pollute long-term memory.
Where the Troop actually lives
The Troop is an orchestrator-side component, on the MCP client side, not part of the engine: it changes no primitive contract, it is not an MCP tool, and there is no vine troop verb. The repository carries a reference implementation under troop/, used by the benchmark. Read the status honestly: it measures as an accuracy amplifier today, and the wall-clock speedup criterion has not been met.
Git is the storage layer
Every write is a commit inside the forest's own repository, with a standardised message: plant(<id>): <title> for a new node, graft(<id>): <summary of the patch> for an edit, gardener(sync): <id> when ingestion refreshes a passport, ranger(promote|prune|landmarks) for maintenance.
Three consequences, and all three are the reason it was built this way:
- Compounding knowledge is auditable. An agent's write and a human's correction land in the same history, review the same way, and revert the same way.
git logandgit blamework on your knowledge base because your knowledge base is text. - Binaries stay out, and that is enforced. The commit layer stages markdown and refuses everything else, even when asked. A payload is referenced by
payloadpluspayload_hash, and drift is detected by comparing hashes rather than diffing blobs. Git delta-compresses text, not binaries: a frequently rewritten spreadsheet inside the repository would make it unusable within a month. - The forest owns its
.git. The engine treats a directory as a repository only when it is the repository's top level, so a forest that happens to sit inside your project never quietly commits into your project's history.
Because history is the storage layer, a backup is one file. A snapshot packages the whole repository as a git bundle, full history included, and restoring it clones the forest back with every audit trail intact. Payloads are not inside it, since they were never in git, and travel as an optional sidecar.
Vocabulary in one table
| Term | In plain terms |
|---|---|
| Forest | A directory of markdown nodes under git. The corpus. |
| Branch | The _index.md of a folder: a map with three sections. |
| Banana | A leaf node. The unit of knowledge a hunt is after. Appears in responses as kind. |
| Passport | A node's frontmatter, and specifically the markdown node that stands in for a non-markdown file. |
| Payload | The binary behind a passport: a SQLite database, an image, the original document. |
| Scent | The summary: what lets an agent choose the next hop without opening anything. |
| Hop / Trail | One navigation call / the chain of ids leading to an answer. |
| Pheromone (heat, whisper) | A decaying 0 to 1 weight per node, deposited by successful hunts, blended into ranking. |
| Shortcut (shout) | A permanent lateral edge an agent mints after a long trail, born at confidence 0.5. |
| Vine | The engine itself: the primitives, and the MCP server over them. |
| Gardener | The ingest pipeline: converts files, writes passports, keeps a source in sync. |
| Ranger | The maintenance pass: evaporates heat, promotes and prunes uncertain links, reports health. |
| Canopy | The optional vector layer over node summaries. Off by default for entry search. |
| Gauntlet | Query-conditioned ordering of the frontier, the one consumer of those vectors that is on by default when ready. |
| Catalog | The derived SQLite index of all frontmatter and edges, plus the full-text index. Serves scan without opening files. |
| Troop | Several foragers on one question, coordinated only by session-scoped heat. |
Next steps
Every mechanism on this page has an exact contract behind it: parameters, defaults, budgets and response shapes. That is what the reference pages are for.
The normative source is the highest numbered docs/monkeyllm-spec-v*.md in the repository, and the reasoning behind the design, with the benchmark numbers, is in the paper.