RouteMind
# RouteMind
**An ontology you can see the agent reading.** Split a domain into areas, let each area advertise
itself in one line, and an agent picks from that list before reading anything else. The map draws
that structure as a wiring diagram, and one click shows you **the exact text an agent is handed**.
The domain is not in this code. The vocabulary and the areas are data, and the data is your own git
repository.

On 700 questions over one frozen corpus, asked **in a person's words** rather than in the codes the
documents use, retrieval scores **0.028** and this scores **1.000** — and it costs 6.7 tool calls and
20–100× more per question, which is why 320 of those 700 are questions nothing here argues for
walking. [The study](#measured), the corpus and every run are in this repository.
```sh
./install.sh --name acme --port 9000 # then `claude` in the same directory
```
---
## How it works

The shape is borrowed from dynamic routing on a network, and the borrowed part is the useful one: an
area advertises **where it is relevant**, not everything it holds. So the cost of finding something
does not grow with how much there is.
The thing easiest to get wrong is that **`use_when` is the only text read before a choice is made** —
an area with a title and no reason is one nobody picks.
### Back-Bone, AS, and the line between them
```
BACK-BONE ─ the whole of this ontology. Hop 0: one row per AS, nothing else,
│ and the only place absence may be claimed
│
├── AS expense "what to do with a receipt · whether the corporate card
│ │ may be used here · how much a business trip pays"
│ ├── AS corp-card ← an AS holds AS's: the same thing one level
│ │ └── card-limit down, advertising itself the same way
│ └── AS evidence
├── AS approval "whom to put in the approval chain · whether a team
│ lead can sign this off"
└── AS payroll "what this payslip line means · whether an allowance
is tax free"
```
**An AS advertises; it does not expose.** What is inside is invisible from hop 0 until something
picks it — so hop 0 is the same size at 80 documents and at 8,000.
**Absence belongs to the Back-Bone alone.** An AS's table says what that AS holds, never what
RouteMind lacks. Every table says which of the two it is, in its own footer.
### Overlays
Some questions do not sit in one area: settling a trip is three at once. An **overlay** is that
working set made as an object — the areas, why each is in it, and what was used to answer.
**[docs/OVERLAY.md](docs/OVERLAY.md)**.
## Measured
700 questions over one frozen corpus of 1,126 documents. Four arms, all of them a census — no
sampling, one fresh agent per question.
| | plain RAG | + reranker | RouteMind |
|---|---|---|---|
| a question in codes the rows use | 0.991 | 1.000 | **1.000** |
| **a question in a person's words** | **0.028** | **0.069** | **1.000** |
| **a rule two revisions back** | **0.133** | **0.200** | **1.000** |
| overall | 0.516 | 0.541 | **0.999** |
The two bold rows are the point: retrieval does not *degrade* there, it fails outright, because every
newer version of a subject outranks the one being asked for and they all look alike.
**What it costs.** A walk is 6.7 tool calls, 23–111 seconds and $0.12–$0.28 a question against one
sub-second embedding call — and **320 of those 700 are questions retrieval already answers first
time.** Nothing here argues for walking those.
**The one miss in 700 was a wrong sentence in the map**, not a wrong document, and both routing arms
obeyed it identically. An agent that trusts the map inherits the map's errors silently. Still
unmeasured: whether a *correct* map has a size at which it stops working.
**Check it rather than take it.** The corpus is 865 documents in `bench/corpus/`, the gold sets are
in `eval/gold/`, the generator that made the corpus is `bench/spec.yaml` + `bench/generate.py`, and
every run — including the ones that failed — is in `eval/runs/`. Re-running needs an API key and
`./bench/run.py eval/gold/<set>.yaml`; **[bench/README.md](bench/README.md)** has the order.
**[eval/report/report-en.html](eval/report/report-en.html)** — the whole thing with figures ·
**[한국어](eval/report/report-ko.html)** · **[eval/PREREGISTRATION.md](eval/PREREGISTRATION.md)** —
written and frozen before any of it ran.
## Quickstart
Docker, with `docker compose`. That is all it needs — the containers carry python and git.
```sh
git clone https://github.com/CSP911/routemind.git routemind && cd routemind
./install.sh --name acme --port 9000
```
→ **http://localhost:9000**
`--name` is what this domain is called: one word, lowercase, and it appears in every address a linked
backbone prints (`/v1/peers/acme/…`). `--port` is where the map answers, 8080 by default. Run
`./install.sh` bare and it asks for both, then asks whether you have an LLM — Enter skips it, and
`--no-llm` does not ask. An LLM changes one thing: a **✨ Suggest** button that drafts a routing line
for you to edit.
Safe to run again; an existing `.env` is kept and only what you pass is replaced.
**Three containers, no database.** `ontology` is the API and the only thing that touches your git
repository; `web` is the map and a proxy; `exchange` is where backbones meet, idle until you link
one. On first boot an empty ontology is laid into `data/repo` and that becomes a git repository —
every write **commits**, so undo is `git revert`.
Start from [`examples/back-office`](examples/) rather than an empty map: five areas, 79 entities,
five levels deep. **[docs/INSTALL.md](docs/INSTALL.md)** — that, the manual route, what to do when it
does not come up, and the first two things to write.
## Connecting an agent
Optional — the map works on its own. An agent reads this ontology the same way every time: **fetch
the list of areas, pick one, fetch that area, read what it points at.** Two operations, never a third.
```sh
python3 mcp/knowledge_mcp.py --api http://localhost:8080/api/knowledge
```
One file, stdlib only: no install, nothing to build. For **Claude Code** there is nothing to do at
all — `install.sh` writes `.mcp.json` at the root, pointed at the port you chose, so `cd routemind &&
claude` is the whole setup and `/mcp` shows the tools.
`knowledge_table(path?)` and `knowledge_read(path)` do the reading. The rest appear only where the
install has what they need: `knowledge_overlay` where overlays are kept, `knowledge_write` where
there is a `workspace` area, `knowledge_circuit` always. `/circuit <url> <token>` is the same thing
from a person's side.
The area list travels in the server's `instructions`, so **you do not have to name RouteMind in the
question** — what decides whether the agent comes here is the `use_when` line on each area. Without
MCP, **Copy for an agent** on the map puts the same text on the clipboard; all of them hand over one
formatter's output, because three descriptions of one ontology would drift.
**[docs/AGENTS.md](docs/AGENTS.md)** — every client, the tools, and the prompts.
## How old is this row
Material goes stale and gets replaced; the old record still has to exist. Both versions are in the
map, both look valid, and an agent reads both as current — so it sometimes answers from the one that
was replaced.
Every routing row carries two times:
```
KIND ADDRESS AGE WHY YOU WOULD PICK THIS ROW
table /v1/nodes/card-limit 2y / today Card limits — what the card may be used for …
file /v1/nodes/qualified-list/body 2y / 2y What qualifies as evidence, and the ceiling …
how long this path ─┘ └─ when what it points at last moved
has been here
```
**One number cannot say both.** A two-year-old route over a document rewritten today is current —
somebody is maintaining it. The same route over a document that has not moved is the one to ask about
before quoting it. Both come from git, so there is nothing to keep in sync.
It is **not** a supersession record: old is not wrong, and an agent that prefers the newest row picks
a draft over a rule that has held for a decade. The column supports asking, not deciding.
**[docs/AGE.md](docs/AGE.md)**.
## More than one backbone
An install is **one backbone and an exchange**. A **domain** is one exchange and the backbones on it —
head office and a subsidiary are one domain; a company and its supplier are two.
An area crosses by somebody setting `export: yes` on it, and by nothing else. The line a peer reads
is that area's own `use_when` — one sentence, the same one this backbone routes on. `export_to`
narrows who sees it; a kind marked `export: no` in `vocab.yaml` never leaves whatever an area says.
```
PEERING — standing, committed, everyone sees it
your BB ── peers.yaml ──▶ EXCHANGE ◀── members.yaml ── their BB
Their areas appear in YOUR hop 0. An agent never learns there is a link.
CIRCUIT — this session only, nothing written on either side
/circuit http://their-host:8100 <token>
Their areas appear under /v1/circuits/<name>/… , beside yours, never in it.
```
Both halves of a peering are declarations, so nobody is enrolled by one side alone. A circuit is the
opposite by design: one person, one session, one address and a token somebody handed them.
Three things worth knowing before relying on either. **Nothing is copied** — a document is relayed,
held for one request and discarded, so the only record of a read is the one its owner writes.
**No transit** — a room offers a neighbour its own backbones, never a third room's. **Absence
suspends itself** — hop 0 may claim something is missing only while every link is up, and says so
when one is not.
The credential is an **enrolment key** that buys a six-hour session; the key opens nothing else, and
a session cannot mint another. It shortens how long a leak is worth something. It does not prove who
is at the far end.
**[docs/PEERING.md](docs/PEERING.md)** — the contract, the circuit, the sessions, and the operator's
screen at `:8090`.
## Carrying one where a link cannot reach
A partner behind a firewall, an air-gapped site, an auditor who gets a copy and nothing else.
```sh
./transfer/export.py --api http://localhost:8100 --token "$TOK" --out partner.rmx
./transfer/import.py partner.rmx --graft data/repo --prefix partner
```
**Export** in the map's header downloads the same file. It is AES-256-GCM with an authenticated
header, so it says what it claims to be before anyone types a passphrase at it.
It holds exactly what a peer would have been able to read — the areas somebody set `export` on,
their documents, and the links between them where both ends are inside that set. That is read from
the same surface a link reads, not filtered on the way out, so no bug in this code can serve an area
nobody decided to share.
Grafting prefixes every id, and the receiving repository's **own validator** decides whether the
result is coherent. Without `--graft` it unpacks to a directory and writes into no ontology at all.
**[transfer/README.md](transfer/README.md)**.
## Layout
```
ontology/ the ontology API — python + pyyaml + git. Knows nothing about any domain
exchange/ where backbones meet. No repository, no areas, no hop 0
web/ the map and a proxy — one FastAPI file. Knows nothing about any domain
admin/ the operator's screen for an exchange. Off unless EXCHANGE_ADMIN_TOKEN is set
mcp/ the MCP server, so any MCP-capable agent can read the ontology
transfer/ export, import and graft — one encrypted file, for where a link cannot reach
static/ the map screen
seed/ an empty ontology, copied into data/repo on first boot
examples/ one worked ontology, and seed-demo.sh — six backbones across two rooms
check/ every check. docs/CHECKS.md
docs/ everything below
data/repo ← your ontology. A git repository, and the only thing to back up
data/* publish, overlays, harness, exchange, access — all derived or local.
docs/DATA-REPO.md
```
## Everything else
| | |
|---|---|
| [ROUTING.html](docs/ROUTING.html) | the whole structure, in a browser |
| [INSTALL.md](docs/INSTALL.md) | installing by hand, what to do when it does not come up, the first two things to write |
| [PEERING.md](docs/PEERING.md) | links between backbones, circuits, six-hour sessions, the operator's screen |
| [AGE.md](docs/AGE.md) | the two times on a routing row, and what they deliberately do not say |
| [transfer/README.md](transfer/README.md) | carrying a backbone somewhere a link cannot reach |
| [OVERLAY.md](docs/OVERLAY.md) | the working set for one question |
| [AGENTS.md](docs/AGENTS.md) | connecting an agent — every client, and the tools |
| [LLM.md](docs/LLM.md) | the optional ✨ Suggest buttons, and the three provider wires |
| [AUTH.md](docs/AUTH.md) | who may write, and whose name goes on the change |
| [DATA-REPO.md](docs/DATA-REPO.md) | your ontology as a git repository, and the one derived file in it |
| [CHECKS.md](docs/CHECKS.md) | every check, what it proves, and where it can run |
| [PLATFORMS.md](docs/PLATFORMS.md) | macOS, Linux, Windows — and what differs on each |
| [I18N.md](docs/I18N.md) | English, 한국어, 日本語, 简体中文 — and adding one |
| [SCENARIOS.md](docs/SCENARIOS.md) | the routing table over a whole lifetime |
| [DOMAIN-NEUTRALITY.md](docs/DOMAIN-NEUTRALITY.md) | which rules still belong to the domain this came from |
| [PROVENANCE.md](docs/PROVENANCE.md) · [DELTA-FROM-IRIS.md](docs/DELTA-FROM-IRIS.md) | where this came from, and what changed |
| [TODO.md](docs/TODO.md) | known gaps, written down rather than glossed over |
| [eval/](eval/) | the study — pre-registered before it is run |
---
## Contact
Business inquiries, collaboration, or just curious: **qct8377@gmail.com**
LinkedIn → [linkedin.com/in/cspark911](https://www.linkedin.com/in/cspark911/)
Bug reports and questions → [GitHub Issues](https://github.com/CSP911/routemind/issues)
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 3 tools
knowledge_table discovers areas and prints addresses, knowledge_read fetches a specific document, and knowledge_circuit federates to a peer's view. The three are mostly separable, though table and circuit both return browsable lists and could be momentarily confused by an agent unfamiliar with the peering concept.
All three share the 'knowledge_' prefix, giving a predictable namespace. However, the suffixes mix a noun (table), a noun-as-metaphor (circuit), and a verb (read), so the verb_noun pattern isn't strictly uniform.
Three tools is thin but defensible for a read-only routing/exploration server with a narrow purpose. Each tool maps to a distinct step (discover, fetch, federate), so nothing feels redundant, though it sits near the low end.
Read-only is intentional, so no write operations are expected. But the primary entry point is broken (HTTP 404 fetching the area list) and there is no search tool, leaving a real dead end for agents trying to orient themselves.