The embedding_cache collection deduplicates embeddings by content hash (visible in mongodb-embedding-retry.ts and the KB import path). The cache key structure does not include a modelId or modelVersion component.
Consequence
If voyage-4-large is updated server-side (Voyage pushes silent model updates occasionally), or if an operator switches the default embedding model via config, the cache silently returns vectors from the old model for any previously-seen content.
A dimension mismatch would be caught at query time. But semantic drift — same output dimensions, different embedding space — will not be caught. ANN results degrade invisibly, and the operator has no signal that recall quality has dropped. This is especially subtle because it only affects content that was seen before the model change; new content gets fresh vectors and appears to work fine.
Suggested fix
Add modelId: string to the cache document schema and include it in the cache key:
// Before
const cacheKey = `hash:${contentHash}`
// After
const cacheKey = `hash:${contentHash}:${modelId}`
This makes the cache naturally namespace-correct across model versions. A one-time migration can backfill existing entries with the then-current model ID (or simply expire them by dropping the collection, since re-embedding is the only safe option anyway).
Alternatively, add a monotonic cacheVersion counter to config that is bumped whenever the embedding model changes. Use it as a cache namespace prefix — stale entries from prior versions are automatically ignored without a migration.
Found this while building a pgvector-based memory layer that uses the same Voyage embedding pipeline. Happy to send a PR if the approach seems right.
The
embedding_cachecollection deduplicates embeddings by content hash (visible inmongodb-embedding-retry.tsand the KB import path). The cache key structure does not include amodelIdormodelVersioncomponent.Consequence
If
voyage-4-largeis updated server-side (Voyage pushes silent model updates occasionally), or if an operator switches the default embedding model via config, the cache silently returns vectors from the old model for any previously-seen content.A dimension mismatch would be caught at query time. But semantic drift — same output dimensions, different embedding space — will not be caught. ANN results degrade invisibly, and the operator has no signal that recall quality has dropped. This is especially subtle because it only affects content that was seen before the model change; new content gets fresh vectors and appears to work fine.
Suggested fix
Add
modelId: stringto the cache document schema and include it in the cache key:This makes the cache naturally namespace-correct across model versions. A one-time migration can backfill existing entries with the then-current model ID (or simply expire them by dropping the collection, since re-embedding is the only safe option anyway).
Alternatively, add a monotonic
cacheVersioncounter to config that is bumped whenever the embedding model changes. Use it as a cache namespace prefix — stale entries from prior versions are automatically ignored without a migration.Found this while building a pgvector-based memory layer that uses the same Voyage embedding pipeline. Happy to send a PR if the approach seems right.