Switching embedding models or dimensions on an existing brain
ModusBrain stores embeddings in a fixed-dimensionvector(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 noALTER/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:
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:- Dropping the HNSW vector index (pgvector won’t survive an
ALTER COLUMN TYPE). - 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).
- Altering the column type (Postgres only — PGLite cannot do this).
- Re-embedding the entire corpus (can take hours on a 50K-page brain and costs $1-100 in API calls depending on model).
- Conditionally recreating the index (HNSW supports up to 2000 dimensions per pgvector; above that you must use exact scans).
PGLite (default install)
PGLite cannotALTER 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:
<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:
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.
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:
v0.37+ followups
- Auto-fallback to alternative embedding providers when the primary
fails quota/auth. Tracked; requires explicit
--try-fallbackconsent because mixing provider vectors silently corrupts retrieval.