loci-rag 0.4.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 (47) hide show
  1. loci_rag-0.4.0/LICENSE +21 -0
  2. loci_rag-0.4.0/PKG-INFO +408 -0
  3. loci_rag-0.4.0/README.md +371 -0
  4. loci_rag-0.4.0/pyproject.toml +48 -0
  5. loci_rag-0.4.0/setup.cfg +4 -0
  6. loci_rag-0.4.0/src/loci/__init__.py +2 -0
  7. loci_rag-0.4.0/src/loci/bm25.py +58 -0
  8. loci_rag-0.4.0/src/loci/chatlog.py +79 -0
  9. loci_rag-0.4.0/src/loci/chunker.py +118 -0
  10. loci_rag-0.4.0/src/loci/cli.py +399 -0
  11. loci_rag-0.4.0/src/loci/config.py +104 -0
  12. loci_rag-0.4.0/src/loci/embedder.py +19 -0
  13. loci_rag-0.4.0/src/loci/loaders.py +200 -0
  14. loci_rag-0.4.0/src/loci/mcp_server.py +492 -0
  15. loci_rag-0.4.0/src/loci/memories.py +92 -0
  16. loci_rag-0.4.0/src/loci/reranker.py +55 -0
  17. loci_rag-0.4.0/src/loci/retriever.py +189 -0
  18. loci_rag-0.4.0/src/loci/store.py +112 -0
  19. loci_rag-0.4.0/src/loci/watcher.py +57 -0
  20. loci_rag-0.4.0/src/loci/wiki.py +90 -0
  21. loci_rag-0.4.0/src/loci_rag.egg-info/PKG-INFO +408 -0
  22. loci_rag-0.4.0/src/loci_rag.egg-info/SOURCES.txt +45 -0
  23. loci_rag-0.4.0/src/loci_rag.egg-info/dependency_links.txt +1 -0
  24. loci_rag-0.4.0/src/loci_rag.egg-info/entry_points.txt +3 -0
  25. loci_rag-0.4.0/src/loci_rag.egg-info/requires.txt +15 -0
  26. loci_rag-0.4.0/src/loci_rag.egg-info/top_level.txt +1 -0
  27. loci_rag-0.4.0/tests/test_bm25.py +45 -0
  28. loci_rag-0.4.0/tests/test_bugfix_regression.py +220 -0
  29. loci_rag-0.4.0/tests/test_chatlog.py +84 -0
  30. loci_rag-0.4.0/tests/test_chunker.py +97 -0
  31. loci_rag-0.4.0/tests/test_config.py +52 -0
  32. loci_rag-0.4.0/tests/test_links.py +67 -0
  33. loci_rag-0.4.0/tests/test_loaders.py +71 -0
  34. loci_rag-0.4.0/tests/test_mcp.py +100 -0
  35. loci_rag-0.4.0/tests/test_mcp_resources.py +110 -0
  36. loci_rag-0.4.0/tests/test_memories.py +133 -0
  37. loci_rag-0.4.0/tests/test_office.py +80 -0
  38. loci_rag-0.4.0/tests/test_operators.py +83 -0
  39. loci_rag-0.4.0/tests/test_per_source.py +53 -0
  40. loci_rag-0.4.0/tests/test_rerank.py +101 -0
  41. loci_rag-0.4.0/tests/test_reranker.py +91 -0
  42. loci_rag-0.4.0/tests/test_retriever.py +93 -0
  43. loci_rag-0.4.0/tests/test_store.py +65 -0
  44. loci_rag-0.4.0/tests/test_unconfigured_server.py +94 -0
  45. loci_rag-0.4.0/tests/test_verify.py +85 -0
  46. loci_rag-0.4.0/tests/test_watcher.py +47 -0
  47. loci_rag-0.4.0/tests/test_wiki.py +125 -0
loci_rag-0.4.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 IvenKooLab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,408 @@
1
+ Metadata-Version: 2.4
2
+ Name: loci-rag
3
+ Version: 0.4.0
4
+ Summary: A local 'second brain': RAG over your scattered notes and docs, with an MCP server so AI agents can use it too.
5
+ Author: IvenKooLab
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/IvenKooLab/loci
8
+ Project-URL: Changelog, https://github.com/IvenKooLab/loci/blob/main/CHANGELOG.md
9
+ Project-URL: Roadmap, https://github.com/IvenKooLab/loci/blob/main/docs/roadmap.md
10
+ Keywords: rag,loci,mcp,obsidian,chromadb,local-first,embeddings
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Text Processing :: Indexing
22
+ Requires-Python: >=3.11
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: openai>=1.50
26
+ Requires-Dist: chromadb>=0.5
27
+ Provides-Extra: pdf
28
+ Requires-Dist: pymupdf4llm>=0.0.21; extra == "pdf"
29
+ Requires-Dist: pypdf>=4; extra == "pdf"
30
+ Provides-Extra: docx
31
+ Requires-Dist: python-docx>=1.1; extra == "docx"
32
+ Provides-Extra: rerank
33
+ Requires-Dist: sentence-transformers>=3; extra == "rerank"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=8; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # loci 🧠
39
+
40
+ [![English](https://img.shields.io/badge/English-README-0969DA)](README.md)
41
+ [![简体中文](https://img.shields.io/badge/简体中文-README-6E7681)](README.zh-CN.md)
42
+ [![繁體中文](https://img.shields.io/badge/繁體中文-README-6E7681)](README.zh-TW.md)
43
+ [![日本語](https://img.shields.io/badge/日本語-README-6E7681)](README.ja.md)
44
+ [![한국어](https://img.shields.io/badge/한국어-README-6E7681)](README.ko.md)
45
+
46
+ <!-- mcp-name: io.github.IvenKooLab/loci -->
47
+
48
+ ![CI](https://github.com/IvenKooLab/loci/actions/workflows/ci.yml/badge.svg)
49
+ ![License](https://img.shields.io/badge/license-MIT-blue.svg)
50
+ ![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)
51
+ [![loci MCP server — quality and maintenance score on Glama](https://glama.ai/mcp/servers/IvenKooLab/loci/badges/score.svg)](https://glama.ai/mcp/servers/IvenKooLab/loci)
52
+ [![ModelScope MCP Square](https://img.shields.io/badge/ModelScope-MCP-7C3AED)](https://modelscope.cn/mcp/servers/IvenKooLab/loci)
53
+
54
+ > Two thousand years ago, orators stored their speeches in the rooms of a
55
+ > palace and walked through them to remember. **loci does the same for your
56
+ > files.**
57
+ >
58
+ > *Loci* is the method behind every memory palace: place knowledge in
59
+ > locations, recall it by walking the path.
60
+
61
+ **A queryable "second brain" for the project docs, notes, and chat logs scattered
62
+ across a dozen directories — and an MCP server so your AI agents can use it too.**
63
+
64
+ Local files → heading-aware chunking → embeddings → hybrid retrieval (vector +
65
+ BM25) → LLM answer with section-level citations. The index lives entirely on
66
+ your machine; only embedding/chat calls go out, to any OpenAI-compatible API
67
+ (Zhipu / DeepSeek / Kimi / OpenAI / …).
68
+
69
+ > **The thesis** (from studying the 90k-star platforms and the graveyard of
70
+ > dead lightweight tools — see
71
+ > [our competitive landscape study](docs/research/competitive-landscape.md)):
72
+ > don't build another chat app. Build the **memory layer that every chat app
73
+ > can mount**. Claude Desktop, Cursor, Cline, or any MCP host becomes this
74
+ > project's UI, for free.
75
+
76
+ ## Demo
77
+
78
+ Real session, indexed against the docs of
79
+ [minimax-h3-turing](https://github.com/IvenKooLab/minimax-h3-turing)
80
+ (paths shortened for display):
81
+
82
+ ```
83
+ $ python main.py search "what the 22G card can and cannot do" -k 3
84
+
85
+ [1] minimax-h3-turing/docs/en/01-hardware-limits.md > 01 · What a 2080Ti 22G Can and Cannot Do (similarity 0.562)
86
+ [2] minimax-h3-turing/docs/en/02-w4a8-vs-w4a4.md > 02 · Quantization Measured > You Can Try Without 22G (similarity 0.446)
87
+ [3] minimax-h3-turing/docs/en/01-hardware-limits.md > ... > 3. VRAM is just barely enough — manage it (similarity 0.504)
88
+
89
+ $ python main.py ask "How should I choose between T8 aggressive mode and the final-render mode, and why?"
90
+
91
+ Answer:
92
+ * Drafts / preview / shot selection: use T8 aggressive mode — a 43% speedup
93
+ (2.7 min/clip), and "a different picture of equal quality" is fine for picking shots.
94
+ * Final shots: use final-render mode (no T8). T8 makes the numerical trajectory
95
+ fork, so re-running with the same seed produces a different clip — which breaks
96
+ the reproducibility final outputs need.
97
+
98
+ [source: docs/en/08-t8-blockcache-4step.md > Practical Advice (4-step Turbo route)]
99
+ [source: docs/en/06-faq.md > 12. Cache-style accelerators break "same-seed re-runs"]
100
+ ```
101
+
102
+ Hybrid retrieval means a Chinese query still finds the English doc (and vice
103
+ versa) — keyword evidence (`BM25`) catches what embeddings miss, and every
104
+ citation points at a **section**, not just a file.
105
+
106
+ ### Does hybrid actually help? (mini-eval, 10 bilingual queries)
107
+
108
+ ```
109
+ $ python scripts/eval_retrieval.py scripts/eval_cases.example.jsonl
110
+ vector-only: 9/10 → hybrid: 10/10
111
+ ```
112
+
113
+ Hybrid also fixed the #1 ranking on keyword-ish queries (e.g. "T8 block cache
114
+ threshold speedup": vector put an FAQ first, hybrid puts the actual T8
115
+ writeup first). Run it against your own corpus with your own cases file.
116
+
117
+ ### Reranking: two providers
118
+
119
+ `--rerank` reorders the fused candidates for precision:
120
+
121
+ | Provider | How | Cost |
122
+ |---|---|---|
123
+ | `llm` (default) | pointwise 0–3 relevance scoring by your chat model | one extra LLM call |
124
+ | `local` | cross-encoder, via `pip install 'loci[rerank]'` | ~30–70 ms for 5 pairs on GPU — offline, free |
125
+
126
+ ```bash
127
+ python main.py search "T8 speedup" --rerank # provider from config
128
+ python main.py search "T8 speedup" --rerank local # cross-encoder (BAAI/bge-reranker-base)
129
+ ```
130
+
131
+ The local model downloads on first use (~1.1 GB; set `HF_ENDPOINT=https://hf-mirror.com`
132
+ if HuggingFace is slow in your region). Measured on a 2080 Ti, bilingual query.
133
+
134
+ ### Office documents, PDF tables, chat logs
135
+
136
+ - **PDFs**: with the `[pdf]` extra, PyMuPDF4LLM extracts pages as markdown —
137
+ **tables come through as pipe rows** (plain pypdf text is the fallback)
138
+ - **Word**: with the `[docx]` extra, `.docx` paragraphs and table rows are indexed
139
+ - **Chat exports**: drop a ChatGPT or Claude `conversations.json` into any
140
+ source directory — it becomes one searchable document per conversation,
141
+ tagged `chatlog` (`search --tag chatlog` scopes to chat history)
142
+
143
+ ## How it relates to Obsidian / your note app
144
+
145
+ It doesn't compete — the two layer up. Obsidian (or any editor) is the
146
+ note-taking frontend; this is the **cross-vault search engine**: point
147
+ `sources` at any directories (Obsidian vaults, project docs, chat exports)
148
+ and query all of them at once — from your terminal, your scripts, or your AI
149
+ agent via MCP. Obsidian-native details are understood: frontmatter `tags:`
150
+ (filter with `search --tag`), `[[wikilinks]]` (walk the graph with `links`),
151
+ code blocks are never cut mid-block, and one-line notes stay searchable.
152
+
153
+ ## How it works
154
+
155
+ ```mermaid
156
+ %%{init: {'theme':'base','themeVariables':{'background':'#000000','primaryColor':'#000000','primaryTextColor':'#00FF41','primaryBorderColor':'#00FF41','lineColor':'#00FF41','secondaryColor':'#001a00','tertiaryColor':'#000000','clusterBkg':'#000000','clusterBorder':'#00FF41','edgeLabelBackground':'#000000','fontSize':'14px','fontFamily':'trebuchet ms, verdana, arial, sans-serif'},'themeCSS':'.nodeLabel { color: #00FF41 !important; } .edgeLabel { background: #000 !important; color: #00FF41 !important; } .cluster-label { color: #00FF41 !important; }'}}%%
157
+ flowchart LR
158
+ subgraph sources["📥 Your machine"]
159
+ notes["Obsidian / markdown notes"]
160
+ docs["PDF tables · docx · project docs"]
161
+ chats["ChatGPT / Claude exports"]
162
+ mem["memories/ — agent-written notes"]
163
+ wikidir["wiki/ — consolidated pages"]
164
+ end
165
+
166
+ subgraph loci["🧠 loci — local index, nothing leaves the machine"]
167
+ ingest["ingest / watch<br>loaders → chunker → embedder"]
168
+ store[("ChromaDB<br>hybrid index")]
169
+ retrieve["hybrid retrieval<br>vector + BM25 → RRF"]
170
+ mcp["loci-mcp<br>8 tools · resources · prompts"]
171
+ end
172
+
173
+ subgraph hosts["🖥️ Your AI hosts"]
174
+ ide["Claude Code · Qoder · Trae<br>Cursor · Cline"]
175
+ desktop["Claude Desktop"]
176
+ term["Terminal<br>search / ask / chat / wiki"]
177
+ end
178
+
179
+ api["☁️ OpenAI-compatible API<br>Zhipu / DeepSeek / Kimi / OpenAI<br>or 100% offline via Ollama"]
180
+
181
+ sources --> ingest --> store
182
+ mem -. auto-indexed .-> store
183
+ wikidir -. auto-indexed .-> store
184
+ store --> retrieve
185
+ retrieve --> term
186
+ retrieve --> mcp
187
+ mcp <--> ide
188
+ mcp <-.-> desktop
189
+ retrieve -. "embedding + chat calls only" .-> api
190
+ ```
191
+
192
+ The write path in one line: `loaders → chunker (heading-aware split) → embedder → store (ChromaDB, persistent)` — incremental, deduplicated by content hash.
193
+
194
+ ## Install & quick start
195
+
196
+ Requires Python 3.11+ (uses the stdlib `tomllib`).
197
+
198
+ ```bash
199
+ # option A: install as a package (adds `loci` and `loci-mcp` commands)
200
+ pip install -e ".[pdf,docx]" # optional extras: PDF w/ tables, Word documents
201
+
202
+ # option B: zero-install quickstart
203
+ pip install -r requirements.txt
204
+
205
+ # 1. Configure: copy the example and fill in your values
206
+ cp config.example.toml config.toml
207
+
208
+ # 2. Ingest (incremental — deduplicated by content hash, safe to re-run)
209
+ loci ingest # or: python main.py ingest
210
+
211
+ # 3. Ask
212
+ loci ask "what did I write about X?"
213
+ ```
214
+
215
+ ### The workflow
216
+
217
+ ```mermaid
218
+ %%{init: {'theme':'base','themeVariables':{'background':'#000000','primaryColor':'#000000','primaryTextColor':'#00FF41','primaryBorderColor':'#00FF41','lineColor':'#00FF41','secondaryColor':'#001a00','tertiaryColor':'#000000','clusterBkg':'#000000','clusterBorder':'#00FF41','edgeLabelBackground':'#000000','fontSize':'14px','fontFamily':'trebuchet ms, verdana, arial, sans-serif'},'themeCSS':'.nodeLabel { color: #00FF41 !important; } .edgeLabel { background: #000 !important; color: #00FF41 !important; } .cluster-label { color: #00FF41 !important; }'}}%%
219
+ flowchart TD
220
+ A["pip install loci-rag"] --> B["cp config.example.toml config.toml<br>fill API keys + source dirs"]
221
+ B --> C["loci ingest — hybrid index built"]
222
+ C --> D["loci watch — index stays fresh (optional)"]
223
+ C --> E{"What do you need?"}
224
+ E -->|"a synthesized answer"| F["loci ask --verify<br>claim-by-claim audit"]
225
+ E -->|"raw excerpts to quote"| G["loci search --tag memory"]
226
+ E -->|"back-and-forth"| H["loci chat"]
227
+ E -->|"scattered notes on a topic"| I["loci wiki topic<br>consolidate into a wiki page"]
228
+ F --> J["loci remember —<br>keep what you learned"]
229
+ I --> J
230
+ ```
231
+
232
+ ## Commands
233
+
234
+ | Command | What it does |
235
+ |---|---|
236
+ | `ingest` | scan sources, index new/changed files, prune deleted ones (`--force` re-embeds everything) |
237
+ | `search "query"` | retrieval only — ranked excerpts with `path > section` breadcrumbs |
238
+ | `ask "question"` | retrieval + LLM answer with `[source: path > section]` citations |
239
+ | `ask "…" --verify` | additionally audit the answer claim-by-claim against the sources (✓ supported, ~ partial, ✗ unsupported) |
240
+
241
+ Filter operators (combine freely, on `search` and `ask`):
242
+
243
+ | Flag | Filters to |
244
+ |---|---|
245
+ | `--tag foo` | files whose frontmatter tags contain `foo` |
246
+ | `--in docs/en` | files whose path contains the substring |
247
+ | `--since 2026-08` / `--since 2026-08-15` | files modified on/after that date |
248
+ | `-e "exact phrase"` | chunks containing the exact phrase |
249
+ | `-k N` | return N hits (default 5) |
250
+ | `links "note"` | show the `[[wikilink]]` graph around a note — outbound and inbound |
251
+ | `chat` | multi-turn Q&A loop with conversation memory (`/clear`, `/exit`) |
252
+ | `watch` | keep the index current by polling sources (interval in `[watch]`) |
253
+ | `stats` | what's in the index: chunks per source, models, retrieval settings |
254
+ | `doctor` | health check: config, source dirs, embed/LLM endpoints, store (exit code 1 on failure — CI-friendly) |
255
+ | `python mcp_server.py` | MCP server over stdio (see below) |
256
+
257
+ ## One memory, every IDE
258
+
259
+ Because every MCP host mounts the *same* loci server (same `config.toml`, same
260
+ index), memory written from one tool is recalled from every other:
261
+
262
+ ```bash
263
+ # Claude Code
264
+ claude mcp add loci -- loci-mcp
265
+ ```
266
+
267
+ ```jsonc
268
+ // Cursor / Cline / Qoder / Trae (mcpServers JSON — same shape everywhere)
269
+ { "mcpServers": { "loci": { "command": "loci-mcp" } } }
270
+ ```
271
+
272
+ Then, from any of them: *"remember that the staging password rotates on
273
+ Mondays"* → `brain_remember` → later, from a *different* IDE:
274
+ *"when does the staging password rotate?"* → answered, with the memory cited.
275
+ Memories live as plain markdown in the `memories` directory (git-friendly, no
276
+ lock-in) and are tagged `memory`, so `loci search --tag memory` scopes to them.
277
+
278
+ > **Cross-IDE tip**: the default `store` / `memories` paths are relative to the
279
+ > directory loci is launched from. If your IDEs start in different project
280
+ > folders, point both at one absolute location in `config.toml` — e.g.
281
+ > `store.path = "~/.loci/store"` and `memories.path = "~/.loci/memories"` —
282
+ > and every IDE shares the exact same memory store.
283
+
284
+ ```mermaid
285
+ %%{init: {'theme':'base','themeVariables':{'background':'#000000','primaryColor':'#000000','primaryTextColor':'#00FF41','primaryBorderColor':'#00FF41','lineColor':'#00FF41','actorBkg':'#000000','actorBorder':'#00FF41','actorTextColor':'#00FF41','signalColor':'#00FF41','signalTextColor':'#00FF41','noteBkgColor':'#001a00','noteBorderColor':'#00FF41','activationBkgColor':'#001a00','edgeLabelBackground':'#000000','fontSize':'14px','fontFamily':'trebuchet ms, verdana, arial, sans-serif'},'themeCSS':'.messageText { fill: #00FF41 !important; } .actor { fill: #000 !important; stroke: #00FF41 !important; } text.actor { fill: #00FF41 !important; }'}}%%
286
+ sequenceDiagram
287
+ participant CC as Claude Code
288
+ participant L as loci-mcp
289
+ participant S as ChromaDB (local)
290
+ participant T as Trae / Qoder / any IDE
291
+ CC->>L: brain_remember("deploy rotates Mondays")
292
+ L->>S: write memory.md + embed + index
293
+ Note over S: persists across sessions and IDEs
294
+ T->>L: brain_search("password rotation")
295
+ L->>S: hybrid retrieval
296
+ L-->>T: cited answer — the memory is recalled
297
+ ```
298
+
299
+ ## Mount it in any MCP host
300
+
301
+ Add to `claude_desktop_config.json` (Claude Desktop) or your MCP client's
302
+ config:
303
+
304
+ ```json
305
+ {
306
+ "mcpServers": {
307
+ "loci": {
308
+ "command": "python",
309
+ "args": ["/path/to/loci/mcp_server.py"]
310
+ }
311
+ }
312
+ }
313
+ ```
314
+
315
+ The server exposes three tools (zero dependencies beyond the core):
316
+
317
+ | Tool | Purpose |
318
+ |---|---|
319
+ | `brain_search(query, k?, tag?, in?)` | ranked excerpts with breadcrumbs |
320
+ | `brain_ask(question, verify?)` | grounded answer with citations; `verify=true` adds a claim-by-claim audit |
321
+ | `brain_links(note)` | outbound/inbound `[[wikilink]]` graph around a note |
322
+ | `brain_stats()` | index overview (chunks per source) |
323
+ | `brain_remember(text, title?, tags?)` | **write a memory** — durable, shared across sessions and IDEs |
324
+ | `brain_forget(query)` | soft-delete matching memories (they go to a `.trash` folder) |
325
+ | `brain_wiki(topic)` | **memory consolidation** — distill the index into a curated wiki page about a topic |
326
+ | `brain_ingest(force?)` | incremental re-index |
327
+
328
+ Beyond tools, the server speaks the full protocol:
329
+
330
+ - **Resources** — `resources/list` exposes `brain://stats` plus one
331
+ `brain://note/…` resource per indexed file (raw markdown via `resources/read`)
332
+ - **Prompts** — three ready-made templates: `brain-briefing`, `study-plan`,
333
+ `contradiction-check`; hosts render them with your topic pre-filled
334
+
335
+ ## Fully offline with Ollama
336
+
337
+ The index is local by design — and the embedding/chat calls can be too. Any
338
+ OpenAI-compatible server works; [Ollama](https://ollama.com) is verified
339
+ end-to-end:
340
+
341
+ ```toml
342
+ [llm]
343
+ base_url = "http://localhost:11434/v1"
344
+ api_key = "ollama" # any non-empty placeholder
345
+ model = "qwen2.5:0.5b"
346
+
347
+ [embed]
348
+ base_url = "http://localhost:11434/v1"
349
+ api_key = "ollama"
350
+ model = "all-minilm"
351
+ ```
352
+
353
+ With this config, `ingest` / `search` / `ask` make zero cloud calls.
354
+ Swap in a bigger local chat model for better answers — the pipeline is
355
+ model-agnostic.
356
+
357
+ ## Configuration
358
+
359
+ | Key | Meaning |
360
+ |---|---|
361
+ | `[llm]` | base_url / api_key / model — any OpenAI-compatible endpoint |
362
+ | `[embed]` | same; the model must be an embedding model (e.g. `embedding-3`) |
363
+ | `[[sources]]` | document directories, scanned recursively for `.md` / `.txt` (plus `.pdf`/`.docx` with the matching extras) |
364
+ | `[[sources]] chunk_size` / `chunk_overlap` | optional per-directory chunking override — wins over the global `[chunk]` block |
365
+ | `[chunk]` | chunking params (default 800 chars / 100 overlap) |
366
+ | `[top_k]` | number of hits per search (default 5) |
367
+ | `[retrieval]` | `hybrid` (vector+BM25 fusion, default on), `rrf_k`, `rerank` (LLM reranking, default off) |
368
+ | `[watch]` | poll `interval` seconds |
369
+
370
+ API keys can also come from the environment variables `BRAIN_LLM_API_KEY` /
371
+ `BRAIN_EMBED_API_KEY` (these override the config file).
372
+
373
+ ## Design decisions
374
+
375
+ - **~300 lines of core, no LangChain** — every stage is readable, hackable,
376
+ and learnable. The whole engine fits in one sitting.
377
+ - **MCP-first** — the agent ecosystem is the UI layer. No web app to maintain.
378
+ - **Hybrid retrieval on by default** — vector search fused with a native
379
+ ~60-line BM25 (CJK-aware tokenizer) via Reciprocal Rank Fusion.
380
+ - **Citations always, with breadcrumbs** — `path > section`, so claims are
381
+ verifiable at a glance.
382
+ - **Robust, inspectable indexing** — defensive loaders (skip what can't be
383
+ parsed, never hang), content-hash incrementality, real pruning, `stats` and
384
+ `doctor` so the index is never a black box.
385
+ - **Tiny notes stay searchable** — no minimum-chunk filter; a one-line note is
386
+ still indexed (a lesson from watching other tools drop or choke on them).
387
+ - **Keys never in code** — `config.toml` (gitignored) or env vars.
388
+
389
+ ## Where it sits
390
+
391
+ | | loci | AnythingLLM (65k★) | Khoj (37k★) | RAGFlow (90k★) |
392
+ |---|---|---|---|---|
393
+ | Positioning | personal retrieval **backend** + MCP | all-in-one chat platform | self-hosted AI assistant | enterprise RAG engine |
394
+ | Footprint | 2 runtime deps, no Docker | desktop app / Docker | Django server + workers | Docker, DeepDoc models |
395
+ | UI | your terminal & your agents | built-in web/desktop | web + Obsidian/Emacs | web |
396
+ | MCP server | ✅ native | consumer | — | — |
397
+ | Hackable core | ✅ ~300 lines | ❌ | ❌ | ❌ |
398
+ | Multi-user | by design, no | ✅ | ✅ | ✅ |
399
+
400
+ (Full data and reasoning: [competitive landscape study](docs/research/competitive-landscape.md).)
401
+
402
+ ## Roadmap
403
+
404
+ See [docs/roadmap.md](docs/roadmap.md) — reranking, GraphRAG experiments, more loaders.
405
+
406
+ ## License
407
+
408
+ MIT