Skip to main content

Switching embedding models or dimensions on an existing brain

ModusBrain stores embeddings in a fixed-dimension vector(N) column on content_chunks. If you switch to a model with a different dimension (e.g. openai:text-embedding-3-large 1536 → zeroentropyai:zembed-1 1280, or voyage:voyage-4-large 2048), the on-disk column type doesn’t change automatically. modusbrain init, modusbrain doctor, and modusbrain embed --stale all detect this mismatch and refuse to silently proceed. This doc is the recipe they point at.

Same-dimension model swaps (v0.41.31.0 — automatic)

If you switch to a different model at the same dimension count (e.g. one 1536-dim provider to another, or a re-tuned model that keeps its width), the column type doesn’t change, so no ALTER/wipe recipe is needed. As of v0.41.31.0, modusbrain stamps an embedding-provenance signature (<provider:model>:<dims>) onto each page when its chunks are embedded. After you point the config at the new model, the stored signatures differ from the current one, and modusbrain embed --stale re-embeds exactly those pages:
Under federated_v2, the same drift is picked up by the per-source embed-backfill jobs that modusbrain sync --all enqueues (capped $X/source/24h). Grandfather: pages embedded before v0.41.31.0 carry a NULL signature and are NEVER flagged stale, so upgrading to v0.41.31.0 does NOT trigger a whole-corpus re-embed. Signatures only get stamped going forward. A dimension change still requires the wipe-and-reinit (PGLite) or column-alter (Postgres) recipe below — the on-disk vector(N) width genuinely has to change.

Why we don’t do this automatically

Switching dimensions requires:
  1. Dropping the HNSW vector index (pgvector won’t survive an ALTER COLUMN TYPE).
  2. Wiping every existing embedding (the old vectors are unusable in the new space — and pgvector refuses to cast them across dimensions, so this must happen before the alter).
  3. Altering the column type (Postgres only — PGLite cannot do this).
  4. Re-embedding the entire corpus (can take hours on a 50K-page brain and costs $1-100 in API calls depending on model).
  5. Conditionally recreating the index (HNSW supports up to 2000 dimensions per pgvector; above that you must use exact scans).
That’s not an upgrade-time auto-run. It’s a deliberate, expensive operation. Run it when you’ve decided you actually want the new model.

PGLite (default install)

PGLite cannot ALTER COLUMN TYPE vector(N). pgvector ships as embedded WASM, not a native extension, and the WASM build rejects the column-type alter with could not access file "$libdir/vector". The SQL recipe below works against Postgres only. The path that works on PGLite is wipe-and-reinit. v0.37 ships a single-command wrapper:
This backs up the existing brain to <path>.bak, runs modusbrain init with the new flags (preserving every other field in ~/.modusbrain/config.json), and re-syncs the brain repo. Add --no-sync to skip the resync, --yes to skip the TTY confirmation, --json for structured output. Equivalent by hand:
If your brain repo is large enough that re-syncing from disk is expensive (>50K pages), see the Postgres section below — migrating to Postgres temporarily lets you run the SQL recipe, then migrate back to PGLite. MODUSBRAIN_HOME users: substitute the active database path (or use modusbrain config get database_path to find it).

Postgres (Supabase / self-hosted)

Postgres supports the in-place column alter. Replace <NEW_DIMS> with your target dimension count.
Then re-init config with the new model:
And re-embed:

A note on modusbrain config set

Pre-v0.37 docs recommended modusbrain config set embedding_model X to switch models. This is a no-op for the embed pipeline. config set writes the DB plane; the embed gateway reads the file plane (~/.modusbrain/config.json). The pre-v0.37 recipe shipped the lie because the contract wasn’t surfaced. As of v0.37, modusbrain config set embedding_model and modusbrain config set embedding_dimensions REFUSE and print the wipe-and-reinit recipe. To change schema-sizing fields, use modusbrain init (PGLite) or the SQL recipe (Postgres). Both update the file plane AND the schema together.

Verify

After the recipe lands, modusbrain doctor --fast should report green and modusbrain doctor should pass the embedding_width_consistency check:
If it doesn’t, file an issue with the doctor output and the steps you ran.

v0.37+ followups

  • Auto-fallback to alternative embedding providers when the primary fails quota/auth. Tracked; requires explicit --try-fallback consent because mixing provider vectors silently corrupts retrieval.