v0.3.0 — voyage-4 migration
Embedding-model migration release. Default provider model switches from voyage-code-3 to voyage-4; existing databases keep working but the daemon now refuses to start with a stored vector / configured model mismatch until embeddings are recomputed.
Breaking changes
- Default embedding model is now
voyage-4. Databases populated withvoyage-code-3vectors trip the new[6020] EmbeddingModelMismatchguard at server start. Run `engram reembed` once after upgrading, then restart the daemon. - `EmbeddingProvider::embed` trait signature gained an `input_type: Option<&str>` parameter. Internal trait with a single real implementor (Voyage); deterministic test fixtures ignore the value. Pre-1.0 deliberate break — out-of-tree implementors must update their method signature.
- `EmbeddingCache::{get, insert}` signatures changed to accept `input_type`. Internal API.
Dimension stays at `1024` — `voyage-4` API default matches, on-disk HNSW geometry is unchanged. Within the voyage-4 family (`voyage-4-large` / `voyage-4` / `voyage-4-lite` / `voyage-4-nano`) embeddings share one space, so future in-family switches won't require another reembed.
New features
- `engram reembed [--force]` CLI recomputes embeddings for every stored memory under the active provider. Replaces vectors in HNSW (delete + insert per memory), writes fresh BLOBs to SQLite with `indexed=true`, clears the in-memory cache at the start, and records the active model in `schema_meta.embedding_model` only when every memory succeeded. Per-memory provider/HNSW failure flips `indexed=0` for retry and the first failure is surfaced to stderr; database write failures abort the run.
- Server startup guard against silent model drift. `server::run` compares the configured `embedding.model` against `schema_meta.embedding_model` after state init and refuses to start with `[6020]` on mismatch. Bootstrap on an empty marker writes the configured model; a stderr warning fires if memories already exist (legacy upgrade path).
- `embedding.output_dimension` config knob for Voyage-4 Matryoshka truncation (256 / 512 / 1024 / 2048). Omit to use the API default (1024). Must match `[hnsw].dimension` when set.
- `EmbeddingCache` keyed by `(text, input_type)` instead of text alone, so a memory stored as a document and a query of the same text no longer collide on a single cache slot.
Migration recipe
```bash
1. update binary (release build)
cargo install --path crates/engram-core --force
or once 0.3.0 hits crates.io: cargo install engram-memory
2. switch the model in your global config
sed -i '' 's/voyage-code-3/voyage-4/' ~/.engram/engram.toml
3. in each project that has memories
cd
engram server # fails fast with [6020] EmbeddingModelMismatch — expected
engram reembed # recomputes every memory with voyage-4
engram server # now starts cleanly
```
Reembed runs at roughly `50ms × 3 fields × N memories` under Voyage API latency. Budget a few minutes for a thousand records. Partial failures (e.g. a transient rate-limit on some rows) leave their `indexed=0` so a rerun retries only the leftovers — `schema_meta` is only updated on a clean run.
Users who want to stay on `voyage-code-3` can set `embedding.model = "voyage-code-3"` in `engram.toml` — the guard compares against the configured model, not against the bundled default.
Notes
- Error codes 6001-6019 were already in use; this release adds 6020 EmbeddingModelMismatch.
- Coverage instrumentation (`cargo tarpaulin`) breaks the parallelism overlap windows that two RwLock tests assert on. Both tests now skip cleanly when `ENGRAM_SKIP_TIMING_TESTS=1` is set; local `cargo test` keeps the strict assertion.
- MCP server entry point no longer runs `main()` on import — `resolveProjectDir` can be imported from tests without spawning the engram binary. Fixes a long-standing `spawn engram ENOENT` in CI environments that don't have the binary on PATH.
Full changelog
See `CHANGELOG.md` in the repository.