Uses neovim. Has since 2019. Prefers a terminal editor over an IDE for anything under a thousand lines.
−your agent forgets+every memory has a commit+every fact has a blame
GitLoom is a memory engine backed by a real git repository. Every recall cites a path and a line range — not a cosine score against a chunk nobody can find again.
Pure Go · no CGo · SQLite + FTS5 · go-git
the thing being replaced
That panel is not a mock-up. It runs a real BM25 pass with vocabulary-alias expansion over a 12-memory corpus and cites the line range it matched.
Search k8s and it returns the memory that says kubernetes — because the engine was taught the alias once, and expansion is pure Go.
Nobody can tell you why your agent remembers something.
Retrieval quality gets measured constantly. Retrieval accountability almost never does — and it is the part that breaks when someone asks where a wrong answer came from.
You can't diff an embedding
A vector store overwrites. When a fact changes, the old one is gone — no record that the agent ever believed something else, and no way to see when it stopped.
Provenance dies at chunk time
Once a document is split and embedded, the answer is a cosine score against opaque floats. "Why does it think that?" has no answer you can open in an editor.
Everything is one flat bag
A stable preference, last Tuesday's incident, a hard rule, and a procedure all get the same treatment — same lifetime, same ranking, same recall path.
It’s just a git repo.
Markdown with YAML frontmatter is the source of truth. Four tiers, each a directory, nested to any depth. Nothing about that is a metaphor — you can cd into it.
Two nodes share a color only if they say the same thing — the same way git names an object by the hash of its own bytes.
Stable knowledge, nested by topic to arbitrary depth.
no ttlDate-sharded and TTL-bearing. Expired ones are swept as their own gc: commit.
ttl 30dSmall, and always loaded whole. Never partially recalled.
loaded wholeLazy-loaded procedures, pulled only when the task calls for them.
lazyWrite, index, recall, cite.
Four moves. The interesting one is the last: a result is an address you can open, not a score you have to trust.
- 01
A markdown file, and a commit
writeFrontmatter carries tier, tags, confidence, ttl, supersedes. The write goes through one serialized writer, so a reader never sees a half-applied tree.
- 02
Diff the Merkle tree, skip what matched
indexGit tree objects are content-addressed. Any subtree whose hash is unchanged is skipped without walking into it — the same trick that makes git status fast.
- 03
Lexical and semantic, fused
recallBM25 over FTS5 and brute-force cosine over float32 vectors, ranked together with RRF. With no embedder configured, every semantic path degrades to lexical.
- 04
A path and a line range
citeEvery result is an address — facts/people/user.md#editor, lines 12–19. Not a similarity score against a chunk nobody can find again.
A vector store would have silently replaced one embedding with another. Here the agent’s previous belief is still on the record, with the commit that changed it and the moment it stopped being true.
Three mechanisms carry the whole thing.
Everything else is plumbing. These are the parts worth understanding before you trust it with anything.
Skip any subtree whose hash did not move
Git tree objects are content-addressed, so a directory that has not changed keeps the same hash across commits. Re-indexing diffs the new root against the last-synced one and prunes equal subtrees without walking into them — the same mechanism behind a fast git status.
50 of 10,000 files visited
Two rankings, reciprocally fused
BM25 over FTS5 produces one ordering; brute-force cosine over float32 blobs produces another. Reciprocal rank fusion merges them. With no embedder configured the vector arm simply is not there, and every semantic path degrades to lexical rather than failing.
+4.0 pts, 61 fixed / 41 broken
One repo and one index per shard
The write path is a single in-process mutex, so an org-wide repo would serialize every write company-wide. Sharding by user or team dissolves that: writes run parallel across shards, and cross-shard reads are scatter-gather over the few that matter.
10,000 shards · 100M memories
Every round was diagnosed from the previous round’s failures.
The real LongMemEval oracle set — 500 instances, 6 question types — ingested, answered by a tool-using agent, and graded by an abstention-aware judge. Not a subset, not a rerun of the easy half.
LongMemEval accuracy · oracle split
456/499 on the oracle split — up from 44% at v1
hosted retrieval, p90
server-side on api.gitloom.cloud · p50 87 ms
incremental sync
50 changed files of 10k — vs 2.05s cold build
search p50
BM25 over FTS5, flat from 1k to 100k
Reasoning back on, extraction rewritten
v1 ran with thinking disabled and told extraction to ignore assistant chatter — so anything the assistant said was never stored, starving single-session-assistant to 12%. v3 captured assistant facts as atomic memories and injected a deterministic topic map into every QA prompt.
45% of failures had already found the answer
Classifying all 116 non-abstention failures showed nearly half retrieved the right memory and said "I don't have that information" anyway — a prompt bug, not a retrieval one. v4 forbids declining when search returns results, and adds hybrid semantic search plus a parallel deterministic prefetch.
Counting and ordering, not retrieval
Most remaining failures were off-by-one counts and wrong "first"/"most recent" on evidence already in hand. v5 adds an evidence-table protocol: enumerate every candidate with its date, dedupe, filter, sort — then read off the answer instead of answering from impression.
Nothing about the benchmark changed
Same model, same judge, same hybrid search as v5 — the gain is two weeks of engine work landing in between: deterministic retrieval fixes, cue-based and entity-aware retrieval, and relationship edges gaining a validity window, so an update to a fact supersedes what it contradicts instead of returning both. The gain is spread across every category rather than concentrated in one, which is what a retrieval correctness fix looks like and a prompt tweak does not.
Retrieval is 0.11% of the QA turn. The bottleneck was never memory.
Where the time actually goes
Each extra search the agent issues costs ~1.6s of LLM round-trip, not retrieval time. Retrieval stays single-digit milliseconds regardless of call count.
log scale · mean over 500 instances, concurrency 48
Embeddings, isolated
Same binary, same prompts, only -embed-endpoint toggled.
61 fixed, 41 broken. Gains land exactly where vocabulary mismatch is expected — multi-session, knowledge-update, preference. Losses land on lexically-easy categories where fused ranking displaces an exact keyword hit, which says the equal-weighted RRF still has room in it.
What is actually in the box.
No service to run, no cluster to size. One Go library, one CLI, one SQLite file per shard.
Incremental indexing
Merkle-tree diffing against the last-synced commit. 50 changed files out of 10k visits ~50 files, not 10k.
Hybrid search
Lexical BM25 and vector cosine fused with reciprocal rank fusion. Vectors keyed by content hash, so a re-sync only re-embeds what changed.
Custom vocabulary
Teach it k8s == kubernetes once. Query tokens expand across a term's aliases, so either surface form finds the other.
Relationship graph
Edges in frontmatter or inline [[wikilinks]], resolved in three passes. A forward reference heals itself once its target exists.
TTL expiry
Incidents carry a lifetime. The sweep deletes expired ones through the same serialized writer, as its own gc: commit.
Navigable tree
PageIndex-shaped: tier → topic → file → nested sections, each with a title, summary and line span. O(direct children), not a corpus scan.
The index under _index/ is a pure cache and never a source of truth. gitloom rebuild reconstructs a correct index from git alone, and a schema-version bump triggers it automatically on next open.
Don’t put everyone in one repo.
The storage numbers are not what stops scaling first. Two other things do — and both dissolve the same way: shard by user or team.
One in-process mutex per repo
There is no cross-process write lock yet, so a single org-wide repo would serialize every write company-wide.
Tree-diff cost grows with tree size
~3.4s of a 100k-file sync is go-git tree diff, not the index. Every sync pays it, not just writes.
Cross-shard queries are scatter-gather across the few relevant shards — a user’s own, plus their team’s — not a fan-out to everything. None of it needs architectural change: RepoDir and IndexPath are already per-instance. What is missing is the routing layer and cross-process write locking, both tracked as follow-on work.
100M
memories organization-wide
10,000 users × 10k memories each — every shard still inside the measured sweet spot: 4ms vector scan, microsecond lexical search, single-digit-millisecond answers.
Or let someone else hold the fleet.
Sharding is the answer to scale, and ten thousand of anything is an operations problem. Managed does not mean opaque — it is the same binary, and the door out is a clone.
Routing you do not write
RepoDir and IndexPath are already per-instance — the missing piece for self-hosting an org is the routing layer and cross-process write locking. Hosted ships both.
Restore to any commit
Because a shard is a git repo, point-in-time restore is a checkout. There is no separate backup format to trust, and no export step that can drift from the original.
The same engine
Hosted runs the identical CGo-free binary you would run yourself. Nothing is held back, and moving off is a git clone rather than a migration.
Where it wins, and the one row where it doesn’t.
A comparison table that only ever favours the seller is a comparison table nobody believes. The last row is the honest one.
| axis | GitLoomgit-backed | Vector DBANN index | Plain RAGchunk + embed |
|---|---|---|---|
| Provenance | git log + blame, per line | None | Chunk id, if you kept it |
| Diffability | git diff between any two commits | Re-embed and compare floats | None |
| Explainability | Path + line range | Cosine score | Cosine score |
| Setup | One binary, no CGo, no service | Cluster or hosted service | Pipeline + store |
| Cost at scale | Per-shard SQLite file | Per-vector hosted pricing | Embedding + store spend |
| Recall over 1M+ vectors | Needs sharding past ~250k/shard | ANN index built for it | Depends on store |
Past roughly 250k vectors in a single shard, an exact scan stops being the right call and a purpose-built ANN index earns its complexity. Below that, brute force is faster than maintaining an index — and it keeps the whole stack CGo-free.
One capability layer. Two front-ends.
Every operation is defined once as a self-describing op — a param schema plus a handler. The CLI and the agent tools are both thin adapters over it, so nothing is implemented twice.
The embedder is injected, not built in. Point it at any OpenAI-compatible /v1/embeddings endpoint — or configure none, and every semantic path degrades transparently to lexical search.
Pay for the work, not the memory.
Storage is free — a memory that costs more the more it remembers is one you will be tempted to forget. What is metered is the work: extraction on writes, search on reads, the model on chats. A write is one saved fact or one conversation. Every paid plan overflows into the wallet rather than stopping.
A working monthly allowance, not a trial.
- 500 writes
- 10,000 retrievals
- 25 playground chats
No subscription. Recharge a wallet and pay for exactly the work you use.
- ₹0.50 per write
- ₹0.02 per retrieval
- ₹1.00 per playground chat
- Tightest rate limits — that is the whole trade
Twice the writes, higher rate limits, and overages instead of a hard stop.
- 1,000 writes · 50,000 retrievals
- Higher rate limits
- Overages billed from the wallet
The ones worth asking.
Memory that can be cross-examined.
Every memory has a commit. Every fact has a blame. When someone asks where an answer came from, you open a file and point at a line.
Experimental. If it pans out, it becomes part of github.com/MelloB1989/karma.
--- tier: facts tags: [user, prefs] confidence: 0.9 updated: 2026-07-26 --- Dark mode preferred. Ships on Fridays without ceremony. ## Editor Uses neovim. Has since 2019. ## Timezone IST. Prefers async review to calls. Works with [[facts/orgs/acme.md]].