Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3382c22
feat(index): add optional Milvus derived index backend
zc277584121 Jul 9, 2026
c1d5baa
fix(milvus): bound fact recall and use exact counts
zc277584121 Jul 9, 2026
e698307
style: format Milvus index changes
zc277584121 Jul 10, 2026
0d38339
refactor(index): require remote Milvus deployments
zc277584121 Aug 18, 2026
79b629f
docs(milvus): add remote connection examples
zc277584121 Aug 19, 2026
7849535
refactor(index): separate derived-index contracts, routing, and adapter
Aug 29, 2026
de56331
refactor(index): stop the port leaking backend limits and syntax
Aug 27, 2026
8029421
fix(milvus): datetime round-trip, update race, and scan window
Aug 27, 2026
f99f4cc
fix(milvus): verify the physical collection schema, not just field names
Aug 28, 2026
6ab6722
test(integration): run the tiered API suites against both index backends
Sep 3, 2026
b0a8ca3
test(e2e): run the pipeline suites against both index backends
Sep 3, 2026
91e3512
test(e2e): assert the truncation warning by substance, not by its label
Sep 3, 2026
340a22b
chore: merge upstream main into Milvus branch
zc277584121 Sep 3, 2026
c4fd720
fix(milvus): verify derived metadata
zc277584121 Sep 3, 2026
11e177f
fix(milvus): tighten validation probes
zc277584121 Sep 3, 2026
05045aa
test(milvus): guarantee fixture finalization
zc277584121 Sep 3, 2026
c963188
test(index): check both adapters against the port contract
Sep 4, 2026
fd3dad1
ci: run the port contract against Milvus too
Sep 4, 2026
927119d
chore: merge live Milvus branch updates
zc277584121 Sep 4, 2026
454e706
chore: merge upstream main into Milvus branch
zc277584121 Sep 4, 2026
7f97291
test(index): tighten Milvus contract isolation
zc277584121 Sep 4, 2026
41a26e4
refactor(persistence): split derived-index ports from their adapters
Sep 4, 2026
8e57690
fix(milvus): clamp search topK to the engine's result window
Sep 7, 2026
0b18349
fix(predicate): reject an empty predicate group at construction
Sep 7, 2026
2ca2cd9
fix(persistence): break the port/adapter import cycle
Sep 7, 2026
ff6fba4
test(integration): pass the low-level Lance repos a rendered filter
Sep 7, 2026
b894a78
test(index): pin empty-group rejection and import-order independence
Sep 7, 2026
6e853d7
test(e2e): revive the knowledge HTTP lifecycle test
Sep 7, 2026
f1768b8
fix(deps): pin everalgo's transitive layer to what the suite resolves
Sep 7, 2026
ab24e70
test: reset the partition-lock pool around every test
Sep 7, 2026
bcc5cb0
chore: merge upstream main into Milvus branch
Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,75 @@ jobs:
- name: Integration tests
run: make integration

integration-milvus:
name: integration tests (Milvus 2.6)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Install uv
uses: astral-sh/setup-uv@v8.2.0
with:
enable-cache: true
cache-dependency-glob: uv.lock

- name: Set up Python
run: uv python install 3.12

- name: Install dependencies (frozen)
run: make install-deps

- name: Start Milvus 2.6.22
run: |
curl --fail --location --retry 3 \
--output /tmp/milvus-compose.yml \
https://github.com/milvus-io/milvus/releases/download/v2.6.22/milvus-standalone-docker-compose.yml
docker compose -f /tmp/milvus-compose.yml up -d
for attempt in {1..60}; do
if curl --fail --silent http://127.0.0.1:9091/healthz >/dev/null; then
exit 0
fi
sleep 2
done
docker compose -f /tmp/milvus-compose.yml logs
exit 1

- name: Milvus repository contract
env:
EVEROS_TEST_MILVUS_URI: http://127.0.0.1:19530
EVEROS_TEST_MILVUS_FULL_STARTUP: "1"
run: uv run --frozen pytest tests/integration/test_milvus_remote.py -v

- name: Milvus /get truncation telemetry
env:
EVEROS_TEST_MILVUS_URI: http://127.0.0.1:19530
run: >-
uv run --frozen pytest
tests/e2e/test_get_endpoint_e2e.py::test_get_truncates_above_max_fetch
-v -k milvus

# The contract tests above exercise the repository port directly. This
# drives the same request path the application does -- /add, /flush,
# cascade, every search method, /get -- against the remote backend, so a
# break anywhere between the API and Milvus surfaces here rather than in
# production. Deselecting the LanceDB half keeps the job to its subject.
- name: Milvus end-to-end (tiered API suites)
env:
EVEROS_TEST_MILVUS_URI: http://127.0.0.1:19530
run: uv run --frozen pytest tests/integration/test_tiers -v -k milvus

# The structural half of the port contract runs in the ordinary unit job
# for both adapters. The behavioural half needs a server, so it only has
# a backend to compare against here.
- name: Derived-index port contract (both backends)
env:
EVEROS_TEST_MILVUS_URI: http://127.0.0.1:19530
run: uv run --frozen pytest tests/unit/test_infra/test_index_contract.py -v

- name: Stop Milvus
if: always()
run: docker compose -f /tmp/milvus-compose.yml down -v

package:
name: package build
runs-on: ubuntu-latest
Expand Down
33 changes: 33 additions & 0 deletions benchmarks/config_hybrid.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Benchmark configuration — single source of truth.
#
# All parameters aligned with the upstream evaluation reference so that
# benchmark numbers are directly comparable to published LoCoMo results.
# BenchmarkConfig reads this file on every run.

# --- EverOS server ---
cascade_timeout = 7200 # max seconds to wait for cascade + OME to complete
batch_size = 25 # messages per /add request (everos caps at 500)

# --- Search ---
methods = "hybrid" # comma-separated: keyword, vector, hybrid, agentic
top_k = 10 # episodes to retrieve per question
eval_owner = "speaker_a" # which speaker's memory partition to query

# --- Answer LLM ---
answer_model = "openai/gpt-4.1-mini" # LLM for generating answers from retrieved context
answer_temperature = 0.0 # deterministic answers
answer_max_tokens = 32768 # max output tokens per answer call
answer_timeout = 300.0 # per-request socket timeout (seconds)
answer_max_retries = 5 # retry budget (HTTP + empty-response retries)

# --- Judge LLM ---
judge_model = "openai/gpt-4.1-mini" # LLM judge for correctness evaluation
judge_temperature = 0.0 # deterministic judging
judge_timeout = 300.0 # per-request socket timeout (seconds)
judge_max_retries = 5 # retry budget
judge_runs = 3 # majority vote across N independent judge runs

# --- Concurrency ---
conversations_concurrency = 10 # how many conversations run at the same time
search_concurrency = 5 # concurrent /search requests per conversation
eval_concurrency = 20 # concurrent answer/judge LLM calls per conversation
33 changes: 33 additions & 0 deletions benchmarks/config_hybrid_judge4o.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Benchmark configuration — single source of truth.
#
# All parameters aligned with the upstream evaluation reference so that
# benchmark numbers are directly comparable to published LoCoMo results.
# BenchmarkConfig reads this file on every run.

# --- EverOS server ---
cascade_timeout = 7200 # max seconds to wait for cascade + OME to complete
batch_size = 25 # messages per /add request (everos caps at 500)

# --- Search ---
methods = "hybrid" # comma-separated: keyword, vector, hybrid, agentic
top_k = 10 # episodes to retrieve per question
eval_owner = "speaker_a" # which speaker's memory partition to query

# --- Answer LLM ---
answer_model = "openai/gpt-4.1-mini" # LLM for generating answers from retrieved context
answer_temperature = 0.0 # deterministic answers
answer_max_tokens = 32768 # max output tokens per answer call
answer_timeout = 300.0 # per-request socket timeout (seconds)
answer_max_retries = 5 # retry budget (HTTP + empty-response retries)

# --- Judge LLM ---
judge_model = "openai/gpt-4o-mini"
judge_temperature = 0.0 # deterministic judging
judge_timeout = 300.0 # per-request socket timeout (seconds)
judge_max_retries = 5 # retry budget
judge_runs = 3 # majority vote across N independent judge runs

# --- Concurrency ---
conversations_concurrency = 10 # how many conversations run at the same time
search_concurrency = 5 # concurrent /search requests per conversation
eval_concurrency = 20 # concurrent answer/judge LLM calls per conversation
33 changes: 33 additions & 0 deletions benchmarks/config_keyword.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Benchmark configuration — single source of truth.
#
# All parameters aligned with the upstream evaluation reference so that
# benchmark numbers are directly comparable to published LoCoMo results.
# BenchmarkConfig reads this file on every run.

# --- EverOS server ---
cascade_timeout = 7200 # max seconds to wait for cascade + OME to complete
batch_size = 25 # messages per /add request (everos caps at 500)

# --- Search ---
methods = "keyword" # comma-separated: keyword, vector, hybrid, agentic
top_k = 10 # episodes to retrieve per question
eval_owner = "speaker_a" # which speaker's memory partition to query

# --- Answer LLM ---
answer_model = "openai/gpt-4.1-mini" # LLM for generating answers from retrieved context
answer_temperature = 0.0 # deterministic answers
answer_max_tokens = 32768 # max output tokens per answer call
answer_timeout = 300.0 # per-request socket timeout (seconds)
answer_max_retries = 5 # retry budget (HTTP + empty-response retries)

# --- Judge LLM ---
judge_model = "openai/gpt-4.1-mini" # LLM judge for correctness evaluation
judge_temperature = 0.0 # deterministic judging
judge_timeout = 300.0 # per-request socket timeout (seconds)
judge_max_retries = 5 # retry budget
judge_runs = 3 # majority vote across N independent judge runs

# --- Concurrency ---
conversations_concurrency = 10 # how many conversations run at the same time
search_concurrency = 5 # concurrent /search requests per conversation
eval_concurrency = 20 # concurrent answer/judge LLM calls per conversation
33 changes: 33 additions & 0 deletions benchmarks/config_vector.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Benchmark configuration — single source of truth.
#
# All parameters aligned with the upstream evaluation reference so that
# benchmark numbers are directly comparable to published LoCoMo results.
# BenchmarkConfig reads this file on every run.

# --- EverOS server ---
cascade_timeout = 7200 # max seconds to wait for cascade + OME to complete
batch_size = 25 # messages per /add request (everos caps at 500)

# --- Search ---
methods = "vector" # comma-separated: keyword, vector, hybrid, agentic
top_k = 10 # episodes to retrieve per question
eval_owner = "speaker_a" # which speaker's memory partition to query

# --- Answer LLM ---
answer_model = "openai/gpt-4.1-mini" # LLM for generating answers from retrieved context
answer_temperature = 0.0 # deterministic answers
answer_max_tokens = 32768 # max output tokens per answer call
answer_timeout = 300.0 # per-request socket timeout (seconds)
answer_max_retries = 5 # retry budget (HTTP + empty-response retries)

# --- Judge LLM ---
judge_model = "openai/gpt-4.1-mini" # LLM judge for correctness evaluation
judge_temperature = 0.0 # deterministic judging
judge_timeout = 300.0 # per-request socket timeout (seconds)
judge_max_retries = 5 # retry budget
judge_runs = 3 # majority vote across N independent judge runs

# --- Concurrency ---
conversations_concurrency = 10 # how many conversations run at the same time
search_concurrency = 5 # concurrent /search requests per conversation
eval_concurrency = 20 # concurrent answer/judge LLM calls per conversation
33 changes: 33 additions & 0 deletions benchmarks/config_vector_judge4o.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Benchmark configuration — single source of truth.
#
# All parameters aligned with the upstream evaluation reference so that
# benchmark numbers are directly comparable to published LoCoMo results.
# BenchmarkConfig reads this file on every run.

# --- EverOS server ---
cascade_timeout = 7200 # max seconds to wait for cascade + OME to complete
batch_size = 25 # messages per /add request (everos caps at 500)

# --- Search ---
methods = "vector" # comma-separated: keyword, vector, hybrid, agentic
top_k = 10 # episodes to retrieve per question
eval_owner = "speaker_a" # which speaker's memory partition to query

# --- Answer LLM ---
answer_model = "openai/gpt-4.1-mini" # LLM for generating answers from retrieved context
answer_temperature = 0.0 # deterministic answers
answer_max_tokens = 32768 # max output tokens per answer call
answer_timeout = 300.0 # per-request socket timeout (seconds)
answer_max_retries = 5 # retry budget (HTTP + empty-response retries)

# --- Judge LLM ---
judge_model = "openai/gpt-4o-mini"
judge_temperature = 0.0 # deterministic judging
judge_timeout = 300.0 # per-request socket timeout (seconds)
judge_max_retries = 5 # retry budget
judge_runs = 3 # majority vote across N independent judge runs

# --- Concurrency ---
conversations_concurrency = 10 # how many conversations run at the same time
search_concurrency = 5 # concurrent /search requests per conversation
eval_concurrency = 20 # concurrent answer/judge LLM calls per conversation
12 changes: 12 additions & 0 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,15 @@ max_concurrent = 5
#
# [lancedb]
# read_consistency_seconds = 5.0
#
# [index]
# backend = "lancedb" # or "milvus"
#
# # Required only when index.backend = "milvus". Install with:
# # pip install "everos[milvus]"
# [milvus]
# uri = "http://127.0.0.1:19530" # Milvus Server or Zilliz Cloud endpoint
# token = "" # e.g. "root:Milvus" or cloud token
# db_name = ""
# consistency_level = "Session"
# collection_prefix = "everos"
15 changes: 8 additions & 7 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ bare FastAPI `detail`); see [Errors](#errors).

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

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

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

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

### GetMemoryType

Expand Down Expand Up @@ -613,7 +614,7 @@ scope.
this `(session_id, app_id, project_id)`, or it was already flushed).

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

Expand Down
26 changes: 15 additions & 11 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
│ + reflection + strategies + get + events │
├──────────────────────────────────────────────────────┤
│ infra/persistence (Storage adapters; infra/ may host other adapter types) │
│ markdown + sqlite + lancedb │
│ markdown + sqlite + derived index │
└──────────────────────────────────────────────────────┘

Cross-cutting (used by all layers, depends on none):
Expand Down Expand Up @@ -68,8 +68,8 @@ layers = [
└────────────────────────────────────────────────────────────────┘

┌──────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Markdown │ │ SQLite │ │ LanceDB │
│ (truth) │ │ (state) │ │ (index) │
│ Markdown │ │ SQLite │ │ Derived index │
│ (truth) │ │ (state) │ │ LanceDB/Milvus │
├──────────────┤ ├──────────────┤ ├─────────────────┤
│ entries + │ │ change queue │ │ vector ANN │
│ frontmatter │ │ + state/LSN │ │ BM25 (Tantivy) │
Expand All @@ -78,7 +78,7 @@ layers = [
└──────────────┘ └──────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
memory-root/ .index/sqlite/ .index/lancedb/
memory-root/ .index/sqlite/ .index/<backend>/
(truth source) (system data) (rebuildable)
```

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

**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.
**Key guarantee**: md write is strongly consistent (fsync). The derived
index is eventually consistent. Index backend unavailability does not block
response — changes buffer in the SQLite `md_change_state` queue, replayed on
recovery.

## Read path

Expand All @@ -115,8 +118,8 @@ User query
1. service.search
│
▼
2. memory.search (hybrid) single LanceDB query =
BM25 + vector ANN + scalar filter
2. memory.search (hybrid) BM25 + vector ANN + scalar filter
through the configured index backend
│
▼
3. (optional) read md original markdown for context
Expand All @@ -139,12 +142,13 @@ extract/

### `memory/cascade/`

Daemon that watches markdown changes and syncs to LanceDB:
Daemon that watches markdown changes and syncs to the configured derived
index backend:

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

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

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