Summary
hivemind_find (vector search path) returns zero results for any query, even when the underlying embeddings exist and have strong cosine similarity to the query — well above the configured 0.3 threshold. hivemind_stats confirms embeddings are present (ollama_available: true, embeddings: N), and manually computing cosine similarity between the stored content and query text via Ollama directly returns scores like 0.80, 0.60, 0.61 — but the plugin's own vector search returns nothing.
Environment
opencode-swarm-plugin v0.63.2 (installed via bun add -g opencode-swarm-plugin, confirmed fresh reinstall — bug reproduces on a clean install, not a local corruption issue)
- Ollama v0.30.10,
mxbai-embed-large pulled and confirmed working via direct /api/embeddings calls
- macOS (Darwin), Bun v1.3.8
Repro steps
hivemind_store({ information: "The katas repo uses .editorconfig and .npmrc for consistent tooling across contributors, but has no package.json yet — it's a fresh scaffold with just docs/, README, and LICENSE.", tags: "katas,scaffold" })
hivemind_stats() → confirms { memories: 1, embeddings: 1, healthy: true, ollama_available: true }
hivemind_find({ query: "katas repo editorconfig npmrc package.json" }) → returns { results: [], count: 0 }
Even an exact keyword match against the stored content returns no results.
Independent verification
Bypassing the plugin entirely, I generated embeddings directly against Ollama and computed cosine similarity by hand:
# mxbai-embed-large via http://localhost:11434/api/embeddings
query = "katas repo editorconfig npmrc package.json"
stored = "The katas repo uses .editorconfig and .npmrc for consistent tooling..."
cosine_similarity(embed(query), embed(stored)) # => 0.7993
0.80 similarity is well above the threshold: 0.3 hardcoded in store.search() (packages/swarm-mail/src/memory/store.ts). The embedding model and Ollama integration are functioning correctly — the bug is isolated to the vector search query path itself.
Suspected cause
store.search() uses vector_top_k('idx_memories_embedding', vector(...), limit*2) (libSQL ANN index) joined back to the memories table, filtered by (1 - vector_distance_cos(...)) >= threshold. Given embeddings are present and well above threshold but zero rows are returned, this points to the idx_memories_embedding vector index not existing, being stale, or not populating correctly for newly-inserted rows via libsql_vector_idx.
Confirmed NOT the cause
- Not an Ollama/embedding model issue (verified via direct API + manual cosine similarity)
- Not a threshold issue (0.80 similarity vs. 0.3 threshold)
- Not a stale local install (reproduces on a fresh
bun remove -g + bun add -g opencode-swarm-plugin reinstall of the same published v0.63.2)
- Not a
collection filter issue (reproduces with and without explicit collection: "default")
Impact
Semantic memory search (hivemind_find without fts: true) is non-functional for all users on v0.63.2, silently returning empty results with no error — likely masked in practice by the automatic FTS fallback path only triggering on Ollama unavailability, not on vector search returning zero rows.
Further investigation: likely root cause
Traced deeper into packages/swarm-mail/src/memory/libsql-schema.ts and store.ts. Notable findings:
-
validateLibSQLMemorySchema() is dead code. It's exported and exercised in its own test file, but it's never called anywhere in the production init path (memory-tools.ts / getMemoryAdapter). It also doesn't check for the existence of idx_memories_embedding at all — only tables/columns/FTS5. So even if it were wired in, it would report a healthy schema despite a missing or stale vector index.
-
Most likely cause: libSQL's libsql_vector_idx() ANN index isn't guaranteed to be incrementally maintained on INSERT. Unlike a normal B-tree index, libSQL's vector index (particularly in embedded/local file mode, as opposed to Turso's hosted infrastructure) is known to require rebuilding after writes rather than updating live. This matches the repro exactly: the memories table + idx_memories_embedding already existed from a prior session; new rows were INSERTed via hivemind_store; the rows and embeddings are verifiably present in the table itself (hivemind_stats, direct cosine similarity against the raw embedding data); but vector_top_k('idx_memories_embedding', ...) returns nothing for them, because the ANN index was never updated to include the new rows.
Proposed fix
In order of preference:
-
Best: drop the ANN index dependency for embedded use, brute-force scan instead. Memory stores here are small (hundreds to low-thousands of rows) — a full scan is fast enough and eliminates the index-staleness class of bug entirely. Replace the vector_top_k(...) JOIN memories query in store.ts's search() with a direct scan:
SELECT m.*, vector_distance_cos(m.embedding, vector(?)) as distance
FROM memories m
WHERE m.embedding IS NOT NULL
AND (1 - vector_distance_cos(m.embedding, vector(?))) >= ?
-- + collection/decay filters
ORDER BY distance ASC
LIMIT ?
This pattern already exists as a fallback branch elsewhere in store.ts (~line 289) — it just needs to become the primary path for local/embedded libSQL instead of the ANN-indexed path.
-
Minimal: reindex after every write. After store(), run REINDEX idx_memories_embedding (or DROP INDEX + recreate) so newly-inserted vectors get folded into the ANN index. Cheap correctness patch, but adds write-path latency and doesn't fully resolve the underlying reliability question under concurrent writers.
-
Diagnostic: make validateLibSQLMemorySchema() load-bearing. Wire it into the adapter init path, and extend it to compare vector_top_k result count against COUNT(id) for a cheap synthetic query, auto-healing (reindexing) on divergence. This also fixes the dead-code issue where the validator exists but is never invoked.
Happy to open a PR for option 1 if that's the preferred direction — it's a small, contained change to store.ts's search() method.
Summary
hivemind_find(vector search path) returns zero results for any query, even when the underlying embeddings exist and have strong cosine similarity to the query — well above the configured 0.3 threshold.hivemind_statsconfirms embeddings are present (ollama_available: true,embeddings: N), and manually computing cosine similarity between the stored content and query text via Ollama directly returns scores like 0.80, 0.60, 0.61 — but the plugin's own vector search returns nothing.Environment
opencode-swarm-pluginv0.63.2 (installed viabun add -g opencode-swarm-plugin, confirmed fresh reinstall — bug reproduces on a clean install, not a local corruption issue)mxbai-embed-largepulled and confirmed working via direct/api/embeddingscallsRepro steps
hivemind_store({ information: "The katas repo uses .editorconfig and .npmrc for consistent tooling across contributors, but has no package.json yet — it's a fresh scaffold with just docs/, README, and LICENSE.", tags: "katas,scaffold" })hivemind_stats()→ confirms{ memories: 1, embeddings: 1, healthy: true, ollama_available: true }hivemind_find({ query: "katas repo editorconfig npmrc package.json" })→ returns{ results: [], count: 0 }Even an exact keyword match against the stored content returns no results.
Independent verification
Bypassing the plugin entirely, I generated embeddings directly against Ollama and computed cosine similarity by hand:
0.80 similarity is well above the
threshold: 0.3hardcoded instore.search()(packages/swarm-mail/src/memory/store.ts). The embedding model and Ollama integration are functioning correctly — the bug is isolated to the vector search query path itself.Suspected cause
store.search()usesvector_top_k('idx_memories_embedding', vector(...), limit*2)(libSQL ANN index) joined back to thememoriestable, filtered by(1 - vector_distance_cos(...)) >= threshold. Given embeddings are present and well above threshold but zero rows are returned, this points to theidx_memories_embeddingvector index not existing, being stale, or not populating correctly for newly-inserted rows vialibsql_vector_idx.Confirmed NOT the cause
bun remove -g+bun add -g opencode-swarm-pluginreinstall of the same published v0.63.2)collectionfilter issue (reproduces with and without explicitcollection: "default")Impact
Semantic memory search (
hivemind_findwithoutfts: true) is non-functional for all users on v0.63.2, silently returning empty results with no error — likely masked in practice by the automatic FTS fallback path only triggering on Ollama unavailability, not on vector search returning zero rows.Further investigation: likely root cause
Traced deeper into
packages/swarm-mail/src/memory/libsql-schema.tsandstore.ts. Notable findings:validateLibSQLMemorySchema()is dead code. It's exported and exercised in its own test file, but it's never called anywhere in the production init path (memory-tools.ts/getMemoryAdapter). It also doesn't check for the existence ofidx_memories_embeddingat all — only tables/columns/FTS5. So even if it were wired in, it would report a healthy schema despite a missing or stale vector index.Most likely cause: libSQL's
libsql_vector_idx()ANN index isn't guaranteed to be incrementally maintained onINSERT. Unlike a normal B-tree index, libSQL's vector index (particularly in embedded/local file mode, as opposed to Turso's hosted infrastructure) is known to require rebuilding after writes rather than updating live. This matches the repro exactly: thememoriestable +idx_memories_embeddingalready existed from a prior session; new rows wereINSERTed viahivemind_store; the rows and embeddings are verifiably present in the table itself (hivemind_stats, direct cosine similarity against the raw embedding data); butvector_top_k('idx_memories_embedding', ...)returns nothing for them, because the ANN index was never updated to include the new rows.Proposed fix
In order of preference:
Best: drop the ANN index dependency for embedded use, brute-force scan instead. Memory stores here are small (hundreds to low-thousands of rows) — a full scan is fast enough and eliminates the index-staleness class of bug entirely. Replace the
vector_top_k(...) JOIN memoriesquery instore.ts'ssearch()with a direct scan:This pattern already exists as a fallback branch elsewhere in
store.ts(~line 289) — it just needs to become the primary path for local/embedded libSQL instead of the ANN-indexed path.Minimal: reindex after every write. After
store(), runREINDEX idx_memories_embedding(orDROP INDEX+ recreate) so newly-inserted vectors get folded into the ANN index. Cheap correctness patch, but adds write-path latency and doesn't fully resolve the underlying reliability question under concurrent writers.Diagnostic: make
validateLibSQLMemorySchema()load-bearing. Wire it into the adapter init path, and extend it to comparevector_top_kresult count againstCOUNT(id)for a cheap synthetic query, auto-healing (reindexing) on divergence. This also fixes the dead-code issue where the validator exists but is never invoked.Happy to open a PR for option 1 if that's the preferred direction — it's a small, contained change to
store.ts'ssearch()method.