From 521772c0826cf2df24a2e2b6140f65580eae82f8 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 1 Jul 2026 01:15:46 -0300 Subject: [PATCH] fix(memory): enabling Qdrant activates it as the engine + inline guidance (#5597) (#5741) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(memory): enabling Qdrant now activates it as the engine + inline guidance (#5597) Enabling Qdrant in the Engine tab was inert: retrieval only routes to Qdrant when memoryVectorStore === "qdrant" (the default "auto" never selects it), and the card only wrote qdrantEnabled — nothing set the engine selector, and there is no UI for it. So users configured Qdrant, saw "enabled", but it was never actually used. - PUT /api/settings/qdrant now sets memoryVectorStore alongside the toggle: enable -> "qdrant", disable -> "auto". Editing other fields leaves it untouched. - Add inline guidance to QdrantConfigCard: a Tier-1-vs-Tier-2 banner + per-field help (host, collection, embedding model). Note there is no "vector dimension" or "distance metric" field: dimension is auto-detected from the embedder, distance is always Cosine. - Document the real behavior in MEMORY.md: engine gate, no back-fill of existing memories, dimension auto-detect, Cosine-only, API-key-only auth. Tests: tests/integration/qdrant-routes.test.ts — enable->qdrant, disable->auto, and field-edit-without-enabled leaves the engine untouched (TDD: red -> green). Closes #5597 * fix(memory): invalidate memory-settings cache on Qdrant toggle (#5597) The PUT handler wrote memoryVectorStore to the DB but retrieval reads through getMemorySettings(), a module-level cache. Without busting it, the engine switch did not take effect until a process restart (the DB said qdrant, retrieval kept routing to sqlite-vec). Now calls invalidateMemorySettingsCache() after the write, mirroring src/app/api/settings/memory/route.ts. Regression test warms the cache, toggles via the route, and asserts getMemorySettings().vectorStore flips to qdrant (fails without the invalidate call). --- docs/frameworks/MEMORY.md | 28 +++++- .../memory/components/QdrantConfigCard.tsx | 8 ++ src/app/api/settings/qdrant/route.ts | 14 ++- src/i18n/messages/en.json | 4 + tests/integration/qdrant-routes.test.ts | 97 +++++++++++++++++++ 5 files changed, 149 insertions(+), 2 deletions(-) diff --git a/docs/frameworks/MEMORY.md b/docs/frameworks/MEMORY.md index 306eac9ca0..14f35f68a6 100644 --- a/docs/frameworks/MEMORY.md +++ b/docs/frameworks/MEMORY.md @@ -223,7 +223,13 @@ chronological order if the FTS table is missing or the FTS query throws. ### Optional: Qdrant (vector store tier 2) `src/lib/memory/qdrant.ts` implements an optional Qdrant integration as tier 2 -vector store. Enabled via `qdrantEnabled` in settings / toggle in Engine tab. +vector store. Retrieval only routes to Qdrant when the engine selector +`memoryVectorStore === "qdrant"` — the default `"auto"` (and `"sqlite-vec"`) +**never** select Qdrant. The Engine-tab toggle sets **both** `qdrantEnabled` and +`memoryVectorStore` together: enabling makes Qdrant the primary store, disabling +resets to `"auto"` (#5597 — before that fix, enabling was inert because nothing +wrote the engine selector). If Qdrant is unreachable or returns nothing, retrieval +falls back to sqlite-vec → FTS5. - `upsertSemanticMemoryPoint()` — embed `key + content` with the configured embedding model, ensure the collection exists (creates cosine-distance @@ -251,6 +257,26 @@ routes under `src/app/api/settings/qdrant/` are all wired as of v3.8.6: | `/api/settings/qdrant/cleanup` | `POST` | Remove expired / old points | | `/api/settings/qdrant/embedding-models` | `GET` | List available embedding models | +**Behavior notes (what to expect):** + +- **Engine selection** — enabling Qdrant in the Engine tab makes it the primary + store (sets `memoryVectorStore="qdrant"`); disabling resets to `"auto"` (#5597). +- **No back-fill** — only memories created/updated **after** Qdrant is enabled are + written to it (fire-and-forget dual-write). Pre-existing SQLite memories are **not** + migrated; "Reindex Now" rebuilds the sqlite-vec index only, not Qdrant. +- **Vector dimension is auto-detected** from the actual embedding on first use — there + is no dimension field to fill in. Changing the embedding model after a collection + exists is **not** auto-handled: the existing collection is left untouched, dimension- + mismatched writes/searches fail and fall back to sqlite-vec. Recreate the collection + (new name, or delete it in Qdrant) to switch embedders. +- **Distance metric** — always **Cosine** (hardcoded on collection creation; not + configurable). +- **Auth** — API key only (sent as the `api-key` header; optional for unauthenticated + local Docker). JWT/RBAC are not used. +- **Config fields** — the UI exposes `host`, `port`, `collection`, `embeddingModel`, + `apiKey`. `vectorSize` / `hnswEfConstruct` are env/DB only and `vectorSize` is not + used for collection creation (dimension comes from the embedding). + ### Vector quantization (int8 — opt-in, both backends) Both vector backends support **opt-in int8 quantization** to cut the memory diff --git a/src/app/(dashboard)/dashboard/memory/components/QdrantConfigCard.tsx b/src/app/(dashboard)/dashboard/memory/components/QdrantConfigCard.tsx index 30b44ffeca..68ab2231d1 100644 --- a/src/app/(dashboard)/dashboard/memory/components/QdrantConfigCard.tsx +++ b/src/app/(dashboard)/dashboard/memory/components/QdrantConfigCard.tsx @@ -205,6 +205,11 @@ export default function QdrantConfigCard() { + {/* Tier 1 vs Tier 2 guidance */} +
+ {t("qdrant.banner")} +
+ {/* Enable toggle + test connection */}
@@ -271,6 +276,7 @@ export default function QdrantConfigCard() { placeholder="http://127.0.0.1" className="w-full px-3 py-2 rounded-lg bg-background border border-border text-sm font-mono focus:outline-none focus:ring-1 focus:ring-emerald-500" /> +

{t("qdrant.hostHelp")}

@@ -292,6 +298,7 @@ export default function QdrantConfigCard() { placeholder="omniroute_memory" className="w-full px-3 py-2 rounded-lg bg-background border border-border text-sm font-mono focus:outline-none focus:ring-1 focus:ring-emerald-500" /> +

{t("qdrant.collectionHelp")}