0.4.0, apache-2.0
one memory for every coding agent you run
i use claude code, codex, cursor, gemini cli and copilot, and every one of them used to start from zero. qenlo-memory is one local store they all read and write, so a fix codex found yesterday shows up in claude code today, labeled by codex.
curl -fsSL https://raw.githubusercontent.com/a3ro-dev/qenlo-memory/main/install.sh | sh
no llm inside, 37 ms recall, 52 MB installed, 25 agents wired in one run
243 cubes, one for each memory my agents have written so far. drag it.
the same thing, explained once per agent
every session starts knowing nothing, and none of my agents knew what the others had figured out an hour ago. so the release steps, the preferences and the no, we tried that
got explained again and again. that's fine the first time i guess, it gets old by the fifth.
$ qenlo-memory remember "release: tag, run sdk-release.yml, verify SHA256SUMS" --kind procedural
remembered #41
$ qenlo-memory recall "how do we release qenlo"
#41 procedural | by codex | 2026-11-15 15:12 | qenlo | release: tag, run sdk-release.yml, verify SHA256SUMS
how it works
one daemon, a thin cli, and the agents you already have. nothing new to learn for them, they call it from the shell like git.
install finds your agents
it looks for every coding agent on the machine and links one brain folder into each: the skill, an always-on rule, and hooks where the agent supports them. it backs up any file it edits, once, and
--dry-runshows the plan first.agents run it from the shell
the first call starts one daemon on 127.0.0.1 with a token from your home folder. every other call is a thin client. there's one owner because qenlo locks a collection to one process.
ollama embeds, qenlo searches
embeddings come from ollama on your gpu (arctic-embed 33m, 58 MB of vram). vectors live in a qenlo collection, text and provenance in sqlite next to it.
every memory has a name on it
the agent that wrote it is read from the shell (claude code sets
AI_AGENT), so a recall saysby codex
orby cursor
and you can tell where an idea came from.
wired in one run
- claude code
- codex
- cursor
- antigravity
- gemini cli
- opencode
- copilot cli
- amp
- factory droid
- auggie
- goose
- roo code
- continue
- cline
- kiro
- qwen
- qoder
- windsurf
- crush
- kilo
- junie
- trae
- vs code
- zed
- claude desktop
the last three over mcp, with install --mcp. it only touches agents whose config folder already exists.
recall looks three ways at once
a 22m or 33m embedding model is small enough to run anywhere, and small enough to blur exactly the things agents remember: names, ports, versions, paths. so recall adds three scores.
- meaning
- the query's embedding against every current memory, exact search in qenlo, no approximate index.
- words
- bm25 over sqlite's fts5, which ships with python. it only counts as far as its best hit is real evidence.
- time
last week
orin september
becomes a timestamp window and a second dense search inside it.
0.5 · meaning + 0.5 · words + 0.25 · time
that recipe came out of a closed-loop study of about sixty memory papers. LoCoMo facts recall@5 went from 51.3 to 58.7, and a recency bonus, the obvious other idea, made things worse.
when a fact changes, the old one steps aside
an old port that keeps coming back is worse than no memory at all. in a test with 20 facts that change and 200 distractors, plain append-only memory put the stale value on top 60% of the time.
the agent writing the memory is already an llm, so it can tell when a fact replaced another one. remember shows the memories the new one is probably about, and --replaces links them. the old one leaves search and stays in history. for facts that are made to change, --key staging.db.port names the slot and needs no id. the new value came first 20 out of 20 times.
qenlo-memory remember "the daemon listens on 7438" --replaces 12
qenlo-memory history 12
- #12 the daemon listens on 7437, by codex
- #52 the daemon listens on 7438, replaces #12
- history 12: replaced by #52, still readable
the numbers, losses included
everything here reproduces from research/ in the repo, including the ideas that made things worse.
against EdgeMem, the strongest published llm-free memory
LongMemEval-S, 470 questions, every turn indexed, no llm anywhere. R is the strict reading: every gold session in the top k.
| R@3 | NDCG@3 | R@10 | NDCG@10 | |
|---|---|---|---|---|
| qenlo-memory 0.4 | 82.77 | 91.44 | 94.89 | 93.81 |
| EdgeMem | 81.49 | 90.49 | 93.83 | 92.80 |
| MemGAS | 78.51 | 86.83 | 94.47 | 89.96 |
against mem0 on its llm-free path
same embedding model in the same ollama, the same 2,541 LoCoMo facts and 1,982 questions, mem0 2.2.1 with its bm25 extras on. writes are slower here, 59 ms against 50, because every save checks for a fact it might replace.
| qenlo-memory | mem0 | |
|---|---|---|
| recall@5, higher is better | 58.1 | 54.3 |
| search p50, ms | 34 | 50 |
| peak ram, MB | 47 | 154 |
| installed, MB | 52 | 1,000 |
answer accuracy, all 500 questions
71.6%
judged with LongMemEval's own prompts. the memory stays llm-free, the llm is only the benchmark's reader and judge (nemotron on nvidia's free api). multi-session questions are the weak spot, and the readout is the next thing i'm fixing. EdgeMem reports 60.0 with a different reader, so it's not a like-for-like comparison.
| single-session assistant | 98.2 |
|---|---|
| single-session user | 95.7 |
| knowledge update | 85.9 |
| preference | 70.0 |
| temporal | 62.4 |
| multi-session | 48.9 |
under it, qenloDB
qenlo is the embedded vector database i'm building, in rust. one process owns one durable record store, filters run before ranking, and search is exact. there's no index to tune and no server to run.
indexes are disposable, application data isn't. so ids, vectors, metadata and tombstones live in one canonical store, and the cpu, wgpu, usearch and pytorch paths are structures built from it that can be thrown away and rebuilt. search runs on the cpu by default and moves to the gpu once a matrix is big enough to be worth moving.
sdks for rust, python and typescript, with go, kotlin and swift in preview.
exact search, 100k vectors of 384 dims, cpu only
p95 call latency from retained raw files, every row exact (recall 1.000). lower is better.
| qenlo | 12.9 |
|---|---|
| Milvus Lite 3.0 | 76.2 |
| LanceDB 0.38 | 242.3 |
time to first query is 0.64 s, against 48 to 80 s for FAISS HNSW, Chroma and Milvus Lite to build an index. a selective filter with 1% of rows eligible answers in 0.24 ms against Chroma's 43.
where it loses: FAISS HNSW answers the same 100k cell in 1.5 ms, approximate, against qenlo's 12.9 exact, and FAISS on a gpu beat qenlo's cuda prototype at 1M × 768. qenlo is exact and embedded. it isn't the fastest ann engine and doesn't try to be.
what building a product on it taught the database
- in alpha.10 the write-ahead log only ever grew, because
flush()returned early after every commit. qenlo-memory found it, alpha.11 fixed it. - an agent's memory is mostly
write one thing, then search
. up to alpha.11 every write threw away the search matrix. beta.1 keeps each vector once in the buffer search reads, which made add-then-search 13x faster at 2,000 rows and cut memory per row by 40%.
install it in one line
the script installs uv if you don't have it, installs the cli, then qenlo-memory install sets up ollama, pulls the embedding model and wires every agent it finds. then restart your agents. run the same line again to upgrade.
# mac and linux
curl -fsSL https://raw.githubusercontent.com/a3ro-dev/qenlo-memory/main/install.sh | sh
# windows (powershell)
irm https://raw.githubusercontent.com/a3ro-dev/qenlo-memory/main/install.ps1 | iex
then, from any agent or your own shell
qenlo-memory recall "how do we release qenlo"
qenlo-memory recent -n 10 --agent codex
qenlo-memory remember "staging moved to 6543" --key staging.db.port
qenlo-memory review
qenlo-memory export > memories.jsonl
qenlo-memory stats
good to know
- runs on windows x64, linux x86_64 and apple silicon. intel macs and linux arm aren't covered yet.
qenlo-memory install --dry-runshows every file it would touch without writing anything.- it's local and single-user. the daemon only listens on 127.0.0.1 and wants a token from your home folder.
- secret redaction is a regex for common key formats. it's a seatbelt, not a guarantee.
importreads a mem0 export too, if you're moving over.