I've been running this on my own machines for six months, every working day, before it had a name I liked or an installer anyone else could use. That's the only qualification I have for writing this.
Every coding agent I use is competent and amnesiac. Claude Code knows my repo layout for exactly as long as the session lasts. Cursor holds a conversation until I reload the window. Claude Desktop keeps its own history in its own store, which Cursor cannot read, which Claude Code cannot read either. The decisions of the last half-year — why I picked AGPL, why the index lives outside the encrypted vault, which approach already failed and why — live in six separate places, none of which talk to each other, and all of which belong to a vendor.
The industry answer to this is vendor memory: ChatGPT remembers you, Claude remembers you, each inside its own walls. That is a retention feature, not a record you own. You cannot export it programmatically into a shape another agent reads, and it does not survive you switching tools.
So I built the other thing. Pomnia is a memory layer that sits underneath whatever agent you happen to be using, speaks MCP, and stores its corpus on hardware you control. It isn't an agent, a chat UI or a wrapper, and it has no opinion about which model you use.
If the name is new to you but the project is not: this was Brain AI Hub when I wrote about it here in June. Same project, renamed — that post points at a repository I have since frozen.
This is a write-up of how it actually works, including the parts that aren't encrypted and the parts that don't exist yet.
What "portable memory layer" means concretely
Three components, and the split between them is the whole design:
Pomnia Desktop (Electron, Windows and macOS, Linux AppImage/deb) is the writer. It reads conversations out of four assistants already on the machine — Claude Code's JSONL under ~/.claude, Cursor's state.vscdb, Claude Desktop and Antigravity — snapshots the profiles of three more (VS Code, Windsurf, Continue) without extracting chats from them, and imports exports from Claude.ai, ChatGPT, Gemini and Grok, plus PDF/DOCX/EPUB documents. It distils them into notes and writes those into a vault folder.
brain-core is the same engine with a different entry point: one Node process that speaks MCP over HTTP, indexes a vault, and serves it to any agent holding a bearer token. No web app, no database server, no queue. This is what you run on a Linux box.
The vault is a plain folder. Copy it to another machine and open it there. That is the portability claim, and it is a folder-copy claim, not a sync-service claim.
The pipeline: distill, chunk, embed, hybrid search
Raw chat logs are terrible retrieval material. They are mostly ceremony — greetings, tool call noise, three attempts at the same thing. Embedding them wholesale gives you an index that returns the shape of a conversation rather than its conclusion.
So the first stage is distill: a local LLM through Ollama turns a conversation into a structured note with fixed sections — Summary, Decisions, Root causes, Solutions, Files touched, Commands run, Errors seen, Facts, Open questions. The note keeps the language of the source; my vault is bilingual Polish and English and nothing translates anything.
Distillation runs on the client, deliberately. brain-core does not distil at all. The LLM stage needs a model, a GPU and a passphrase-holder, and none of those belong on a headless server that exists to serve many agents.
Then chunk and embed. Chunking is byte-identical between the desktop build and the server, and both apply nomic's search_document: / search_query: prefixes, so a library.db built by one is readable by the other as long as the embedding model matches. Change --embed-model and the index is invalid — and an incremental reindex will not fix it, because file contents did not change so the indexer skips every file and reports success. You have to delete library.db.
Then hybrid search. search_library combines semantic similarity with keyword hits and returns chunks with source path, page and score. Query in either language. Notes that failed the quality gate during distillation carry a penalty and rank below full-value versions of the same conversation, which matters more than it sounds: without it, a garbage summary of a good session outranks the good one because it is shorter and denser.
Indexing is incremental — indexDir skips unchanged files on mtime and size, then content hash, so nothing re-embeds without reason.
Search that says how sure it is
For a long time search_library returned its top five whether or not the vault knew anything. With nothing relevant indexed it returned the five least irrelevant chunks — in exactly the shape of a real answer. An agent handed five loosely related fragments writes a plausible answer out of them, so the memory layer was itself a source of the thing it exists to prevent.
Every response now carries a verdict in prose, because a model has no calibration for score: 0.32 but will read a sentence: strong, lexical ("matched on words, not on meaning — leads to verify"), or none ("nothing covers this, do not build an answer out of these").
Nothing is suppressed, only labelled. Measured against my own vault, questions whose answer is in there score about 0.21 on semantic similarity; questions about things that could not be in there score about 0.06 — and two genuine hits in that measurement sat below the floor, carried entirely by their words. Dropping them to look decisive would have lost real answers.
What six months of this actually changed
Three things, and none of them are features.
I stopped re-explaining myself. This is the boring one and it is the one that matters. A new session used to open with me establishing the same context — the repo layout, the constraint I settled on in March, the approach that already failed and why. Now the agent asks the corpus before it answers. My vault today holds 2575 distilled notes and sessions, indexed into 3879 chunks, built from conversations I've already had and would otherwise have had to repeat.
It got cheaper. Not because of a pricing trick — because re-establishing context costs tokens every single time, and I stopped paying that toll several times a day. If you work with agents seriously you already know the shape of that bill.
I got something I can point at. This is the one I didn't expect to care about. My assistants' memory used to be a thing that happened to me somewhere else, governed by someone else's retention policy. Now it is a folder. I can open it, grep it, back it up, copy it to another machine, delete a note I regret. That is a smaller claim than "AI memory" and a much more useful one.
The honesty section, which is the point
You don't find out that your search index is plaintext by designing a product. You find it out by living inside one.
Pomnia's vault blobs are encrypted with AES-256-GCM, random IV per blob, integrity tag, key derived by scrypt at N=2¹⁷ from a passphrase that is never stored. That is real and you can read src/core/crypto.ts to check it.
Here's what is not encrypted, and I'd rather you learn it from me than from strings:
-
Vault sidecars are plaintext.
skills/,USER.md,sessions/, the distilled notes. They sit in the vault folder as ordinary Markdown, on purpose, because folder-first is the property that makes them yours — you can open them in Obsidian, grep them, put them in git. The crypto layer ignores them. -
The search index is plaintext.
library.dbholds the full text of every chunk, not just vectors, because hybrid search and citations need the text. It lives in AppData (or/var/lib/pomniaon a server) as a machine-local rebuildable cache. - Locking the vault seals the key, not the index. Lock clears the open-vault path. It does not wipe AppData. An unlocked-then-locked machine still has a readable index on disk.
So the accurate sentence is: the passphrase seals the archive of blobs; the knowledge surface your agents read is local plaintext — protect the folder and the disk. Full-disk encryption and folder ACLs are doing real work here and I'm not going to pretend otherwise.
I chose messaging over shipping SQLCipher before launch, and that was a deliberate call rather than an oversight. Encrypting the index costs a key lifecycle, a reindex UX, and breakage on every agent path that reads a sidecar directly, and it buys nothing against the threat most single-user local-first setups actually face. If the stolen-laptop or shared-PC case turns out to hurt real users, wipe-on-lock is the cheap fix and a vault-keyed index is the expensive one. Neither is written yet.
The night the vault would not open
Worth telling because it's the least flattering thing here, and because it's why I trust the durability code now.
I was running a backup when the machine lost power. On the way back up, the vault refused the passphrase — bad magic, not a Pomnia blob. manifest.cvb, the index of every snapshot in the vault, had come back as 55 088 bytes of zeros. Not corrupted in an interesting way. Zeros.
The cause was a line I'd written myself and would have defended: write to a temporary file, then rename() over the target. That's the standard atomic-write recipe, and it's genuinely atomic — against readers. It says nothing about durability. The rename is journalled, the file's contents sit in the page cache, and a power cut between the two leaves you with a perfectly-named file full of nothing.
I wrote a repair tool that rebuilds the manifest by reading the snapshots that survived. It got 137 of 141 back. Four were gone. No tool was going to fix that.
Then I fixed the actual bug: fsync before the rename, a manifest.cvb.prev kept one generation back, a fallback that reads it when the primary is unreadable, and an error message that tells you how many snapshots survived and what command to run — instead of bad magic, which is true and useless. There is a test that simulates the interrupted write, because a durability fix without a reproduction is a hope.
If you're evaluating anything that holds data you care about, that's the question worth asking: not whether the author claims atomic writes, but whether they know the difference between atomic and durable.
Wiring it up
Server, on a Linux box with systemd and Node 22 at /usr/bin/node:
curl -fsSL https://pomnia.ai/install.sh | sh
It resolves pomnia-brain-core-*-linux-x64.tar.gz from releases/latest, verifies sha256 against the published .sha256 asset, then hands off to deploy/install.sh under sudo. POMNIA_BOOTSTRAP_DRY_RUN=1 stops after unpack so you can read the thing before it touches your system, which is the correct instinct with any curl | sh. Add --with-ollama to answer the embedding-model prompt up front.
The installer creates a system user, writes the unit, starts the service, and checks that it answers before telling you it worked. It prints an admin password and a bearer token once. On a fresh install the host claims the empty vault and becomes its writer, so an agent can save into it immediately with no desktop involved.
Then point an agent at it. Cursor, ~/.cursor/mcp.json:
{
"mcpServers": {
"pomnia": {
"url": "http://192.168.1.10:7865/mcp",
"headers": { "Authorization": "Bearer btk_…" }
}
}
}
Claude Code uses ~/.claude.json with "type": "http". VS Code 1.103+ uses the same shape but the top-level key is servers, not mcpServers. Pomnia Desktop's Connect tab generates all of these for you, which is less impressive than it is useful, because the top-level key differing between clients is exactly the kind of thing that costs an hour.
The tools an agent then sees: search_library, save_conversation, checkpoint_session, get_user_profile, memory, library_status, list_skills, list_cli_skills, get_skill. Nine, and that's the whole surface. Two of them do most of the work — get_user_profile at session start so the agent knows who it is talking to, and search_library before it answers anything that might already have been decided.
The unit does not depend on Ollama at all — no Requires=, not even a Wants=, because the appliance embeds in-process and never needs it. Without Ollama the server still starts and still serves skills, profile and note reads; semantic search stops and /healthz reports degraded rather than pretending. /healthz returns 503 when the server genuinely cannot serve — an index that is empty while the vault holds notes, an unreadable vault, a database that will not open — because "process is up, every search returns nothing" used to be the state that looked healthy. An empty index over an empty vault is degraded and answers 200: a fresh install has not failed, it has nothing yet.
What it does not do
- Sync is push-based and local. The desktop syncs both ways to a server you run, keeping both copies when a file differs on each side. No git-remote, no S3, no hosted anything, no mobile client — portability past your own machines is still copying a folder.
- One vault, one writer. A second instance over the same corpus serves read-only and names who holds the lock. Real multi-tenancy needs auth and per-user isolation, which is a different product.
-
Nothing is signed. SmartScreen warns on every new Windows hash, Gatekeeper warns on the macOS
.dmg. That is reputation, not a verdict, and the certificates cost money I have not spent. Do not turn off your antivirus for me. - Distillation still wants a chat model, and it takes the machine with it. The server embeds in-process now, but distilling needs Ollama, and a large backlog on a consumer GPU runs for hours. Pointing the desktop at Ollama on another box fixes that completely, and it is what I actually run.
-
run_skill,search_codeandcode_statusare stubs. Handled so a client with a cached catalogue gets a clear answer. They do not work. - It is 0.1.x. The repo went public two months ago; the first installable release is a month old. Six months on my own machines is a real test and a narrow one: one person, one language pair, three computers. Everything above is what happened to me, not a claim about what will happen to you.
Why AGPL and not a cloud vault
A hosted vault would be easier to build and much easier to sell. It would also make me the thing I'm trying to route around: another company holding the record of how you think, with its own retention policy and its own exit.
AGPL-3.0-only, specifically, rather than MIT. Section 13 means anyone offering a modified Pomnia as a network service owes their users the source. MIT would let someone take this, close it, host it, and sell the exact product I refused to build. The licence doesn't forbid making money — I hold the whole copyright and can sell commercial licences to anyone AGPL doesn't suit. It forbids making money quietly on a closed fork.
The name is reserved separately: fork the code, call it something else. Code can be audited; a name has to be trusted.
Where it is
Source: github.com/lobrzut/pomnia — AGPL-3.0-only. Site and docs: pomnia.ai. Releases carry sha256 for every artifact.
The name is Old Polish. Pomnieć — to remember.
If you try the server and the install script does something stupid on your distro, that's the feedback I want most — it's the newest code in the project.



Top comments (0)