lodedb 0.2.0__tar.gz → 0.2.1__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 (103) hide show
  1. {lodedb-0.2.0 → lodedb-0.2.1}/PKG-INFO +121 -7
  2. {lodedb-0.2.0 → lodedb-0.2.1}/README.md +116 -6
  3. {lodedb-0.2.0 → lodedb-0.2.1}/pyproject.toml +5 -1
  4. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/__init__.py +1 -1
  5. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/core.py +115 -10
  6. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/index.py +36 -0
  7. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/graph/knowledge_graph.py +60 -14
  8. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/local/backends.py +16 -0
  9. lodedb-0.2.1/src/lodedb/local/cli.py +518 -0
  10. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/local/db.py +41 -4
  11. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/local/doctor.py +48 -0
  12. lodedb-0.2.1/src/lodedb/local/integrations/__init__.py +17 -0
  13. lodedb-0.2.1/src/lodedb/local/integrations/llama_index.py +468 -0
  14. lodedb-0.2.1/src/lodedb/local/integrations/llama_index_graph.py +404 -0
  15. lodedb-0.2.1/src/lodedb/local/integrations/mem0.py +531 -0
  16. lodedb-0.2.1/src/lodedb/local/mcp_install.py +587 -0
  17. lodedb-0.2.1/src/lodedb/local/mcp_server.py +220 -0
  18. lodedb-0.2.0/src/lodedb/local/cli.py +0 -267
  19. lodedb-0.2.0/src/lodedb/local/integrations/__init__.py +0 -10
  20. lodedb-0.2.0/src/lodedb/local/mcp_server.py +0 -158
  21. {lodedb-0.2.0 → lodedb-0.2.1}/LICENSE +0 -0
  22. {lodedb-0.2.0 → lodedb-0.2.1}/NOTICE +0 -0
  23. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/__main__.py +0 -0
  24. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/config.py +0 -0
  25. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/__init__.py +0 -0
  26. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/_atomic_io.py +0 -0
  27. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/_commit_manifest.py +0 -0
  28. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/_filelock.py +0 -0
  29. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/_filter_plan.py +0 -0
  30. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/_lexical.py +0 -0
  31. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/_predicate.py +0 -0
  32. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/document_text_store.py +0 -0
  33. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/embedding_backends.py +0 -0
  34. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/gpu_turbovec.py +0 -0
  35. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/lexical_index_store.py +0 -0
  36. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/mps_turbovec.py +0 -0
  37. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/route_profiles.py +0 -0
  38. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/route_registry.py +0 -0
  39. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/runtime_policy.py +0 -0
  40. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/state_journal_store.py +0 -0
  41. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/turbovec_delta_store.py +0 -0
  42. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/turbovec_index.py +0 -0
  43. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/engine/turbovec_resident.py +0 -0
  44. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/graph/__init__.py +0 -0
  45. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/graph/_store.py +0 -0
  46. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/local/__init__.py +0 -0
  47. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/local/benchmark.py +0 -0
  48. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/local/integrations/langchain.py +0 -0
  49. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/local/presets.py +0 -0
  50. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/local/server.py +0 -0
  51. {lodedb-0.2.0 → lodedb-0.2.1}/src/lodedb/py.typed +0 -0
  52. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/Cargo.lock +0 -0
  53. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/Cargo.toml +0 -0
  54. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/Cargo.toml +0 -0
  55. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/README.md +0 -0
  56. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/build.rs +0 -0
  57. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/examples/dump_state.rs +0 -0
  58. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/examples/kernel_xtest.rs +0 -0
  59. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/codebook.rs +0 -0
  60. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/encode.rs +0 -0
  61. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/error.rs +0 -0
  62. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/id_map.rs +0 -0
  63. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/io.rs +0 -0
  64. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/lib.rs +0 -0
  65. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/pack.rs +0 -0
  66. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/rotation.rs +0 -0
  67. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/src/search.rs +0 -0
  68. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/codebook.rs +0 -0
  69. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/concurrent_search.rs +0 -0
  70. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/distortion.rs +0 -0
  71. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/encode.rs +0 -0
  72. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/encoded_rows.rs +0 -0
  73. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/filtering.rs +0 -0
  74. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/id_map.rs +0 -0
  75. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/input_validation.rs +0 -0
  76. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/io_versioning.rs +0 -0
  77. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/kernel_correctness.rs +0 -0
  78. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/lazy_init.rs +0 -0
  79. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/reconstruction.rs +0 -0
  80. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/rotation.rs +0 -0
  81. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/state_sequences.rs +0 -0
  82. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/swap_remove.rs +0 -0
  83. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec/tests/tqplus_calibration.rs +0 -0
  84. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/Cargo.toml +0 -0
  85. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/README.md +0 -0
  86. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/build.rs +0 -0
  87. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/__init__.py +0 -0
  88. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/_dedup.py +0 -0
  89. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/_persist.py +0 -0
  90. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/agno.py +0 -0
  91. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/haystack.py +0 -0
  92. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/langchain.py +0 -0
  93. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/python/turbovec/llama_index.py +0 -0
  94. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/src/lib.rs +0 -0
  95. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_agno.py +0 -0
  96. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_dedup.py +0 -0
  97. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_filtering.py +0 -0
  98. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_haystack.py +0 -0
  99. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_id_map.py +0 -0
  100. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_index.py +0 -0
  101. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_langchain.py +0 -0
  102. {lodedb-0.2.0 → lodedb-0.2.1}/third_party/turbovec/turbovec-python/tests/test_llama_index.py +0 -0
  103. {lodedb-0.2.0 → lodedb-0.2.1}/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.0
3
+ Version: 0.2.1
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: Programming Language :: Python :: 3 :: Only
@@ -14,11 +14,15 @@ Requires-Dist: pytest>=8.3.0 ; extra == 'dev'
14
14
  Requires-Dist: ruff==0.15.17 ; extra == 'dev'
15
15
  Requires-Dist: cupy-cuda12x>=13.0.0 ; platform_system == 'Linux' and extra == 'gpu'
16
16
  Requires-Dist: langchain-core>=0.3.0 ; extra == 'langchain'
17
+ Requires-Dist: llama-index-core>=0.11.0 ; extra == 'llama-index'
17
18
  Requires-Dist: mcp>=1.0.0 ; extra == 'mcp'
19
+ Requires-Dist: mem0ai>=2.0.0 ; extra == 'mem0'
18
20
  Provides-Extra: dev
19
21
  Provides-Extra: gpu
20
22
  Provides-Extra: langchain
23
+ Provides-Extra: llama-index
21
24
  Provides-Extra: mcp
25
+ Provides-Extra: mem0
22
26
  License-File: LICENSE
23
27
  License-File: NOTICE
24
28
  Summary: Local-first, privacy-by-default embedded vector database. Your data never leaves your machine.
@@ -62,8 +66,9 @@ Fast on a laptop. Faster on a GPU. Exact every time. Never phones home.
62
66
  - **Private by default**: text, ids, and vectors stay local; telemetry is metrics-only
63
67
  (counts, bytes, latency), never raw payloads.
64
68
  - **Local embeddings**: `sentence-transformers` on CUDA, MPS, or CPU.
65
- - **Batteries included**: a `lodedb` CLI, a loopback dev server, an MCP server, and a
66
- LangChain `VectorStore` adapter.
69
+ - **Batteries included**: a `lodedb` CLI, a loopback dev server, an
70
+ [MCP server](#use-as-an-mcp-server), and LangChain, LlamaIndex, and mem0 adapters
71
+ (`VectorStore`s, plus a LlamaIndex `PropertyGraphStore`).
67
72
 
68
73
  > 🏢 **Enterprise** The LodeDB core is Apache-2.0 and free to use. Enterprise licensing is
69
74
  > available for commercial support, managed and at-scale serving, and on-prem / BYOC
@@ -80,10 +85,43 @@ Python 3.11+, and bundle the TurboVec (Rust) core, so there's nothing to compile
80
85
  the install with `lodedb doctor`. Optional extras:
81
86
 
82
87
  ```bash
83
- pip install "lodedb[gpu]" # GPU-resident scan (Linux/CUDA)
84
- pip install "lodedb[mcp,langchain]" # MCP server + LangChain adapter
88
+ pip install "lodedb[gpu]" # GPU-resident scan (Linux/CUDA)
89
+ pip install "lodedb[mcp,langchain,llama-index,mem0]" # MCP server + LangChain/LlamaIndex/mem0 adapters
85
90
  ```
86
91
 
92
+ Using LodeDB as memory for a coding assistant? After installing the `mcp` extra, register its
93
+ server in one step (details under [Use as an MCP server](#use-as-an-mcp-server)):
94
+
95
+ ```bash
96
+ lodedb mcp install --client claude-code # or: claude-desktop | cursor | lm-studio | codex | all
97
+ ```
98
+
99
+ <details>
100
+ <summary><b>Windows: NVIDIA GPU embeddings</b></summary>
101
+
102
+ On Windows, PyPI serves the CPU-only PyTorch build by default, so `pip install lodedb` (and
103
+ `uv`) leave embeddings on the CPU even on a CUDA machine, and no package metadata can override
104
+ which torch wheel pip resolves. `lodedb doctor` detects this and prints the fix; `lodedb doctor
105
+ --fix` reinstalls the CUDA build for you:
106
+
107
+ ```bash
108
+ lodedb doctor # flags a CPU-only PyTorch on Windows and prints the command
109
+ lodedb doctor --fix # reinstalls the CUDA build so embeddings use your NVIDIA GPU
110
+ ```
111
+
112
+ Or reinstall manually, picking the index for your CUDA version (`cu121`, `cu124`, ...) from the
113
+ [PyTorch install guide](https://pytorch.org/get-started/locally/):
114
+
115
+ ```bash
116
+ pip install torch --force-reinstall --no-deps --index-url https://download.pytorch.org/whl/cu121
117
+ uv pip install torch --reinstall --index-url https://download.pytorch.org/whl/cu121 # with uv
118
+ ```
119
+
120
+ This is Windows-only: the default Linux PyPI wheel already bundles CUDA, and macOS uses CPU
121
+ or MPS.
122
+
123
+ </details>
124
+
87
125
  <details>
88
126
  <summary><b>Build from source</b> (contributors, or a platform without a wheel)</summary>
89
127
 
@@ -93,8 +131,8 @@ Linux). [uv](https://docs.astral.sh/uv/) builds and bundles the core for you:
93
131
  ```bash
94
132
  git clone https://github.com/Egoist-Machines/LodeDB && cd LodeDB
95
133
  uv sync # builds + bundles the TurboVec core via maturin
96
- uv sync --extra mcp --extra langchain # + MCP server, LangChain adapter
97
- uv sync --extra gpu # + GPU-resident scan (Linux/CUDA)
134
+ uv sync --extra mcp --extra langchain --extra llama-index --extra mem0 # + MCP server, adapters
135
+ uv sync --extra gpu # + GPU-resident scan (Linux/CUDA)
98
136
  ```
99
137
 
100
138
  Run with `uv run` (e.g. `uv run lodedb doctor`).
@@ -292,6 +330,82 @@ lodedb mcp # stdio MCP server for agent memory
292
330
  lodedb benchmark # local, metrics-only benchmark
293
331
  ```
294
332
 
333
+ ## Use as an MCP server
334
+
335
+ LodeDB ships a [Model Context Protocol](https://modelcontextprotocol.io) server, so an agent
336
+ can use a local on-disk database as long-term memory or a RAG store. It runs over stdio, adds
337
+ no storage logic of its own, and your data stays on the machine. Install the extra and point
338
+ your host at `lodedb mcp`:
339
+
340
+ ```bash
341
+ pip install "lodedb[mcp]"
342
+ ```
343
+
344
+ It exposes `lodedb_add`, `lodedb_search`, `lodedb_remove`, and `lodedb_stats`, plus
345
+ `lodedb_get` when text is available. `lodedb_search` returns each hit's stored text alongside
346
+ the score, id, and metadata, so a model can rank and answer in a single call rather than
347
+ chaining a follow-up lookup. It runs [hybrid search](#hybrid-search) (BM25 lexical + vector,
348
+ fused with RRF) by default when text is retained, so exact tokens like error codes and serials
349
+ surface next to semantic matches; with no text retained it falls back to a vector scan. Start
350
+ the server with `--exclude-text` to return metrics only (this also withdraws `lodedb_get`), or
351
+ `--no-store-text` to keep no text on disk at all. `lodedb_stats` is always metrics-only and raw
352
+ query text never leaves the process.
353
+
354
+ ### One command
355
+
356
+ `lodedb mcp install` writes the correct entry to a client's config for you, so you do not have to
357
+ find the file or hand-write the JSON/TOML:
358
+
359
+ ```bash
360
+ lodedb mcp install --client claude-code # or: claude-desktop | cursor | lm-studio | codex | all
361
+ lodedb mcp install --client cursor --path ./data --model bge
362
+ ```
363
+
364
+ It resolves the launch command for your environment, so `command` and `args` are correct even when
365
+ `lodedb` is not on `PATH` (it falls back to the `uv run --project ...` form, then an absolute path to
366
+ the entry point), and it resolves `--path` to an absolute path so the server opens the right
367
+ directory wherever the client starts it. The edit is idempotent (an existing `lodedb` entry is
368
+ updated, never duplicated) and never touches other servers in the file. It passes through the same
369
+ options as `lodedb mcp` (`--path`, `--model`, `--device`, `--exclude-text`, `--no-store-text`);
370
+ `--dry-run` prints the entry and target file without writing, and `lodedb mcp uninstall --client
371
+ <client>` removes it again. Override the config location with `--config <path>` (Claude Desktop and
372
+ LM Studio paths differ per OS), and use `--project <dir>` to write Cursor's project-level
373
+ `.cursor/mcp.json`. For Claude Code it runs `claude mcp add`; for the others it edits the config file
374
+ directly.
375
+
376
+ <details>
377
+ <summary><b>Register by hand</b> (Claude Code, Claude Desktop, Cursor, LM Studio, Codex)</summary>
378
+
379
+ The `lodedb` command must be on the host's `PATH`; if you installed into a virtual environment
380
+ (including a `uv` project) where it isn't, use the `uv run` form at the bottom.
381
+
382
+ **Claude Code, Claude Desktop, Cursor, LM Studio**: add the stdio entry to the host's MCP
383
+ config (`claude_desktop_config.json`, `.cursor/mcp.json`, or LM Studio's `mcp.json`), or run
384
+ `claude mcp add lodedb -- lodedb mcp --path ./data`:
385
+
386
+ ```json
387
+ { "mcpServers": { "lodedb": { "command": "lodedb", "args": ["mcp", "--path", "./data"] } } }
388
+ ```
389
+
390
+ **Codex**: add to `~/.codex/config.toml`:
391
+
392
+ ```toml
393
+ [mcp_servers.lodedb]
394
+ command = "lodedb"
395
+ args = ["mcp", "--path", "./data"]
396
+ ```
397
+
398
+ **From a virtual environment (uv)**, when `lodedb` is not on `PATH`:
399
+
400
+ ```json
401
+ { "mcpServers": { "lodedb": { "command": "uv",
402
+ "args": ["run", "--project", "/path/to/LodeDB", "lodedb", "mcp", "--path", "/path/to/data"] } } }
403
+ ```
404
+
405
+ See [`examples/mcp_config.json`](examples/mcp_config.json) for a copy-paste starting point.
406
+
407
+ </details>
408
+
295
409
  ## Concurrency & durability
296
410
 
297
411
  - **Single writer, many readers, per path.** One handle holds the path open for *writing* at
@@ -28,8 +28,9 @@ Fast on a laptop. Faster on a GPU. Exact every time. Never phones home.
28
28
  - **Private by default**: text, ids, and vectors stay local; telemetry is metrics-only
29
29
  (counts, bytes, latency), never raw payloads.
30
30
  - **Local embeddings**: `sentence-transformers` on CUDA, MPS, or CPU.
31
- - **Batteries included**: a `lodedb` CLI, a loopback dev server, an MCP server, and a
32
- LangChain `VectorStore` adapter.
31
+ - **Batteries included**: a `lodedb` CLI, a loopback dev server, an
32
+ [MCP server](#use-as-an-mcp-server), and LangChain, LlamaIndex, and mem0 adapters
33
+ (`VectorStore`s, plus a LlamaIndex `PropertyGraphStore`).
33
34
 
34
35
  > 🏢 **Enterprise** The LodeDB core is Apache-2.0 and free to use. Enterprise licensing is
35
36
  > available for commercial support, managed and at-scale serving, and on-prem / BYOC
@@ -46,10 +47,43 @@ Python 3.11+, and bundle the TurboVec (Rust) core, so there's nothing to compile
46
47
  the install with `lodedb doctor`. Optional extras:
47
48
 
48
49
  ```bash
49
- pip install "lodedb[gpu]" # GPU-resident scan (Linux/CUDA)
50
- pip install "lodedb[mcp,langchain]" # MCP server + LangChain adapter
50
+ pip install "lodedb[gpu]" # GPU-resident scan (Linux/CUDA)
51
+ pip install "lodedb[mcp,langchain,llama-index,mem0]" # MCP server + LangChain/LlamaIndex/mem0 adapters
51
52
  ```
52
53
 
54
+ Using LodeDB as memory for a coding assistant? After installing the `mcp` extra, register its
55
+ server in one step (details under [Use as an MCP server](#use-as-an-mcp-server)):
56
+
57
+ ```bash
58
+ lodedb mcp install --client claude-code # or: claude-desktop | cursor | lm-studio | codex | all
59
+ ```
60
+
61
+ <details>
62
+ <summary><b>Windows: NVIDIA GPU embeddings</b></summary>
63
+
64
+ On Windows, PyPI serves the CPU-only PyTorch build by default, so `pip install lodedb` (and
65
+ `uv`) leave embeddings on the CPU even on a CUDA machine, and no package metadata can override
66
+ which torch wheel pip resolves. `lodedb doctor` detects this and prints the fix; `lodedb doctor
67
+ --fix` reinstalls the CUDA build for you:
68
+
69
+ ```bash
70
+ lodedb doctor # flags a CPU-only PyTorch on Windows and prints the command
71
+ lodedb doctor --fix # reinstalls the CUDA build so embeddings use your NVIDIA GPU
72
+ ```
73
+
74
+ Or reinstall manually, picking the index for your CUDA version (`cu121`, `cu124`, ...) from the
75
+ [PyTorch install guide](https://pytorch.org/get-started/locally/):
76
+
77
+ ```bash
78
+ pip install torch --force-reinstall --no-deps --index-url https://download.pytorch.org/whl/cu121
79
+ uv pip install torch --reinstall --index-url https://download.pytorch.org/whl/cu121 # with uv
80
+ ```
81
+
82
+ This is Windows-only: the default Linux PyPI wheel already bundles CUDA, and macOS uses CPU
83
+ or MPS.
84
+
85
+ </details>
86
+
53
87
  <details>
54
88
  <summary><b>Build from source</b> (contributors, or a platform without a wheel)</summary>
55
89
 
@@ -59,8 +93,8 @@ Linux). [uv](https://docs.astral.sh/uv/) builds and bundles the core for you:
59
93
  ```bash
60
94
  git clone https://github.com/Egoist-Machines/LodeDB && cd LodeDB
61
95
  uv sync # builds + bundles the TurboVec core via maturin
62
- uv sync --extra mcp --extra langchain # + MCP server, LangChain adapter
63
- uv sync --extra gpu # + GPU-resident scan (Linux/CUDA)
96
+ uv sync --extra mcp --extra langchain --extra llama-index --extra mem0 # + MCP server, adapters
97
+ uv sync --extra gpu # + GPU-resident scan (Linux/CUDA)
64
98
  ```
65
99
 
66
100
  Run with `uv run` (e.g. `uv run lodedb doctor`).
@@ -258,6 +292,82 @@ lodedb mcp # stdio MCP server for agent memory
258
292
  lodedb benchmark # local, metrics-only benchmark
259
293
  ```
260
294
 
295
+ ## Use as an MCP server
296
+
297
+ LodeDB ships a [Model Context Protocol](https://modelcontextprotocol.io) server, so an agent
298
+ can use a local on-disk database as long-term memory or a RAG store. It runs over stdio, adds
299
+ no storage logic of its own, and your data stays on the machine. Install the extra and point
300
+ your host at `lodedb mcp`:
301
+
302
+ ```bash
303
+ pip install "lodedb[mcp]"
304
+ ```
305
+
306
+ It exposes `lodedb_add`, `lodedb_search`, `lodedb_remove`, and `lodedb_stats`, plus
307
+ `lodedb_get` when text is available. `lodedb_search` returns each hit's stored text alongside
308
+ the score, id, and metadata, so a model can rank and answer in a single call rather than
309
+ chaining a follow-up lookup. It runs [hybrid search](#hybrid-search) (BM25 lexical + vector,
310
+ fused with RRF) by default when text is retained, so exact tokens like error codes and serials
311
+ surface next to semantic matches; with no text retained it falls back to a vector scan. Start
312
+ the server with `--exclude-text` to return metrics only (this also withdraws `lodedb_get`), or
313
+ `--no-store-text` to keep no text on disk at all. `lodedb_stats` is always metrics-only and raw
314
+ query text never leaves the process.
315
+
316
+ ### One command
317
+
318
+ `lodedb mcp install` writes the correct entry to a client's config for you, so you do not have to
319
+ find the file or hand-write the JSON/TOML:
320
+
321
+ ```bash
322
+ lodedb mcp install --client claude-code # or: claude-desktop | cursor | lm-studio | codex | all
323
+ lodedb mcp install --client cursor --path ./data --model bge
324
+ ```
325
+
326
+ It resolves the launch command for your environment, so `command` and `args` are correct even when
327
+ `lodedb` is not on `PATH` (it falls back to the `uv run --project ...` form, then an absolute path to
328
+ the entry point), and it resolves `--path` to an absolute path so the server opens the right
329
+ directory wherever the client starts it. The edit is idempotent (an existing `lodedb` entry is
330
+ updated, never duplicated) and never touches other servers in the file. It passes through the same
331
+ options as `lodedb mcp` (`--path`, `--model`, `--device`, `--exclude-text`, `--no-store-text`);
332
+ `--dry-run` prints the entry and target file without writing, and `lodedb mcp uninstall --client
333
+ <client>` removes it again. Override the config location with `--config <path>` (Claude Desktop and
334
+ LM Studio paths differ per OS), and use `--project <dir>` to write Cursor's project-level
335
+ `.cursor/mcp.json`. For Claude Code it runs `claude mcp add`; for the others it edits the config file
336
+ directly.
337
+
338
+ <details>
339
+ <summary><b>Register by hand</b> (Claude Code, Claude Desktop, Cursor, LM Studio, Codex)</summary>
340
+
341
+ The `lodedb` command must be on the host's `PATH`; if you installed into a virtual environment
342
+ (including a `uv` project) where it isn't, use the `uv run` form at the bottom.
343
+
344
+ **Claude Code, Claude Desktop, Cursor, LM Studio**: add the stdio entry to the host's MCP
345
+ config (`claude_desktop_config.json`, `.cursor/mcp.json`, or LM Studio's `mcp.json`), or run
346
+ `claude mcp add lodedb -- lodedb mcp --path ./data`:
347
+
348
+ ```json
349
+ { "mcpServers": { "lodedb": { "command": "lodedb", "args": ["mcp", "--path", "./data"] } } }
350
+ ```
351
+
352
+ **Codex**: add to `~/.codex/config.toml`:
353
+
354
+ ```toml
355
+ [mcp_servers.lodedb]
356
+ command = "lodedb"
357
+ args = ["mcp", "--path", "./data"]
358
+ ```
359
+
360
+ **From a virtual environment (uv)**, when `lodedb` is not on `PATH`:
361
+
362
+ ```json
363
+ { "mcpServers": { "lodedb": { "command": "uv",
364
+ "args": ["run", "--project", "/path/to/LodeDB", "lodedb", "mcp", "--path", "/path/to/data"] } } }
365
+ ```
366
+
367
+ See [`examples/mcp_config.json`](examples/mcp_config.json) for a copy-paste starting point.
368
+
369
+ </details>
370
+
261
371
  ## Concurrency & durability
262
372
 
263
373
  - **Single writer, many readers, per path.** One handle holds the path open for *writing* at
@@ -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.0"
8
+ version = "0.2.1"
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"
@@ -52,6 +52,10 @@ gpu = ["cupy-cuda12x>=13.0.0; platform_system == 'Linux'"]
52
52
  mcp = ["mcp>=1.0.0"]
53
53
  # Optional LangChain VectorStore adapter.
54
54
  langchain = ["langchain-core>=0.3.0"]
55
+ # Optional LlamaIndex VectorStore adapter.
56
+ llama-index = ["llama-index-core>=0.11.0"]
57
+ # Optional mem0 vector-store adapter.
58
+ mem0 = ["mem0ai>=2.0.0"]
55
59
  # ruff is pinned (not floored) so `ruff format`/`ruff check` are reproducible across
56
60
  # contributors and CI — the formatter's output can change between ruff releases.
57
61
  dev = ["pytest>=8.3.0", "ruff==0.15.17"]
@@ -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.0"
22
+ __version__ = "0.2.1"
23
23
 
24
24
  __all__ = [
25
25
  "LOCAL_MODEL_PRESETS",
@@ -188,15 +188,18 @@ class EngineVectorDocument:
188
188
  """Stores one pre-embedded document supplied to the local engine (vector-in).
189
189
 
190
190
  Unlike :class:`EngineDocument`, the caller supplies the embedding vector
191
- directly and there is no text: the engine skips chunking and embedding and
192
- stores the vector as a single chunk. The vector must match the index's
193
- ``native_dim`` and is expected to be L2-normalized (the SDK normalizes by
194
- default) so cosine scoring stays comparable with the text path.
191
+ directly: the engine skips chunking and embedding and stores the vector as a
192
+ single chunk. ``text`` is optional retained payload text only; when present
193
+ and raw-text storage is enabled it is stored in the dedicated text sidecar,
194
+ never in the redacted snapshot or metadata. The vector must match the
195
+ index's ``native_dim`` and is expected to be L2-normalized (the SDK
196
+ normalizes by default) so cosine scoring stays comparable with the text path.
195
197
  """
196
198
 
197
199
  document_id: str
198
200
  vector: tuple[float, ...]
199
201
  metadata: dict[str, str] = field(default_factory=dict)
202
+ text: str | None = None
200
203
 
201
204
 
202
205
  @dataclass(frozen=True)
@@ -1107,10 +1110,12 @@ class LodeEngine:
1107
1110
  """Upserts pre-embedded documents (vector-in), skipping chunking/embedding.
1108
1111
 
1109
1112
  The caller supplies embedding vectors directly; each is stored as a
1110
- single chunk and **no** raw text is captured. Vectors must match the
1111
- index ``native_dim``. Reuses the same atomic-commit + O(changed) direct
1112
- TurboVec sync path as :meth:`upsert_documents`, so vector-in inherits
1113
- crash-atomic commits and incremental persistence unchanged.
1113
+ single chunk. If a vector document carries optional ``text``, that text
1114
+ is captured only in the raw-text sidecar when enabled; it is never
1115
+ embedded, chunked, or written to redacted metadata. Vectors must match
1116
+ the index ``native_dim``. Reuses the same atomic-commit + O(changed)
1117
+ direct TurboVec sync path as :meth:`upsert_documents`, so vector-in
1118
+ inherits crash-atomic commits and incremental persistence unchanged.
1114
1119
  """
1115
1120
 
1116
1121
  state = self._index_for_context(context, index_id=index_id, operation="upsert_vectors")
@@ -1124,8 +1129,7 @@ class LodeEngine:
1124
1129
  )
1125
1130
  if isinstance(result, EngineResponse):
1126
1131
  return result
1127
- # Deliberately no _capture_document_text: vector-in documents carry no
1128
- # raw text, so get()/get_text() return None for them by design.
1132
+ self._capture_vector_document_text(state, vectors)
1129
1133
  self._finalize_document_ingest(
1130
1134
  context=context,
1131
1135
  state=state,
@@ -1140,6 +1144,92 @@ class LodeEngine:
1140
1144
  },
1141
1145
  )
1142
1146
 
1147
+ @_synchronized
1148
+ def update_document_payload(
1149
+ self,
1150
+ *,
1151
+ context: EngineRequestContext,
1152
+ document_id: str,
1153
+ metadata: Mapping[str, Any] | None = None,
1154
+ text: str | None = None,
1155
+ clear_text: bool = False,
1156
+ index_id: str | None = None,
1157
+ ) -> EngineResponse:
1158
+ """Updates an existing document's metadata and/or retained raw text.
1159
+
1160
+ This is intentionally narrow: it does not alter vectors or chunk
1161
+ membership, and it never embeds. It exists for adapters that maintain
1162
+ host-framework payloads beside precomputed vectors, such as mem0's
1163
+ payload-only entity-link updates.
1164
+ """
1165
+
1166
+ state = self._index_for_context(
1167
+ context, index_id=index_id, operation="update_document_payload"
1168
+ )
1169
+ if isinstance(state, EngineResponse):
1170
+ return state
1171
+ document_id = str(document_id).strip()
1172
+ if not document_id:
1173
+ return self._error(
1174
+ 400, "document_id is required", "update_document_payload", context.client_id
1175
+ )
1176
+ if document_id not in state.document_hashes:
1177
+ return self._error(
1178
+ 404, "document not found", "update_document_payload", context.client_id
1179
+ )
1180
+
1181
+ changed = False
1182
+ if metadata is not None:
1183
+ try:
1184
+ state.document_metadata[document_id] = _validate_metadata(metadata)
1185
+ except ValueError as exc:
1186
+ return self._error(400, str(exc), "update_document_payload", context.client_id)
1187
+ self._metadata_posting_indexes.pop(state.index_key, None)
1188
+ changed = True
1189
+
1190
+ if text is not None or clear_text:
1191
+ if not self.raw_text_storage_enabled:
1192
+ return self._error(
1193
+ 400,
1194
+ "raw document text storage is not enabled for this index",
1195
+ "update_document_payload",
1196
+ context.client_id,
1197
+ )
1198
+ if clear_text:
1199
+ state.document_text.pop(document_id, None)
1200
+ else:
1201
+ if not isinstance(text, str):
1202
+ return self._error(
1203
+ 400,
1204
+ "text must be a string",
1205
+ "update_document_payload",
1206
+ context.client_id,
1207
+ )
1208
+ state.document_text[document_id] = text
1209
+ changed = True
1210
+
1211
+ if changed:
1212
+ state.updated_at = _utc_now_iso(context.now)
1213
+ self._record_pending_state_journal_documents(
1214
+ state, upserted_document_ids=(document_id,)
1215
+ )
1216
+ # Run the incremental TurboVec sync even though no vectors changed: it is
1217
+ # also what marks this commit delta-eligible (it sets the pending TVIM
1218
+ # delta that _delta_append_ok requires), so _persist_state appends an
1219
+ # O(changed) journal delta instead of rewriting the full O(corpus) base.
1220
+ self._sync_direct_turbovec_index(state)
1221
+ self._persist_state(state)
1222
+
1223
+ return EngineResponse(
1224
+ 200,
1225
+ {
1226
+ "status": "updated",
1227
+ "document_id": document_id,
1228
+ "metadata_updated": metadata is not None,
1229
+ "text_updated": text is not None or clear_text,
1230
+ },
1231
+ )
1232
+
1143
1233
  def _build_initial_documents(
1144
1234
  self,
1145
1235
  *,
@@ -4491,6 +4581,21 @@ class LodeEngine:
4491
4581
  for document in documents:
4492
4582
  state.document_text[document.document_id] = document.text
4493
4583
 
4584
+ def _capture_vector_document_text(
4585
+ self,
4586
+ state: ClientIndexState,
4587
+ documents: tuple[EngineVectorDocument, ...],
4588
+ ) -> None:
4589
+ """Records optional vector-in payload text without embedding it."""
4590
+
4591
+ if not self.raw_text_storage_enabled:
4592
+ return
4593
+ for document in documents:
4594
+ if document.text is None:
4595
+ state.document_text.pop(document.document_id, None)
4596
+ else:
4597
+ state.document_text[document.document_id] = document.text
4598
+
4494
4599
  def _capture_lexical_tokens(
4495
4600
  self,
4496
4601
  state: ClientIndexState,
@@ -227,6 +227,27 @@ class LodeIndex:
227
227
  )
228
228
  )
229
229
 
230
+ def update_document_payload(
231
+ self,
232
+ document_id: str,
233
+ *,
234
+ metadata: Mapping[str, Any] | None = None,
235
+ text: str | None = None,
236
+ clear_text: bool = False,
237
+ ) -> dict[str, Any]:
238
+ """Updates an existing document's metadata and/or retained raw text."""
239
+
240
+ return self._unwrap(
241
+ self.engine.update_document_payload(
242
+ context=self._context(),
243
+ index_id=self.index_id,
244
+ document_id=_required_text(document_id, "document_id"),
245
+ metadata=metadata,
246
+ text=text,
247
+ clear_text=clear_text,
248
+ )
249
+ )
250
+
230
251
  def query_vector(
231
252
  self,
232
253
  vector: Iterable[float],
@@ -540,6 +561,7 @@ def _vector_document_from_item(
540
561
  document_id=_required_text(item.get("document_id"), "document_id"),
541
562
  vector=tuple(float(value) for value in vector),
542
563
  metadata=safe_metadata,
564
+ text=_optional_text(item.get("text"), "text"),
543
565
  )
544
566
 
545
567
 
@@ -666,6 +688,20 @@ def _required_text(value: Any, name: str) -> str:
666
688
  return stripped
667
689
 
668
690
 
691
+ def _optional_text(value: Any, name: str) -> str | None:
692
+ """Returns an optional string for vector-in retained payload text."""
693
+
694
+ if value is None:
695
+ return None
696
+ if not isinstance(value, str):
697
+ raise EngineError(
698
+ f"{name} must be a string",
699
+ status_code=400,
700
+ response={"status": "error", "error": f"{name} must be a string"},
701
+ )
702
+ return value
703
+
704
+
669
705
  def _required_bool(value: Any, name: str) -> bool:
670
706
  """Returns a JSON-style boolean value while rejecting strings and integers."""
671
707