Skip to content

Commit 9758ba2

Browse files
committed
feat(index): add optional Milvus derived index backend
Signed-off-by: Cheney Zhang <chen.zhang@zilliz.com>
1 parent 56ee9c8 commit 9758ba2

58 files changed

Lines changed: 2436 additions & 479 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

QUICKSTART.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,9 @@ works.
8181

8282
## 3. Start the server
8383

84-
Check your file descriptor limit — EverOS opens many LanceDB segment
85-
files under concurrent search + indexing. Platform defaults:
84+
With the default LanceDB index backend, check your file descriptor limit —
85+
EverOS opens many LanceDB segment files under concurrent search + indexing.
86+
Platform defaults:
8687
**macOS 256** · **Linux 1024** · **Windows 8192**. If yours is below
8788
4096, raise it before starting:
8889

@@ -258,7 +259,8 @@ This is what makes EverOS different — memory persists as plain Markdown:
258259
├── ome.toml ← strategy config (hot-reloaded)
259260
├── .index/ ← derived indexes (rebuildable from md)
260261
│ ├── sqlite/system.db
261-
│ └── lancedb/
262+
│ ├── lancedb/ ← default derived index backend
263+
│ └── milvus/ ← Milvus Lite DB when configured
262264
└── .tmp/
263265
```
264266

README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@
3939
EverOS is a Python library and local-first memory runtime for agents and
4040
makers. It gives one portable memory layer across coding assistants, apps,
4141
devices, and workflows from day one. It stores conversations, files, and agent
42-
trajectories as readable Markdown, then syncs local SQLite and LanceDB indexes
43-
for fast retrieval and self-evolving reuse.
42+
trajectories as readable Markdown, then syncs local SQLite and a derived
43+
vector/BM25 index for fast retrieval and self-evolving reuse. LanceDB is the
44+
default embedded index backend; Milvus can be enabled when you want the same
45+
rebuildable index to run on Milvus Lite, Milvus server, or Zilliz Cloud.
4446

4547
<table>
4648
<tr>
@@ -60,7 +62,7 @@ for fast retrieval and self-evolving reuse.
6062
</tr>
6163
<tr>
6264
<td><strong>Local three-part stack</strong></td>
63-
<td>✅ Markdown + SQLite + LanceDB; no MongoDB, Elasticsearch, or Redis required</td>
65+
<td>✅ Markdown + SQLite + embedded LanceDB by default; optional Milvus when configured</td>
6466
<td>❌ Often depends on managed services, vector DBs, graph DBs, or server stacks</td>
6567
</tr>
6668
<tr>
@@ -114,6 +116,12 @@ uv pip install everos
114116
# or: pip install everos
115117
```
116118

119+
To use Milvus as the derived index backend:
120+
121+
```bash
122+
uv pip install "everos[milvus]"
123+
```
124+
117125
### 2. Play With The Demo
118126

119127
Run this before configuring API keys or starting the server:
@@ -642,7 +650,7 @@ Explore stored entities and relationships in a graph interface. Frontend demo; b
642650
## Documentation
643651

644652
- [docs/everos-demo.md](docs/everos-demo.md) — Demo scope and TUI source layout
645-
- [docs/how-memory-works.md](docs/how-memory-works.md) — Markdown, SQLite, LanceDB, and recall flow
653+
- [docs/how-memory-works.md](docs/how-memory-works.md) — Markdown, SQLite, derived index backends, and recall flow
646654
- [docs/use-cases.md](docs/use-cases.md) — Full use-case gallery and integration examples
647655
- [docs/engineering.md](docs/engineering.md) — Contributor engineering reference: build, test, CI, conventions
648656
- [docs/migration-to-1.0.0.md](docs/migration-to-1.0.0.md) — Legacy API migration notes

config.example.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,19 @@ max_concurrent = 5
5656
#
5757
# [lancedb]
5858
# read_consistency_seconds = 5.0
59+
60+
# ── Optional Milvus derived index ─────────────────────
61+
# Markdown remains the source of truth. Set the index backend to Milvus
62+
# when you want the rebuildable vector/BM25 index to live in Milvus.
63+
# Leave uri empty for Milvus Lite at <root>/.index/milvus/milvus.db.
64+
#
65+
# [index]
66+
# backend = "milvus"
67+
#
68+
# [milvus]
69+
# uri = "" # or "http://localhost:19530"
70+
# token = "" # required for Zilliz Cloud/auth-enabled Milvus
71+
# db_name = ""
72+
# consistency_level = "Session"
73+
# dimension = 1024 # must match your embedding model output
74+
# collection_prefix = "everos"

docs/api.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,9 @@ bare FastAPI `detail`); see [Errors](#errors).
8484

8585
`/add` and `/flush` write the markdown file (the source of truth)
8686
**synchronously** — when the call returns with `status: "extracted"`,
87-
the new entry exists on disk. The LanceDB vector / BM25 / scalar index
88-
is rebuilt by the in-process **cascade coroutine asynchronously**.
87+
the new entry exists on disk. The configured vector / BM25 / scalar
88+
index backend is rebuilt by the in-process **cascade coroutine
89+
asynchronously**.
8990

9091
That means `/search` and `/get` may not see a record immediately after
9192
the `/flush` that produced it. Typical sync latency is sub-second, but
@@ -362,7 +363,7 @@ A recursive boolean tree of predicates. Used by `/search.filters` and
362363
`/get.filters`. The Pydantic envelope only checks the recursive
363364
combinator shape; field-level validity (which scalar fields are
364365
filterable, which operators apply, value coercion) runs when the
365-
node is compiled to a LanceDB `where` clause server-side. Compile
366+
node is compiled to a backend-specific filter clause server-side. Compile
366367
errors surface as `422` with the offending field / operator in
367368
`error.message`.
368369

@@ -455,12 +456,12 @@ Examples:
455456
|---|---|
456457
| `"keyword"` | BM25 only — pure lexical match, no embedding cost |
457458
| `"vector"` | Dense vector ANN only — semantic recall, no lexical |
458-
| `"hybrid"` *(default)* | Reciprocal-rank fuse of BM25 + vector + optional scalar filter in a single LanceDB query |
459+
| `"hybrid"` *(default)* | Reciprocal-rank fuse of BM25 + vector + optional scalar filter against the configured derived index backend |
459460
| `"agentic"` | Iterative cluster-path retrieval driven by a cross-encoder rerank loop; higher quality at higher latency / cost |
460461

461462
`"hybrid"` is the default because it balances recall and precision
462-
with one LanceDB roundtrip. `"agentic"` calls the LLM in a loop and
463-
should be reserved for offline or background workflows.
463+
without requiring the agentic loop. `"agentic"` calls the LLM in a loop
464+
and should be reserved for offline or background workflows.
464465

465466
### GetMemoryType
466467

@@ -596,7 +597,7 @@ scope.
596597
this `(session_id, app_id, project_id)`, or it was already flushed).
597598

598599
`/flush` is synchronous with respect to markdown persistence: by the
599-
time the response returns, the new entry is on disk. LanceDB index
600+
time the response returns, the new entry is on disk. Derived index
600601
sync is still asynchronous — see
601602
[Eventual consistency](#eventual-consistency).
602603

docs/architecture.md

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
│ + reflection + strategies + get + events │
1818
├──────────────────────────────────────────────────────┤
1919
│ infra/persistence (Storage adapters; infra/ may host other adapter types) │
20-
│ markdown + sqlite + lancedb
20+
│ markdown + sqlite + derived index
2121
└──────────────────────────────────────────────────────┘
2222
2323
Cross-cutting (used by all layers, depends on none):
@@ -68,8 +68,8 @@ layers = [
6868
└────────────────────────────────────────────────────────────────┘
6969
7070
┌──────────────┐ ┌──────────────┐ ┌─────────────────┐
71-
│ Markdown │ │ SQLite │ │ LanceDB
72-
│ (truth) │ │ (state) │ │ (index)
71+
│ Markdown │ │ SQLite │ │ Derived index
72+
│ (truth) │ │ (state) │ │ LanceDB/Milvus
7373
├──────────────┤ ├──────────────┤ ├─────────────────┤
7474
│ entries + │ │ change queue │ │ vector ANN │
7575
│ frontmatter │ │ + state/LSN │ │ BM25 (Tantivy) │
@@ -78,7 +78,7 @@ layers = [
7878
└──────────────┘ └──────────────┘ └─────────────────┘
7979
│ │ │
8080
▼ ▼ ▼
81-
memory-root/ .index/sqlite/ .index/lancedb/
81+
memory-root/ .index/sqlite/ .index/<backend>/
8282
(truth source) (system data) (rebuildable)
8383
```
8484

@@ -101,10 +101,13 @@ External message
101101
│ │
102102
▼ ▼
103103
4a. SQLite 4b. memory.cascade (async daemon)
104-
audit watches md → diff entries → LanceDB sync
104+
audit watches md → diff entries → index sync
105105
```
106106

107-
**Key guarantee**: md write is strongly consistent (fsync). LanceDB is eventually consistent. LanceDB unavailability does not block response — changes buffer in the SQLite `md_change_state` queue, replayed on recovery.
107+
**Key guarantee**: md write is strongly consistent (fsync). The derived
108+
index is eventually consistent. Index backend unavailability does not block
109+
response — changes buffer in the SQLite `md_change_state` queue, replayed on
110+
recovery.
108111

109112
## Read path
110113

@@ -115,8 +118,8 @@ User query
115118
1. service.search
116119
117120
118-
2. memory.search (hybrid) single LanceDB query =
119-
BM25 + vector ANN + scalar filter
121+
2. memory.search (hybrid) BM25 + vector ANN + scalar filter
122+
through the configured index backend
120123
121124
122125
3. (optional) read md original markdown for context
@@ -139,12 +142,13 @@ extract/
139142

140143
### `memory/cascade/`
141144

142-
Daemon that watches markdown changes and syncs to LanceDB:
145+
Daemon that watches markdown changes and syncs to the configured derived
146+
index backend:
143147

144148
- inotify / FSEvents file watcher (cross-platform via `watchdog`)
145149
- 500ms debounce
146150
- Entry-level diff (added / changed / removed)
147-
- LanceDB single-transaction update (text + vector columns atomic)
151+
- Per-entry index upsert / delete (text + vector columns update together)
148152
- LSN-based crash recovery via the SQLite `md_change_state` queue
149153
- Handlers for all eight business kinds: episode, atomic_fact, foresight,
150154
user_profile, agent_case, agent_skill, knowledge_document, knowledge_topic
@@ -225,7 +229,7 @@ holding **only memory extraction algorithms**:
225229
everalgo is:
226230

227231
- **Stateless** — pure functions, no class hierarchy
228-
- **No I/O** — does not touch md files / LanceDB / SQLite
232+
- **No I/O** — does not touch md files, derived indexes, or SQLite
229233
- **No prompts inline** — extractors that accept a prompt-override parameter use the project-supplied value; others use their algo-bundled defaults
230234

231235
This boundary lets everalgo be reused across product forms (this open-source build, EverOS Cloud, OpenClaw plugins, etc.).

docs/cascade_runbook.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
# Cascade Runbook
22

3-
The cascade daemon keeps LanceDB in sync with the markdown files under
4-
the memory root. Service / entry points only ever write markdown; the
5-
daemon is the **sole** writer of the LanceDB index. This runbook covers
6-
the recurring operational questions.
3+
The cascade daemon keeps the configured derived index in sync with the
4+
markdown files under the memory root. Service / entry points only ever write
5+
markdown; the daemon is the **sole** writer of the derived index. This runbook
6+
covers the recurring operational questions.
7+
8+
Sections that mention LanceDB-specific schemas, index cache, file descriptors,
9+
or `lance error` messages apply to the default LanceDB backend. Milvus uses the
10+
same cascade queue with backend-specific collection management.
711

812
## What runs where
913

@@ -13,7 +17,7 @@ providers in order:
1317
1. **Metrics** — Prometheus collector.
1418
2. **LLM** — LLM client initialisation.
1519
3. **SQLite** — system DB + schema (`SQLModel.metadata.create_all`).
16-
4. **LanceDB** — async connection + schema verification + FTS indexes.
20+
4. **Derived index** — async connection + schema verification + search indexes.
1721
5. **Cascade** — watcher + scanner + worker, all in-process tasks.
1822
6. **OME** — offline memory engine.
1923

@@ -23,7 +27,7 @@ The cascade subsystem itself is three independent loops:
2327
|---|---|---|
2428
| Watcher | `watchdog` filesystem events (sync thread) | `md_change_state.upsert` per registered kind |
2529
| Scanner | Periodic walk (`scan_interval_seconds`, default 30 s) | Same — catches changes the watcher missed |
26-
| Worker | `claim_pending_batch` polling (default 1 s when idle) | Handler dispatch → LanceDB upsert / delete |
30+
| Worker | `claim_pending_batch` polling (default 1 s when idle) | Handler dispatch → index upsert / delete |
2731

2832
Every loop talks to the same `md_change_state` sqlite table. The
2933
worker's claim mode (`pending → processing → done/failed`) keeps

docs/cli.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
The `everos` command-line entry point covers **setup and operations**
44
generate starter config files (`init`), run the HTTP API server (`server
55
start`), inspect effective config (`config show`), and operate the
6-
md → LanceDB index queue (`cascade`). Hot-path
6+
md → derived index queue (`cascade`). Hot-path
77
business (`/add` `/flush` `/search` `/get`) is the **HTTP API**, not the
88
CLI.
99

@@ -32,7 +32,7 @@ everos
3232
│ └── show [--root PATH] Show effective configuration
3333
├── server
3434
│ └── start [--host] [--port] [--root] [--reload] [--log-level] Start the HTTP API server (uvicorn)
35-
└── cascade [--root PATH] Inspect / operate the md → LanceDB sync queue
35+
└── cascade [--root PATH] Inspect / operate the md → derived index sync queue
3636
├── status Queue / LSN summary
3737
├── sync [PATH] Drain the queue now (optional PATH force-enqueues)
3838
└── fix [--apply] List failed rows / re-enqueue retryable ones
@@ -43,8 +43,9 @@ Each subcommand lives in its own module under
4343
registered in `cli/main.py`. The CLI is intentionally small — hot-path
4444
business (`/add` `/flush` `/search` `/get`) is the **HTTP API**, not the
4545
CLI; the CLI covers setup (`init`), running the server, and index ops
46-
(`cascade`). There is no `reindex` command — rebuild by deleting
47-
`<root>/.index/lancedb` and restarting, or run `everos cascade sync`.
46+
(`cascade`). There is no `reindex` command — rebuild by deleting the
47+
selected backend's derived index directory (`<root>/.index/lancedb` or
48+
`<root>/.index/milvus`) and restarting, or run `everos cascade sync`.
4849

4950
## `everos server start`
5051

@@ -68,8 +69,8 @@ everos server start \
6869
| `--root` | `EVEROS_ROOT` | `~/.everos` |
6970
| `--reload` || off (use in development) |
7071

71-
Lifespan startup wires the storage backends (SQLite engine + LanceDB
72-
connection) on app boot; see
72+
Lifespan startup wires the storage backends (SQLite engine + configured
73+
derived index backend) on app boot; see
7374
[`entrypoints/api/lifespans/`](../src/everos/entrypoints/api/lifespans/).
7475

7576
## Configuration via env vars
@@ -81,7 +82,9 @@ Both CLI and HTTP server read configuration from `pydantic-settings`:
8182
| `EVEROS_ROOT` | memory-root path (default `~/.everos`) |
8283
| `EVEROS_MEMORY__TIMEZONE` | `Settings.memory.timezone` (e.g. `Asia/Shanghai`) |
8384
| `EVEROS_SQLITE__BUSY_TIMEOUT_MS` | `Settings.sqlite.busy_timeout_ms` |
85+
| `EVEROS_INDEX__BACKEND` | `Settings.index.backend` (`lancedb` or `milvus`) |
8486
| `EVEROS_LANCEDB__READ_CONSISTENCY_SECONDS` | `Settings.lancedb.read_consistency_seconds` |
87+
| `EVEROS_MILVUS__URI` | `Settings.milvus.uri` |
8588

8689
Pattern: `EVEROS_<SECTION>__<KEY>` (double underscore = nesting). See
8790
[`config/settings.py`](../src/everos/config/settings.py).

docs/configuration.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,26 @@ everos init --root /data/everos
9999
| `read_consistency_seconds` | float \| null | `null` | Read consistency interval. `null` = no check, `0` = strict, `>0` = eventual. |
100100
| `index_cache_size_bytes` | int | `16777216` | Upper bound on LanceDB index cache (16 MB default). |
101101

102+
### `[index]`
103+
104+
| Field | Type | Default | Description |
105+
|---|---|---|---|
106+
| `backend` | `"lancedb"` \| `"milvus"` | `"lancedb"` | Rebuildable vector/BM25 index backend used by cascade, search, and get. Markdown remains the source of truth; SQLite remains the system state store. |
107+
108+
### `[milvus]`
109+
110+
Milvus is optional. Install with `everos[milvus]`, then set
111+
`[index].backend = "milvus"`.
112+
113+
| Field | Type | Default | Description |
114+
|---|---|---|---|
115+
| `uri` | string \| null | `""` | Empty uses Milvus Lite at `<root>/.index/milvus/milvus.db`. Set to a Milvus server or Zilliz Cloud endpoint to use an external service. |
116+
| `token` | string \| null | `""` | Token for Zilliz Cloud or auth-enabled Milvus. |
117+
| `db_name` | string \| null | `""` | Optional Milvus database name. |
118+
| `consistency_level` | `"Strong"` \| `"Session"` \| `"Bounded"` \| `"Eventually"` | `"Session"` | Milvus consistency level for created collections. |
119+
| `dimension` | int | `1024` | Dense vector dimension. Must match the configured embedding model output. |
120+
| `collection_prefix` | string | `"everos"` | Prefix for EverOS Milvus collections. |
121+
102122
### `[llm]`
103123

104124
| Field | Type | Default | Required | Description |
@@ -228,3 +248,5 @@ Examples:
228248
| `[llm] api_key = "sk-..."` | `EVEROS_LLM__API_KEY=sk-...` |
229249
| `[sqlite] busy_timeout_ms = 10000` | `EVEROS_SQLITE__BUSY_TIMEOUT_MS=10000` |
230250
| `[memory] timezone = "Asia/Tokyo"` | `EVEROS_MEMORY__TIMEZONE=Asia/Tokyo` |
251+
| `[index] backend = "milvus"` | `EVEROS_INDEX__BACKEND=milvus` |
252+
| `[milvus] uri = "http://localhost:19530"` | `EVEROS_MILVUS__URI=http://localhost:19530` |

0 commit comments

Comments
 (0)