The memory is yours. The model is a guest.
Your coding agent answered out of the wrong file. Ulpia is the memory it asks instead: your own markdown, indexed where it sits, with zero guessing.
See the benchmarks Read the source
Pre-launch. Used daily.
A library that lives with you.
Ulpia is a library, named for Trajan's. Your notes, your decisions, your protocols are its books: markdown files, in folders, on your disk. A folder with a note in it is already a base, with no git init and no manifest, and moving the folder moves everything, because no absolute path exists anywhere inside a fleet.
Vesta is its librarian. Ask her a question and she answers with the files that should open, and no model reads your library to do it. When she is wrong, she says why.
What you would use it instead of.
A notes app, and Obsidian is the one people name. Graph view, backlinks, canvas, sync, a plugin ecosystem years deep, and Ulpia has none of them. Every one is drawn for a person to look at. The agent in your repository opens a shell, not a vault: it greps your markdown, and grep has no opinion about which file is right. If you read your own notes with your own eyes, keep Obsidian.
A vector memory layer. It fixes the matching and leaves the certainty alone: a ranking has a top entry whether or not the corpus holds the answer, so the confident wrong citation survives the upgrade. It does ingest without asking you to write anything, which Ulpia does not.
Ulpia organises the same files for the query instead of for the eye, and nothing in its retrieval path guesses, which is also what lets a score come up short. On LongMemEval, a public benchmark of 500 instances, the fleet got that call right 29 of 30 on 24 August 2026, and answered 51 of 56 on single-session-assistant in the same run.
Watch her work.
$ kb route "why is there no embedding model in the retrieval path" decisions --hybrid 0.033 decisions 0018-no-model-in-the-retrieval-path.md keywords #1 + text #2 0.032 decisions 0027-a-model-decides-who-answers.md keywords #4 + text #1 0.029 decisions 0017-no-dense-scorer-yet.md keywords #6 + text #13
Two scorers read every question: one over the Search for line each note carries under its title, the label on the book, and one over the full text. The verdict is hit, guess or nothing, from one number: the top file's keyword score against a floor that scales with the library.
Whether the second scorer agreed is reported beside it, in the last column, and never as the verdict. The score shown fuses both rankings; kb route --json prints the keyword score behind the verdict and its floor.
A miss is answered too: instead of an empty screen, the library offers the nearest keys it does hold. And when a subject belongs to nobody in your fleet, Vesta says exactly that, which no score can do. The guest below is who does.
How long one question takes.
A hosted memory layer embeds your question and crosses a network. This one reads an index sitting beside the files it indexes. That gap is the two mechanisms, not an optimisation.
128x mem0's own published search p50 against ours, measured. Against Zep's fastest published figure, 69x at the same percentile.
-
Ulpiawe measured it
1.16 ms
-
mem0their paper
148 ms
-
Zeptheir blog
150 ms
-
Supermemorytheir site
under 300 ms
-
Lettatheir docs
none published
benchmarks/latency/RESULTS.md.
The model is a guest.
An embedding model between you and your own notes buys semantic matching, and your notes have been somewhere you did not choose. A line of keys on each note is a smaller bill, paid earlier, and this is what it buys back.
- Offline & Air-Gapped, because your files are read on your machine and nowhere else; with local models, zero bytes leave the physical device.
- Explainable, because the answer names the words that matched under each file.
- Reproducible, because the same question finds the same files, today and in a year.
- Private, because each base declares its own private layer, and Vesta serves nothing inside it without --all.
- Portable, because the source of truth is plain markdown and relative paths, so moving across directories or operating systems (Windows, Linux, macOS) never breaks Git or memory.
One step may still call a model, and it is about your fleet, not your notes. Finding the file is a lookup, and code does that exactly. Choosing the agent is a judgement, so a model does that one.
It sees your question, the roster, the file names Vesta found with the words that matched, and the one line at the top of each where you said what it is for. Never the files: a briefing at the door, never the shelves. It takes seconds, and you name the command it calls, or remove it and let the keyword scores pick.
A model may stand between you and your fleet. Never between you and your notes. Hand the files to whatever model you like: yours, locally; Claude, through MCP; something else next year. The memory outlives the model.
Where it stands. The doors are open.
Early, used daily, and honest about which is which. The core is tested; no count is printed because the number moves between sessions. The tray app is Windows only and young. There is no hosted instance, and local model routing is not built. The command line tool ships built for Linux, Windows and macOS, each artifact executed on its own architecture before it is published and each one signed, so a stranger can check where it came from and not only that it arrived whole.
The source is public at github.com/richard-wollyce/ulpia, Apache 2.0, cloneable today. The benchmarks put every number of ours beside its command, machine and date.
Our address is hello@ulpia.io, a plain mailto under this button. Press it and the address goes to your clipboard, and you can leave yours: we write when there is something you can install, rarely otherwise.