Repository navigation
Description
Summary
mcpx's catalog.VectorIndex accepts embedding vectors of any dimension at write time — Put returns nil, Import counts them as imported, and the daemon's semantic search stores whatever the configured embedder produces — but the SQLite reload path silently discards every stored vector whose dimension is not exactly VectorDim (384). With a remote embedder configured (the default model, text-embedding-3-small, returns 1536 dimensions), every tool vector the daemon caches is thrown away on the next start, with no error and no log. In-process, the same dimension gap silently zeroes semantic scores: CosineSimilarity returns exactly 0 for mismatched lengths, so a query embedded by one backend against tool vectors cached under another scores every tool 0 and semantic search degrades to keyword-only without any signal.
Evidence (verified on main ed8e5910)
pkgs/mcpx/internal/catalog/vector.go:159-182—loadFromDBloads a row only whenlen(blob) == VectorDim*4(:172); any other dimension is skipped silently (no log, no error surfaced —NewVectorIndexignores the outcome entirely).pkgs/mcpx/internal/catalog/vector.go:204-222—Putstores a vector of any length in memory and on disk and never validateslen(vec); itserrorreturn is never used for this.pkgs/mcpx/internal/catalog/embeddings.go:346-376—Importaccepts and counts vectors of any length and persists them as blobs thatloadFromDBwill drop on the next open;Export(embeddings.go:324-343) stampsDim: VectorDimregardless of the dimensions actually stored.pkgs/mcpx/internal/catalog/embeddings.go:67-127—RemoteEmbedder.EmbedL2-normalizes but never checks the returned dimension: the default modeltext-embedding-3-smallreturns 1536 floats, not 384.CascadeEmbedder.Embed(embeddings.go:268-302) tries remote first inautomode.pkgs/mcpx/internal/catalog/vector.go:20-29—CosineSimilarityreturns 0 whenlen(v) != len(other). The daemon's hybrid search (internal/daemon/registry.go:534) combines that 0 into the score with no mismatch signal; a tool with no keyword score is then dropped by thecombined > 15.0 || kwScore > 0gate at registry.go:542.- The write path that populates the cache with embedder-dimension vectors is live:
internal/daemon/registry.go:524-531embeds each uncached tool with the configured embedder and callsvIdx.Put(t.Function, tVec)(:531, ignoring Put's error).
Probe (laptop, real internal/catalog package)
Probe test in a detached laptop worktree at the same HEAD (vector.go sha256 5e901780, embeddings.go sha256 19044242 — byte-identical to this clone), run twice with identical results (probe + log: hidden_files/probe-2026-10-28-laneB-vecdim/):
- CTRL:
Puta 384-dim vector → close → reopen → present. - BUG-A:
Puta 1536-dim vector returnserr=niland is readable in-process; after close → reopen it is missing — silently dropped byloadFromDB. - BUG-B:
Importof a payload carrying one 1536-dim vector reportscount=1, err=nil; after reopen it is missing. - BUG-C:
CosineSimilarity(1536-dim, 384-dim)= 0 exactly, while identical 384-dim vectors score > 0 — the silent in-process half of the same gap.
Why it matters
The vector index is the semantic-search cache for mcpx search: with a remote (or WASM/Ollama) embedder configured, the daemon pays to embed every tool once, persists the results, and then silently loses all of them on restart — and any mid-flight backend switch (config change, remote outage falling back to local in the cascade) zeroes the semantic half of the hybrid score for already-cached tools. Nothing logs, errors, or tests the mismatch: existing tests (embeddings_test.go, vector_test.go) only ever exercise 384-dim local vectors.
Acceptance criteria
- A vector whose dimension is not the index's dimension cannot silently enter the persistent index:
Put/Importeither reject it with an error that names the expected and actual dimensions, or the index records and reloads vectors together with their dimension — one behaviour, pinned by tests. -
loadFromDBno longer drops rows silently: unloadable rows are surfaced (error or logged count). - A restart round-trip test with a non-384 embedder vector exists and passes (either the vector survives, or the write failed loudly at write time).
- The daemon search path does not score a dimension-mismatched pair as a plain 0 without a signal (fallback to re-embed, or an explicit debug/log trace).
- Existing catalog tests pass unchanged for the 384-dim local path.
Filed by new-issue-creator-code-b. Whether to pin the index to one dimension per DB (reject at write) vs store per-vector dimensions is the triage call — the defect is the silence, which either ruling removes.
Activity
developing-today commentedon Oct 28, 2026
CODE-660
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
18 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · parked
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done