Skip to content

vec0 module not loaded in worker connections (Windows) — embeddings reindex fails with "no such module: vec0" #767

Closed

Description

@andrewmautone

Environment

  • basic-memory 0.20.3 (installed via uv tool install basic-memory)
  • Python 3.12.11 (uvx-managed CPython for Windows)
  • OS: Windows 11 Pro 26200
  • sqlite-vec 0.1.9
  • Default config (semantic_search_enabled: true, fastembed bge-small-en-v1.5)

Symptom

basic-memory reindex -p <project> populates the knowledge graph (entities, relations, observations) but generates 0 embeddings. Status panel reports:

Indexed: 0/258
Status: Reindex recommended
Reason: SQLite vector tables exist but sqlite-vec is unavailable in this Python environment — install/update basic-memory, then run: bm reindex --embeddings

Running the suggested fix bm reindex -p vault --embeddings raises:

sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such module: vec0
[SQL: DELETE FROM search_vector_embeddings WHERE rowid IN (SELECT id FROM search_vector_chunks WHERE project_id = ? AND entity_id NOT IN (SELECT id FROM entity WHERE project_id = ?))]

Diagnosis

sqlite-vec loads correctly in the same uvx environment when invoked directly:

$ uvx --from basic-memory python -c "
import sqlite3, sqlite_vec
conn = sqlite3.connect(':memory:')
conn.enable_load_extension(True)
sqlite_vec.load(conn)
print(conn.execute('SELECT vec_version()').fetchone())
"
('v0.1.9',)

So the package is installed and loadable. The bug appears to be that basic-memory's worker SQLAlchemy connections don't call enable_load_extension + sqlite_vec.load() before issuing queries that depend on the vec0 virtual table.

Suggested fix

Hook a connect event on the SQLAlchemy engine to enable extension loading and load sqlite_vec on each new connection, similar to:

from sqlalchemy import event
import sqlite_vec

@event.listens_for(engine, "connect")
def _on_connect(dbapi_conn, _):
    dbapi_conn.enable_load_extension(True)
    sqlite_vec.load(dbapi_conn)
    dbapi_conn.enable_load_extension(False)

Side note (separate issue, can file if useful)

basic-memory project default <name> is currently a no-op — returns "No default project is currently set" even after project add, and editing default_project directly in ~/.basic-memory/config.json gets reverted on the next CLI invocation.

Activity

  1. phernandez commented on Apr 29, 2026

    @phernandez
    Member

    Thanks for the detailed reports — and special thanks to @mrhoga for tracing the exact bypass path in search_service.py. That diagnosis was correct for v0.20.3.

    Status on main

    The specific code path you identified has already been refactored. In v0.20.3, _purge_stale_search_rows ran the embedding cleanup via self.repository.execute_query(...), which pulled a fresh connection from the SQLAlchemy pool with no vec0 loaded — exactly the failure mode you described.

    On main, that branch was rewritten in PR #731 to delegate to a new method on the repository:

    src/basic_memory/services/search_service.py:599-610 (current main):

    # SQLite vec has no CASCADE — must delete embeddings before chunks
    if isinstance(self.repository, SQLiteSearchRepository):
        await self.repository.delete_stale_vector_rows()
    else:
        # Postgres CASCADE handles embedding deletion automatically
        await self.repository.execute_query(...)

    And delete_stale_vector_rows opens its own session and loads sqlite-vec before issuing the DELETE — src/basic_memory/repository/sqlite_search_repository.py:591-619:

    async def delete_stale_vector_rows(self) -> None:
        await self._ensure_vector_tables()
        async with db.scoped_session(self.session_maker) as session:
            await self._ensure_sqlite_vec_loaded(session)   # <-- the missing piece in 0.20.3
            ...
            await session.execute(
                text("DELETE FROM search_vector_embeddings WHERE rowid IN (...)"),
                params,
            )

    The wider invariant (every method that touches vec0 must load sqlite-vec on its own session) is now consistent across the repository — _ensure_sqlite_vec_loaded is called in delete_stale_vector_rows, _ensure_vector_tables, and the per-entity vector sync paths.

    Suggested next step

    Could you re-test against main (or wait for the next release after v0.20.3)? The existing integration test test_reindex_vectors (tests/services/test_search_service.py:1213) exercises the full reindex_vectors → _purge_stale_search_rows → delete_stale_vector_rows chain end-to-end with semantic enabled, which is what your reproduction hits.

    If bm reindex --embeddings still fails for you on main, please reopen with the new traceback — the only ways the failure could survive that refactor are (a) a different vec0-touching SQL still routed through execute_query, or (b) _ensure_sqlite_vec_loaded failing silently on Windows. Both would be useful to see.

    I'll close as fixed pending the next release. @andrewmautone — your "side note" about bm project default is a real separate issue; please do file it if you have a moment.

  2. phernandez commented on Apr 29, 2026

    @phernandez
    Member

    Duplicate of #735 — same root cause, fixed on main. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions