lodedb 0.2.1__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. {lodedb-0.2.1 → lodedb-0.3.0}/PKG-INFO +43 -12
  2. {lodedb-0.2.1 → lodedb-0.3.0}/README.md +42 -11
  3. {lodedb-0.2.1 → lodedb-0.3.0}/pyproject.toml +1 -1
  4. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/__init__.py +1 -1
  5. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/core.py +607 -54
  6. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/runtime_policy.py +46 -0
  7. lodedb-0.3.0/src/lodedb/engine/wal_store.py +282 -0
  8. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/cli.py +16 -1
  9. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/db.py +32 -4
  10. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/server.py +10 -2
  11. {lodedb-0.2.1 → lodedb-0.3.0}/LICENSE +0 -0
  12. {lodedb-0.2.1 → lodedb-0.3.0}/NOTICE +0 -0
  13. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/__main__.py +0 -0
  14. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/config.py +0 -0
  15. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/__init__.py +0 -0
  16. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/_atomic_io.py +0 -0
  17. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/_commit_manifest.py +0 -0
  18. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/_filelock.py +0 -0
  19. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/_filter_plan.py +0 -0
  20. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/_lexical.py +0 -0
  21. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/_predicate.py +0 -0
  22. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/document_text_store.py +0 -0
  23. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/embedding_backends.py +0 -0
  24. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/gpu_turbovec.py +0 -0
  25. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/index.py +0 -0
  26. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/lexical_index_store.py +0 -0
  27. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/mps_turbovec.py +0 -0
  28. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/route_profiles.py +0 -0
  29. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/route_registry.py +0 -0
  30. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/state_journal_store.py +0 -0
  31. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/turbovec_delta_store.py +0 -0
  32. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/turbovec_index.py +0 -0
  33. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/turbovec_resident.py +0 -0
  34. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/graph/__init__.py +0 -0
  35. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/graph/_store.py +0 -0
  36. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/graph/knowledge_graph.py +0 -0
  37. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/__init__.py +0 -0
  38. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/backends.py +0 -0
  39. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/benchmark.py +0 -0
  40. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/doctor.py +0 -0
  41. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/integrations/__init__.py +0 -0
  42. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/integrations/langchain.py +0 -0
  43. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/integrations/llama_index.py +0 -0
  44. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/integrations/llama_index_graph.py +0 -0
  45. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/integrations/mem0.py +0 -0
  46. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/mcp_install.py +0 -0
  47. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/mcp_server.py +0 -0
  48. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/presets.py +0 -0
  49. {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/py.typed +0 -0
  50. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/Cargo.lock +0 -0
  51. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/Cargo.toml +0 -0
  52. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/Cargo.toml +0 -0
  53. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/README.md +0 -0
  54. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/build.rs +0 -0
  55. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/examples/dump_state.rs +0 -0
  56. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/examples/kernel_xtest.rs +0 -0
  57. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/codebook.rs +0 -0
  58. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/encode.rs +0 -0
  59. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/error.rs +0 -0
  60. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/id_map.rs +0 -0
  61. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/io.rs +0 -0
  62. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/lib.rs +0 -0
  63. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/pack.rs +0 -0
  64. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/rotation.rs +0 -0
  65. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/search.rs +0 -0
  66. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/codebook.rs +0 -0
  67. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/concurrent_search.rs +0 -0
  68. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/distortion.rs +0 -0
  69. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/encode.rs +0 -0
  70. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/encoded_rows.rs +0 -0
  71. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/filtering.rs +0 -0
  72. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/id_map.rs +0 -0
  73. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/input_validation.rs +0 -0
  74. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/io_versioning.rs +0 -0
  75. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/kernel_correctness.rs +0 -0
  76. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/lazy_init.rs +0 -0
  77. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/reconstruction.rs +0 -0
  78. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/rotation.rs +0 -0
  79. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/state_sequences.rs +0 -0
  80. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/swap_remove.rs +0 -0
  81. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/tqplus_calibration.rs +0 -0
  82. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/Cargo.toml +0 -0
  83. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/README.md +0 -0
  84. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/build.rs +0 -0
  85. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/python/turbovec/__init__.py +0 -0
  86. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/python/turbovec/_dedup.py +0 -0
  87. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/python/turbovec/_persist.py +0 -0
  88. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/python/turbovec/agno.py +0 -0
  89. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/python/turbovec/haystack.py +0 -0
  90. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/python/turbovec/langchain.py +0 -0
  91. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/python/turbovec/llama_index.py +0 -0
  92. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/src/lib.rs +0 -0
  93. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_agno.py +0 -0
  94. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_dedup.py +0 -0
  95. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_filtering.py +0 -0
  96. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_haystack.py +0 -0
  97. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_id_map.py +0 -0
  98. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_index.py +0 -0
  99. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_langchain.py +0 -0
  100. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_llama_index.py +0 -0
  101. {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_security.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lodedb
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: Programming Language :: Python :: 3 :: Only
@@ -36,23 +36,34 @@ Project-URL: Homepage, https://github.com/Egoist-Machines/LodeDB
36
36
  Project-URL: Issues, https://github.com/Egoist-Machines/LodeDB/issues
37
37
  Project-URL: Repository, https://github.com/Egoist-Machines/LodeDB
38
38
 
39
- # LodeDB
39
+ <h1 align="center">LodeDB</h1>
40
+ <p align="center">🔥 the <b>fastest</b> and <i>most compact</i> embedded vector database in the world 🌍</p>
40
41
 
41
42
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
42
43
  [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](pyproject.toml)
43
44
 
44
- **A fast, exact embedded vector database for local RAG: in-process, on-disk, no server.**
45
-
46
45
  *Built by [Egoist Machines, Inc.](https://egoistmachines.com) - efficient full-stack infrastructure
47
46
  for reliable AI systems.*
48
47
 
49
- Most embedded vector databases stop at the CPU. LodeDB runs the same on-disk index on the
50
- GPU when you have one: batched search hits **24k queries/sec on an A10 and 50k qps on an L40S**,
48
+ LodeDB is great for local RAG; it's _extremely fast_, exact, in-process, and on-disk. We're the **best drop-in** durable memory backend for **LangChain, LlamaIndex, and mem0** across all metrics that matter: the most
49
+ compact on disk, GPU-accelerated, and sub-millisecond on durable writes. Point any of them at
50
+ LodeDB instead of its default store. Over 17.5k documents, per framework default:
51
+
52
+ | vs the framework's default store | LangChain `InMemoryVectorStore` | LlamaIndex `SimpleVectorStore` | mem0 Qdrant |
53
+ |---|---|---|---|
54
+ | On-disk footprint | **7.2× smaller** (28 vs 199 MB) | **5.3× smaller** (28 vs 145 MB) | **4.6× smaller** (15 vs 70 MB) |
55
+ | Single-query p50 (CPU) | **~410× faster** (0.8 vs 345 ms) | **~500× faster** (0.9 vs 427 ms) | **~25× faster** (0.9 vs 23 ms) |
56
+ | Batched retrieval, 64 (GPU) | **~2,000×** (6,280 vs ~3 qps) | **~2,800×** (5,744 vs ~2 qps) | **~62×** (2,686 vs 43 qps) |
57
+ | Durable add of one memory | **~10,000× faster** (0.8 ms vs 8.6 s) | **~22,000× faster** (0.9 ms vs 19.3 s) | 0.9 vs 0.5 ms (both sub-ms) |
58
+
59
+ [Full benchmark, all backends
60
+ (FAISS, Chroma, Qdrant, LanceDB, sqlite-vec, pgvector), and method.](benchmarks/memory_integrations)
61
+
62
+ Most embedded vector databases stop at the CPU. LodeDB runs the same on-disk index **on the
63
+ GPU** when you have one: batched search hits *24k queries/sec on an A10 and 50k qps on an L40S*,
51
64
  2.8× to 4.8× the all-CPU ceiling, with recall unchanged. It also persists changed rows
52
65
  incrementally, so a commit stays **sub-millisecond even at 1M vectors**.
53
66
 
54
- Fast on a laptop. Faster on a GPU. Exact every time. Never phones home.
55
-
56
67
  - **GPU-resident batch search**: an fp16 copy of the index lives on the GPU, scored with a
57
68
  tiled GEMM plus a streaming top-k (`[gpu]`, Linux/CUDA). [How it works](#gpu-resident-index).
58
69
  - **O(changed) persistence**: commits only the rows that changed, 173× to 1,308× faster
@@ -196,7 +207,8 @@ db.search("E1234", k=5, mode="hybrid") # BM25 + RRF: surfaces it in the top-k
196
207
  db.search("E1234", k=5, mode="lexical") # BM25 ranking alone, no vector scan
197
208
  ```
198
209
 
199
- ### Prerequisites
210
+ <details>
211
+ <summary><b>Prerequisites</b></summary>
200
212
 
201
213
  `mode="hybrid"` and `mode="lexical"` build a BM25 index over your text, so they need a text
202
214
  source enabled when you open the database. `mode="vector"` (the default) needs nothing.
@@ -210,8 +222,10 @@ source enabled when you open the database. `mode="vector"` (the default) needs n
210
222
  Either source is enough and you can enable both. `store_text=True` is the default, so hybrid
211
223
  works out of the box. With neither source enabled, a hybrid or lexical query raises a clear,
212
224
  actionable error rather than silently degrading.
225
+ </details>
213
226
 
214
- ### How it works
227
+ <details>
228
+ <summary><b>How it works</b></summary>
215
229
 
216
230
  A `filter` constrains both rankers, so `mode="hybrid"` with a filter returns the true top-k of
217
231
  the matching subset. The vector half of a hybrid query runs on the same scan as `mode="vector"`,
@@ -219,8 +233,10 @@ including the GPU-resident batch scan that serves `search_many`; only the BM25 r
219
233
  fusion run on the CPU, and the vector kernel and on-disk format are untouched. The serving BM25
220
234
  index lives in memory and is maintained incrementally: a small mutation folds just the changed
221
235
  chunks into the existing index, so a single `add` never forces a full re-tokenization.
236
+ </details>
222
237
 
223
- ### Durable lexical index (`index_text=True`)
238
+ <details>
239
+ <summary><b>Durable lexical index (`index_text=True`)</b></summary>
224
240
 
225
241
  By default the BM25 index is rebuilt from the retained raw text, so it needs `store_text=True`
226
242
  and is re-tokenized on the first hybrid query after opening. Pass `index_text=True` to persist
@@ -241,6 +257,7 @@ db.close()
241
257
  reopened = LodeDB(path="./data", index_text=True, store_text=False)
242
258
  reopened.search("E1234", k=5, mode="hybrid") # works after reopen, rebuilt from persisted terms
243
259
  ```
260
+ </details>
244
261
 
245
262
  ## GPU-resident index
246
263
 
@@ -339,6 +356,9 @@ your host at `lodedb mcp`:
339
356
 
340
357
  ```bash
341
358
  pip install "lodedb[mcp]"
359
+
360
+ # for coding assistant:
361
+ lodedb mcp install --client claude-code # or: claude-desktop | cursor | lm-studio | codex | all
342
362
  ```
343
363
 
344
364
  It exposes `lodedb_add`, `lodedb_search`, `lodedb_remove`, and `lodedb_stats`, plus
@@ -351,7 +371,7 @@ the server with `--exclude-text` to return metrics only (this also withdraws `lo
351
371
  `--no-store-text` to keep no text on disk at all. `lodedb_stats` is always metrics-only and raw
352
372
  query text never leaves the process.
353
373
 
354
- ### One command
374
+ ### One command install
355
375
 
356
376
  `lodedb mcp install` writes the correct entry to a client's config for you, so you do not have to
357
377
  find the file or hand-write the JSON/TOML:
@@ -423,6 +443,17 @@ See [`examples/mcp_config.json`](examples/mcp_config.json) for a copy-paste star
423
443
  - **Durability is `fast` by default.** Commits are *atomic* but not fsync'd. Pass
424
444
  `durability="fsync"` (or `--durability fsync` / `LODEDB_DURABILITY=fsync`) to fsync each
425
445
  file and its directory on commit for power-loss durability, at some commit-throughput cost.
446
+ - **WAL commit by default for low-latency durable writes.** Each `add`/`remove` appends one
447
+ framed record to a `<key>.wal` log and a full generation is checkpointed periodically, so a
448
+ durable single add costs roughly an order of magnitude less than publishing a whole generation
449
+ per write, into the sqlite-vec/qdrant range (see the comparison up top). The WAL is replayed
450
+ crash-atomically on reopen (a half-written trailing record is discarded), every writable open
451
+ folds it into a clean committed generation, and `close()`/`persist()` checkpoint it. WAL is
452
+ single-writer: a concurrent `open_readonly` reader still loads a consistent committed
453
+ generation, but the last *checkpointed* one, not the writer's in-flight WAL. Pass
454
+ `commit_mode="generation"` (or `LODEDB_COMMIT_MODE=generation`) for the classic path that
455
+ publishes a crash-atomic, MVCC-readable generation on every write; pick it when many
456
+ out-of-process readers must see each write the instant it commits.
426
457
  - **Local filesystems only.** The OS advisory lock is unreliable on NFS/SMB.
427
458
 
428
459
  ## Limitations
@@ -1,20 +1,31 @@
1
- # LodeDB
1
+ <h1 align="center">LodeDB</h1>
2
+ <p align="center">🔥 the <b>fastest</b> and <i>most compact</i> embedded vector database in the world 🌍</p>
2
3
 
3
4
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
4
5
  [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](pyproject.toml)
5
6
 
6
- **A fast, exact embedded vector database for local RAG: in-process, on-disk, no server.**
7
-
8
7
  *Built by [Egoist Machines, Inc.](https://egoistmachines.com) - efficient full-stack infrastructure
9
8
  for reliable AI systems.*
10
9
 
11
- Most embedded vector databases stop at the CPU. LodeDB runs the same on-disk index on the
12
- GPU when you have one: batched search hits **24k queries/sec on an A10 and 50k qps on an L40S**,
10
+ LodeDB is great for local RAG; it's _extremely fast_, exact, in-process, and on-disk. We're the **best drop-in** durable memory backend for **LangChain, LlamaIndex, and mem0** across all metrics that matter: the most
11
+ compact on disk, GPU-accelerated, and sub-millisecond on durable writes. Point any of them at
12
+ LodeDB instead of its default store. Over 17.5k documents, per framework default:
13
+
14
+ | vs the framework's default store | LangChain `InMemoryVectorStore` | LlamaIndex `SimpleVectorStore` | mem0 Qdrant |
15
+ |---|---|---|---|
16
+ | On-disk footprint | **7.2× smaller** (28 vs 199 MB) | **5.3× smaller** (28 vs 145 MB) | **4.6× smaller** (15 vs 70 MB) |
17
+ | Single-query p50 (CPU) | **~410× faster** (0.8 vs 345 ms) | **~500× faster** (0.9 vs 427 ms) | **~25× faster** (0.9 vs 23 ms) |
18
+ | Batched retrieval, 64 (GPU) | **~2,000×** (6,280 vs ~3 qps) | **~2,800×** (5,744 vs ~2 qps) | **~62×** (2,686 vs 43 qps) |
19
+ | Durable add of one memory | **~10,000× faster** (0.8 ms vs 8.6 s) | **~22,000× faster** (0.9 ms vs 19.3 s) | 0.9 vs 0.5 ms (both sub-ms) |
20
+
21
+ [Full benchmark, all backends
22
+ (FAISS, Chroma, Qdrant, LanceDB, sqlite-vec, pgvector), and method.](benchmarks/memory_integrations)
23
+
24
+ Most embedded vector databases stop at the CPU. LodeDB runs the same on-disk index **on the
25
+ GPU** when you have one: batched search hits *24k queries/sec on an A10 and 50k qps on an L40S*,
13
26
  2.8× to 4.8× the all-CPU ceiling, with recall unchanged. It also persists changed rows
14
27
  incrementally, so a commit stays **sub-millisecond even at 1M vectors**.
15
28
 
16
- Fast on a laptop. Faster on a GPU. Exact every time. Never phones home.
17
-
18
29
  - **GPU-resident batch search**: an fp16 copy of the index lives on the GPU, scored with a
19
30
  tiled GEMM plus a streaming top-k (`[gpu]`, Linux/CUDA). [How it works](#gpu-resident-index).
20
31
  - **O(changed) persistence**: commits only the rows that changed, 173× to 1,308× faster
@@ -158,7 +169,8 @@ db.search("E1234", k=5, mode="hybrid") # BM25 + RRF: surfaces it in the top-k
158
169
  db.search("E1234", k=5, mode="lexical") # BM25 ranking alone, no vector scan
159
170
  ```
160
171
 
161
- ### Prerequisites
172
+ <details>
173
+ <summary><b>Prerequisites</b></summary>
162
174
 
163
175
  `mode="hybrid"` and `mode="lexical"` build a BM25 index over your text, so they need a text
164
176
  source enabled when you open the database. `mode="vector"` (the default) needs nothing.
@@ -172,8 +184,10 @@ source enabled when you open the database. `mode="vector"` (the default) needs n
172
184
  Either source is enough and you can enable both. `store_text=True` is the default, so hybrid
173
185
  works out of the box. With neither source enabled, a hybrid or lexical query raises a clear,
174
186
  actionable error rather than silently degrading.
187
+ </details>
175
188
 
176
- ### How it works
189
+ <details>
190
+ <summary><b>How it works</b></summary>
177
191
 
178
192
  A `filter` constrains both rankers, so `mode="hybrid"` with a filter returns the true top-k of
179
193
  the matching subset. The vector half of a hybrid query runs on the same scan as `mode="vector"`,
@@ -181,8 +195,10 @@ including the GPU-resident batch scan that serves `search_many`; only the BM25 r
181
195
  fusion run on the CPU, and the vector kernel and on-disk format are untouched. The serving BM25
182
196
  index lives in memory and is maintained incrementally: a small mutation folds just the changed
183
197
  chunks into the existing index, so a single `add` never forces a full re-tokenization.
198
+ </details>
184
199
 
185
- ### Durable lexical index (`index_text=True`)
200
+ <details>
201
+ <summary><b>Durable lexical index (`index_text=True`)</b></summary>
186
202
 
187
203
  By default the BM25 index is rebuilt from the retained raw text, so it needs `store_text=True`
188
204
  and is re-tokenized on the first hybrid query after opening. Pass `index_text=True` to persist
@@ -203,6 +219,7 @@ db.close()
203
219
  reopened = LodeDB(path="./data", index_text=True, store_text=False)
204
220
  reopened.search("E1234", k=5, mode="hybrid") # works after reopen, rebuilt from persisted terms
205
221
  ```
222
+ </details>
206
223
 
207
224
  ## GPU-resident index
208
225
 
@@ -301,6 +318,9 @@ your host at `lodedb mcp`:
301
318
 
302
319
  ```bash
303
320
  pip install "lodedb[mcp]"
321
+
322
+ # for coding assistant:
323
+ lodedb mcp install --client claude-code # or: claude-desktop | cursor | lm-studio | codex | all
304
324
  ```
305
325
 
306
326
  It exposes `lodedb_add`, `lodedb_search`, `lodedb_remove`, and `lodedb_stats`, plus
@@ -313,7 +333,7 @@ the server with `--exclude-text` to return metrics only (this also withdraws `lo
313
333
  `--no-store-text` to keep no text on disk at all. `lodedb_stats` is always metrics-only and raw
314
334
  query text never leaves the process.
315
335
 
316
- ### One command
336
+ ### One command install
317
337
 
318
338
  `lodedb mcp install` writes the correct entry to a client's config for you, so you do not have to
319
339
  find the file or hand-write the JSON/TOML:
@@ -385,6 +405,17 @@ See [`examples/mcp_config.json`](examples/mcp_config.json) for a copy-paste star
385
405
  - **Durability is `fast` by default.** Commits are *atomic* but not fsync'd. Pass
386
406
  `durability="fsync"` (or `--durability fsync` / `LODEDB_DURABILITY=fsync`) to fsync each
387
407
  file and its directory on commit for power-loss durability, at some commit-throughput cost.
408
+ - **WAL commit by default for low-latency durable writes.** Each `add`/`remove` appends one
409
+ framed record to a `<key>.wal` log and a full generation is checkpointed periodically, so a
410
+ durable single add costs roughly an order of magnitude less than publishing a whole generation
411
+ per write, into the sqlite-vec/qdrant range (see the comparison up top). The WAL is replayed
412
+ crash-atomically on reopen (a half-written trailing record is discarded), every writable open
413
+ folds it into a clean committed generation, and `close()`/`persist()` checkpoint it. WAL is
414
+ single-writer: a concurrent `open_readonly` reader still loads a consistent committed
415
+ generation, but the last *checkpointed* one, not the writer's in-flight WAL. Pass
416
+ `commit_mode="generation"` (or `LODEDB_COMMIT_MODE=generation`) for the classic path that
417
+ publishes a crash-atomic, MVCC-readable generation on every write; pick it when many
418
+ out-of-process readers must see each write the instant it commits.
388
419
  - **Local filesystems only.** The OS advisory lock is unreliable on NFS/SMB.
389
420
 
390
421
  ## Limitations
@@ -5,7 +5,7 @@ build-backend = "maturin"
5
5
  [project]
6
6
  name = "lodedb"
7
7
  # Static (maturin reads it here); keep in sync with src/lodedb/__init__.py __version__.
8
- version = "0.2.1"
8
+ version = "0.3.0"
9
9
  description = "Local-first, privacy-by-default embedded vector database. Your data never leaves your machine."
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.11"
@@ -19,7 +19,7 @@ from lodedb.local import (
19
19
  from lodedb.local.cli import app, main
20
20
 
21
21
  # Keep in sync with `version` in pyproject.toml (the release workflow asserts they match).
22
- __version__ = "0.2.1"
22
+ __version__ = "0.3.0"
23
23
 
24
24
  __all__ = [
25
25
  "LOCAL_MODEL_PRESETS",