Skip to content

−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

live · 12 memories indexedthis is real
7.66facts/people/user.md#editor+ editor, ide

Uses neovim. Has since 2019. Prefers a terminal editor over an IDE for anything under a thousand lines.

L12–1982e45e8confidence 0.9
try
1 hits · 0.22ms

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.

ONE BINARY·NO CGO·NO SERVICE TO RUN·SQLITE + FTS5·GO-GIT·LLM-AGNOSTIC·ARM64 + AMD64·INDEX IS A PURE CACHE·
the problem[01/13]

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.

no history

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.

no trust

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.

no structure

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.

the mechanic[02/13]

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.

memory/node color + hash derived from content
database/replication.mdcf6dc12
orgs/acme.mda953af5
2026-07/pg-connection-storm.md07becd7
never-force-push.md5487c64
database/restore-from-wal.md0933763

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.

blamemove the cursor
$ gitloom log facts/people/user.md --blame
3f9a1c2ingestUses neovim. Has since 2019.
8b21e04ingestDark mode preferred.
8b21e04ingestShips on Fridays without ceremony.
c07d5aagcconfidence: 0.9
c07d5aagctags: [user, prefs]
1e4f7b9ingesttier: facts
diffhistory · diff · blame — free
$ gitloom diff 8b21e04..3f9a1c2 -- facts/people/user.md
-editor: vscode
+editor: neovim
+confidence: 0.9 · held since 2019
facts/long-term

Stable knowledge, nested by topic to arbitrary depth.

no ttl
incidents/short-term

Date-sharded and TTL-bearing. Expired ones are swept as their own gc: commit.

ttl 30d
rules/always-on

Small, and always loaded whole. Never partially recalled.

loaded whole
skills/procedural

Lazy-loaded procedures, pulled only when the task calls for them.

lazy
how it works[03/13]

Write, 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.

  1. 01

    A markdown file, and a commit

    write

    Frontmatter carries tier, tags, confidence, ttl, supersedes. The write goes through one serialized writer, so a reader never sees a half-applied tree.

  2. 02

    Diff the Merkle tree, skip what matched

    index

    Git 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.

  3. 03

    Lexical and semantic, fused

    recall

    BM25 over FTS5 and brute-force cosine over float32 vectors, ranked together with RRF. With no embedder configured, every semantic path degrades to lexical.

  4. 04

    A path and a line range

    cite

    Every result is an address — facts/people/user.md#editor, lines 12–19. Not a similarity score against a chunk nobody can find again.

a memory changes — and the change is legible
# facts/people/user.md
tier: facts
-confidence: 0.6
+confidence: 0.9
-Uses VS Code.
+Uses neovim. Has since 2019.
$gitloom diff facts/people/user.md 8b21e04 3f9a1c2
1 file changed, 2 insertions(+), 2 deletions(-)

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.

internals[04/13]

Three mechanisms carry the whole thing.

Everything else is plumbing. These are the parts worth understanding before you trust it with anything.

i. Merkle diffing

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

ii. Rank fusion

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

iii. Shard routing

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

benchmarks[05/13]

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.

91.4%

LongMemEval accuracy · oracle split

456/499 on the oracle split — up from 44% at v1

96ms

hosted retrieval, p90

server-side on api.gitloom.cloud · p50 87 ms

235ms

incremental sync

50 changed files of 10k — vs 2.05s cold build

200–760µs

search p50

BM25 over FTS5, flat from 1k to 100k

accuracy by question type — v1 → v7 v1 gain loss
knowledge-update
94.9%+26.9
multi-session
87.9%+55.9
single-session-assistant
96.4%+84.4
single-session-preference
100%+87
single-session-user
97.1%+34.1
temporal-reasoning
85.7%+32.7
abstention (subset)
87%-6
OVERALL
91.4%+47.4
v1 → v3+28 pts

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.

v3 → v4+8 pts

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.

v4 → v5+3 pts

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.

v5 → v7+8.4 pts

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.
15ms retrieval against a 13.4s mean QA turn — v5 run, 500 instances

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.

retrieval — parallel prefetch
7ms
retrieval — agent tool calls
8ms
judge (LLM grader)
1.0s
QA turn (LLM agent)
13.4s
ingest (LLM extraction)
63.0s

log scale · mean over 500 instances, concurrency 48

Embeddings, isolated

Same binary, same prompts, only -embed-endpoint toggled.

embeddings on80.0%400/500
control (off)76.0%380/500

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.

engine[06/13]

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.

scaling[07/13]

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.

10,000 independent shardswrites parallel, not serialized
…× 10,000

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.

hosted[08/13]

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.

10,000shardsone repo + one index each
100Mmemories10k per shard
96msp90 recallserver-side, hosted API
parallelwritesnot serialized through one mutex

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.

positioning[09/13]

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.

integration[10/13]

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.

cli
$gitloom --repo ./memory search neovim
0.00 facts/people/user.md#editor
Uses [neovim].
# before filing under a new topic, check what exists
$gitloom topics --like databas
facts/database (212 memories)
skills/database (18 memories)
# teach it an abbreviation, once
$gitloom vocab add --term kubernetes --alias k8s
k8s → kubernetes · search finds either form
agent
$gitloom agent "which payment providers do we use?"
→ tool search{query: "payment provider"}
→ tool related{path: "facts/orgs/acme.md"}
→ tool recall{path: "facts/orgs/acme.md"}
Stripe and Razorpay. Migrated off Braintree in 2024.
cite facts/orgs/acme.md L4–9 3f9a1c2

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.

OpenAI-compatiblego installarm64 & amd64
pricing[11/13]

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.

Free
₹0
500 writes / mo

A working monthly allowance, not a trial.

  • 500 writes
  • 10,000 retrievals
  • 25 playground chats
Create an account
Pay as you go
₹500 min recharge
per-unit, from a wallet

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
Recharge
Starterruns the CLI
$5/mo
1,000 writes / mo

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
Subscribe
questions[12/13]

The ones worth asking.

start[13/13]

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.

facts/people/user.mdmove the cursor
---
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]].