Geometric memory for language models and agents. Knowledge is a point in a semantic space, found by proximity, while versions, freshness, and forgetting are derived from an immutable journal. A query returns the current version of the nearest fact, and when no suitable knowledge exists, the memory honestly abstains instead of fabricating. The core is written in Rust and runs on an ordinary processor, without a graphics accelerator.
- Faster than leading vector databases at equal recall. On the standard mnist-784 benchmark, at a recall@10 of 0.9998, geollm serves 953 queries per second against 347 for Qdrant (a factor of 2.7), with the lowest latency, memory footprint, and disk usage.
- Versions and freshness out of the box. A query returns the current version of a fact rather than a mixture of the old and the new. This is something ordinary vector databases do not provide.
- Honest abstention. An answer of "I do not know" when the nearest knowledge lies farther than a calibrated threshold, instead of a plausible fabrication.
- Contradiction resolution by a deterministic rule. On contradictory fact updates (MQUAKE-CF), geollm returns the current version in almost 100% of cases, whereas the combination of retrieval plus a neural-network arbiter returns the outdated fact in the majority of cases.
- Enterprise grade: multi-tenancy, full-text and hybrid search, vector quantization (a quarter of the memory), encryption at rest, TLS, authentication, snapshots, Raft replication, and a Model Context Protocol server.
- Detection of semantic substitution: a fact's history is the path of a point, an abrupt jump is visible as a substitution, which neither an exact key nor full-text search notices.
Everything below is confirmed by 58 automated tests (cargo test) and live benchmarks on
real data.
With Docker in a single command, with the model embedded inside, offline:
docker compose upOr from source (only Rust is required):
cargo build --release
./target/release/geollm-server ./data 0.0.0.0:6333The embedded neural encoder (the default mode embedded) requires model weights, which
are not included in the repository. Download them once and point to the directory:
pip install huggingface_hub
python scripts/fetch_model.py # places the model in ./models/e5-small
export GEOLLM_MODEL_DIR=./models/e5-smallWithout the weights, you can start in lexical mode without a model by setting
GEOLLM_EMBEDDER=lexical.
Security note: the address 0.0.0.0:6333 exposes the server on all network interfaces,
and authentication is enabled only when the variable GEOLLM_API_KEY is set. For a local
run use 127.0.0.1:6333, and on a public address always set GEOLLM_API_KEY and TLS.
The service listens on port 6333 and speaks HTTP with JSON, as is customary with Qdrant:
curl -X POST localhost:6333/write -d '{"text":"loan interest rate","value":"21 percent","fact_key":"rate"}'
curl -X POST localhost:6333/write -d '{"text":"loan interest rate","value":"22 percent","fact_key":"rate"}'
curl -X POST localhost:6333/ask -d '{"query":"interest on the loan"}'
# {"value":"22 percent", ...} the current version, not the previous one
curl -X POST localhost:6333/ask -d '{"query":"temperature of tungsten"}'
# {"abstained":true, ...} honest abstentionAll figures were taken on a single machine (Apple M1, eight cores) on the recognized ann-benchmarks mnist-784 set (sixty thousand vectors of dimension 784, one thousand queries, cosine, recall against exact brute force, identical graph parameters m=16, ef_construction=200, ef_search=96). The competitors (Qdrant 1.18.3, pgvector 0.8.5 on PostgreSQL 16, Manticore 28.6.6) were brought up locally and queried by a single client. This is a head-to-head run on one bench, not vendor-claimed figures. The best value in each row is highlighted.
| Metric | geollm | Qdrant | pgvector | Manticore |
|---|---|---|---|---|
| Recall recall@10 | 0.9998 | 0.9997 | 0.9990 | 0.9973 |
| Queries per second | 953 | 347 | 327 | 121 |
| Latency p50, ms | 0.98 | 2.79 | 3.03 | 8.21 |
| Latency p99, ms | 1.36 | 6.22 | 4.55 | 10.46 |
| Resident memory, MB | 318 | 528 | not measured | 479 |
| Data on disk, MB | 187 | 244 | not measured | 450 |
Bottom line: at recall on par with the best, geollm answers 2.7 times faster than the nearest competitor, with the lowest latency, memory, and disk usage.
Under concurrent load geollm scales across cores. Pure-read throughput in queries per second as the number of simultaneous clients grows (a load generator on a raw socket, the engines loaded with 60000 vectors):
| Simultaneous clients | geollm | Qdrant | Manticore |
|---|---|---|---|
| 1 | 1674 | 385 | 225 |
| 8 | 5795 | 1138 | 883 |
| 32 | 5600 | 1225 | 835 |
| 64 | 5528 | 1193 | 801 |
geollm reaches its peak of about 5800 queries per second already at eight clients, which is 4.7 times higher than Qdrant, at a p99 latency under thirty-two clients of 8 milliseconds against 64 for Qdrant. (The figures are honest, on loaded data: on an empty database any engine yields an illusory tens of thousands of queries per second.)
Under mixed load the trade-off is gone: the write path was rebuilt so that readers do not wait for writers. The expensive planning of an insertion into the graph runs under a shared read lock, an exclusive lock is taken only for a short in-memory commit, the append to disk is performed outside it, and graph nodes orphaned during pruning are repaired by a cheap feedback mechanism with amortized exact recomputation on the writer side. Queries per second at thirty-two clients:
| Write share | geollm | Qdrant | Manticore |
|---|---|---|---|
| Read only | 5319 | 1225 | 836 |
| Five percent | 3739 | 1170 | 875 |
| Twenty percent | 2023 | 957 | 957 |
The previous implementation dropped to 577 and 163 queries per second at five and twenty percent writes; now geollm is ahead across the entire profile, and the number of completed writes over the same six-second window grew from 177 to 1129 at five percent and from 195 to 2468 at twenty.
For write-heavy load, geollm offers horizontal scaling (shards GEOLLM_SHARDS and journal
replication across nodes). The neighbor graph is persisted to disk in the file graph.bin
on a clean shutdown, checkpoint, and snapshot, so a normal restart brings the ready graph
up from the file and becomes ready to search within a fraction of a second (measured at
about 0.2 seconds for sixty thousand vectors, with search results before and after the
restart matching). For comparison, Qdrant and Manticore on the same set and bench come up
in 1.3 and 0.4 seconds. A full graph rebuild taking tens of seconds remains only for the
very first run on data without a graph and for recovery after an abnormal termination,
when the persisted graph has fallen behind (in which case only the missing tail is
appended) or is absent. The complete profile across all axes an architect needs is
summarized in docs/METRICS.md. The runs are reproduced by the binaries
bench_ann, loadgen and the scripts in the scripts directory.
Speed is not the main point. The main point is the deterministic resolution of contradictions by the freshness rule. This is verified not by words but by numbers on the real FactConsolidation task and the MQUAKE-CF data (a subject had an old answer to a relation, then a new one; the test checks whether the system returns the current version rather than the outdated one). The competitor chosen is a strong one, of the class of industrial memory systems: the bge-m3 encoder for retrieval and the gemma3 neural network as an arbiter, which is explicitly given both versions with timestamps and an instruction that a larger timestamp denotes the current record.
| On 400 adversarial cases | geollm (freshness rule) | bge-m3 plus gemma3 as arbiter |
|---|---|---|
| Share of current (correct) answers | about 100% | 3.8% |
| Share of outdated answers | 0.0% | 55.3% |
| Share of a fabricated third answer | 0.0% | 41.0% |
The neural-network arbiter, even when given both versions directly, in most cases falls
back to its internal knowledge and returns the outdated fact, and often fabricates a
third. The deterministic freshness gate does not consult world knowledge at all and does
not get confused, and it does not depend on the model size. The runs are reproduced by the
scripts scripts/bench_factcons_strong.py and scripts/bench_factconsolidation.py. On
pure retrieval quality on BEIR (NFCorpus) the embedded offline model gives nDCG@10 =
0.3106, on par with BM25 and almost level with bge-m3.
Multi-tenancy. Any operation accepts a tenant via the tenant field (for GET this is
?tenant=). One tenant's reads do not see another tenant's data in any projection:
semantic search, full-text, hybrid, exact key, history, trajectory, drift. Identical keys
in different scopes do not conflict. Forgetting another tenant's card by number is
impossible. A single-tenant deployment without the tenant field works as before and pays
nothing for multi-tenancy.
Full-text and hybrid search. /search_text ranks by the BM25 measure and catches
exact rare terms where vector proximity blurs them; /search_hybrid combines geometry and
full-text with a tunable weight alpha.
Vector quantization, a quarter of the memory. Enabled by a single variable
GEOLLM_QUANTIZATION=int8, like quantization_config in Qdrant. Vectors are held in
memory as signed eight-bit integers with a per-row scale, and the neighbor graph and search
work directly over the compressed representation. On mnist-784 the live quantized graph
gave recall@10 = 0.973 at 47 megabytes against 188 (a quarter of the memory). The
volume is visible in /stats.
Encryption at rest. GEOLLM_ENCRYPT_KEY enables AES-256-GCM: cards, tombstones,
vectors, and the key file are encrypted on disk, and no plaintext remains in the files.
Channel encryption and authentication. GEOLLM_TLS_CERT and GEOLLM_TLS_KEY bring up
HTTPS without an external proxy; GEOLLM_API_KEY requires the Authorization: Bearer
header.
Integrity schema. An optional layer (/schema): the requirement of a key and a value,
required fields, types, enumerated values; a violation is rejected with code 422.
Replication and scale. Sharding (GEOLLM_SHARDS) parallelizes writes, journal
replication on the Raft consensus spreads data across nodes (verified by Jepsen-style
scenarios). Cluster snapshot /snapshot, checkpoint without restart /checkpoint.
Memory as an agent tool (MCP). The binary geollm-mcp gives a language model memory
through a set of tools over the Model Context Protocol, embedded with a single line of
configuration.
The operations are native to memory, not SQL. The body and the response are JSON.
| Method and path | Purpose |
|---|---|
POST /write |
write a fact: text, value, optionally fact_key, source, tenant, payload |
POST /ask |
query by meaning: query, optionally k, as_of, tenant; a fact or abstention |
POST /search_text |
full-text search with BM25 ranking |
POST /search_hybrid |
hybrid of geometry and full-text, vector weight alpha |
POST /get |
exact key without geometry: fact_key, optionally as_of |
GET /history/{fact_key} |
version history of a fact |
GET /trajectory/{fact_key} |
the point's path across versions: steps, drift, suspicious jumps |
GET /drift?min=<threshold> |
facts with substitution: the step between versions below the threshold |
POST /forget |
forget a card by card_id |
POST /write_batch |
batch write of a package of ready vectors |
POST /train_keys |
train contrastive keys on domain triples |
POST /schema |
set the integrity schema; GET /schema returns the current one |
POST /checkpoint |
checkpoint without restart |
GET /snapshot_read |
the transaction number of a snapshot for repeatable reads (like as_of) |
POST /snapshot |
a consistent cluster snapshot into a directory |
GET /stats |
cards, versions, tombstones, tx, distribution, and compressed vector volume |
GET /metrics |
metrics in Prometheus format |
GET /health |
liveness check |
The as_of field in ask and get sets a view into the past: the transaction number
(from tx in /stats) at which the current version of a fact is computed.
A replaceable part, switched by the variable GEOLLM_EMBEDDER. Embedded (embedded, the
default in the image): the multilingual model multilingual-e5-small inside the image,
executed on the processor through the Candle framework, entirely in Rust, offline, without
external services. Lexical (lexical): character n-grams with signed hashing,
deterministic, the lightest. External neural (neural): the bge-m3 model through a local
ollama daemon.
A dependency-free Python client (clients/python/geollm.py) and a console client
geollm (write, ask, get, stats over the network service).
from geollm import Geollm
mem = Geollm("http://127.0.0.1:6333")
mem.write("loan interest rate", "21 percent", fact_key="rate")
answer = mem.ask("interest on the loan")
if not answer["abstained"]:
print(answer["value"])geollm is distributed under the Business Source License 1.1 (BSL 1.1), which is a source-available license, not an OSI-approved open source license. Non-commercial use (development, testing, evaluation, personal, and educational) is free. Production or commercial use requires a commercial license from the author. Each version automatically converts to the Apache License 2.0 on the Change Date specified in the file LICENSE.
Additionally, the license requires prominently crediting the author (Max Birkin) when using it and notifying him of the use. The author, as the sole rights holder, reserves the right to release future versions under other terms, including paid ones.
Commercial license and notice of use: mbirkin@internet.ru, maxbirkin@gmail.com, Telegram https://t.me/max_birkin.