* fix: batch 1 — deadlock, AVX-512 gating, Windows case-collisions
Closes #437: VectorDb::delete in ruvector-router-core acquired the stats
RwLock twice in one statement. parking_lot::RwLock is non-reentrant, so
the second .write() deadlocked against the first guard's lifetime. Bind
the guard once.
Closes #438: Gate AVX-512 intrinsics behind a new `simd-avx512` Cargo
feature (default-on). Lets downstream consumers on stable Rust 1.77–1.88
(before avx512f stabilization in 1.89) opt out without forcing nightly:
cargo build --no-default-features --features simd,storage,hnsw,api-embeddings,parallel
Runtime dispatch falls back to AVX2 + FMA when the feature is disabled.
All 4 #[target_feature(enable = "avx512f")] sites + 4 dispatch branches
updated. Both feature configurations verified to compile cleanly; all
18 simd_intrinsics tests pass.
Closes #458: Rename two pairs of case-colliding research artifacts under
docs/research/claude-code-rvsource/versions/v2.1.x/tree/react_memo_cache_sentinel/
that broke `git clone` on Windows/NTFS:
tmux.js → tmux_lc.js (TMUX.js kept)
type.js → type_lc.js (Type.js kept)
modules-manifest.json updated to match.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(brain): observable hydration + larger page-error budget (issue #464)
Bisect outcome: source diff between the 2026-04-14 working revision
(00203-brv → 22,005 memories) and current main (00204-92l → 10,227)
is whitespace-only (cargo fmt 2026-04-24 + clippy 2026-04-25). No
semantic change in store.rs, types.rs, or graph.rs. BrainMemory schema
is byte-identical. So the regression is environmental, surfacing
through a code path that has no observability today.
Two changes:
1. load_from_firestore() now emits per-collection counters so the next
deploy is diagnosable instead of a black box:
Hydrate brain_memories: considered=N accepted=M rejected_parse=K
First 5 parse errors are logged with the serde_json error so any
live schema drift surfaces immediately.
2. firestore_list MAX_PAGE_ERRORS raised 3 → 8. Hydration crosses ~75
pages of 300 docs each; 3 transient OAuth-refresh blips at the
wrong moment terminated the load at ~10K, consistent with the
reported 10,227 number. 8 still bounds runaway behaviour while
tolerating realistic blip rates.
The actual environmental cause is recoverable from one deploy with the
new logs in place. Until then, traffic stays on 00203-brv (which is
what the rollback already did).
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(router-core): HNSW result-heap inversion, prune drops oldest, k > ef_search (#430)
Three correctness bugs in crates/ruvector-router-core/src/index.rs that
together collapsed recall@1 at scale:
1. `Neighbor::Ord` is reversed so BinaryHeap acts as a min-heap. Correct
for `candidates` (pop closest unexplored first), but WRONG for the
`result` heap — peek returned the BEST candidate, so the eviction
path kept dropping the best item instead of the worst whenever the
set was full. Wrap result in `std::cmp::Reverse<Neighbor>` so
peek/pop return the furthest item (the actual eviction target). This
is the primary recall@1 fix.
2. Per-insert connection pruning used `truncate(m)`, which keeps the
OLDEST m connections — including dropping the just-pushed edge when
it landed past index m. Switch to `drain(0..len-m)` so the freshly
inserted edge always survives.
3. `search()` capped at `ef_search` regardless of caller's k. With
default ef_search=10 and k=25, results were silently 10. Raise ef
to `max(ef_search, k)` before invoking search_knn_internal.
New tests:
- `test_recall_at_1_with_biased_insertion_order`: 1024 vectors,
biased insertion order (the topology that historically exposed the
bug); asserts recall@1 ≥ 95% AND ≥ 80% distinct ids across queries.
- `test_k_exceeds_ef_search_default`: 50 vectors, default ef_search=10,
k=25; asserts 25 results returned.
All 19 router-core tests pass.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(npm): publish pipeline — dist/ guaranteed + dual ESM/CJS pi-brain (#462/#415/#376/#372)
@ruvector/pi-brain 0.1.1 → 0.1.2 (closes #462, #372):
* Add `prepack` hook so dist/ is always built before publish — tarballs
on 0.1.0/0.1.1 shipped without dist/ because `tsc` never ran.
* Add a second tsconfig (tsconfig.cjs.json) that emits CommonJS to
dist/cjs/ alongside the ESM build in dist/. A generated
dist/cjs/package.json carries {"type":"commonjs"} so Node treats
that subtree as CJS regardless of the package-level "type":"module".
* Expand the exports map with import + require + default conditions
so ruvector@0.2.x's CJS MCP server (Node 20.x, no require(ESM)
until 22.12) can require() the package. Add subpath exports for
./mcp and ./client.
* Verified locally: dist/cjs/index.js loads via `require()` and
dist/index.js loads via dynamic `import()`.
@ruvector/rvf-wasm 0.1.5 → 0.1.6 (closes #415):
* pkg/rvf_wasm.js contains ESM syntax (`import.meta.url`,
`export default`). The old exports map pointed `require` at this
file, which fails on every CJS consumer. Mark the package
explicitly `"type": "module"`, drop the `require` condition (the
`.mjs` build is the canonical one), and add a `./wasm` subpath for
consumers that want the raw bytes.
ruvector npm 0.2.25 (extends #376 mitigation):
* Add `prepack` mirroring `prepublishOnly` so `npm pack` (and CI
smoke tests that run pack) regenerate dist/ + run verify-dist.
Without this, `npm pack` skips prepublishOnly, masking
missing-dist regressions until publish.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(mcp): hooks_route_enhanced in-process — drop spawnSync (#463/#422)
The hooks_route_enhanced MCP tool shelled out via
execSync('npx ruvector hooks route-enhanced …', { timeout: 30000 })
which deterministically timed out: npx's package-resolution and
bin-launch overhead can spike past 30s on cold-cache machines, even
though the underlying work finishes in ~500ms. Callers got
deterministic `spawnSync /bin/sh ETIMEDOUT`.
The sibling hooks_route tool (reported as working in #463) uses
intel.route() directly. Mirror that pattern: call intel.route(), then
inline the same coverage-router + AST-parser signal enrichment the CLI
does. No subprocess, no timeout, no npx dependency.
Falls back gracefully when coverage-router or ast-parser aren't
installed (try/catch around each optional enhancement, same as the
CLI handler).
Co-Authored-By: claude-flow <ruv@ruv.net>
* ci: regression guard for 9 issues + fixes for 5 latent regressions it surfaced
New workflow .github/workflows/regression-guard.yml runs on every push +
PR. Each job pins one of these issue classes shut:
#437 reentrant-rwlock-double-write
Forbids `x.write()…x.(write|read)()` and `x.read()…x.write()` in
a single statement (parking_lot is non-reentrant). PCRE
backreference matches only same-lock cases.
#458 case-insensitive-collisions
Fails if `git ls-files` has any two paths that match after
lowercasing — Windows clones drop one of each silently.
#438 ruvector-core-no-avx512-builds-on-stable
cargo check ruvector-core with AND without the simd-avx512
feature so the AVX-512 gating doesn't regress.
#430 hnsw-recall-at-1
Runs the new recall@1 (biased insertion / 1024 vectors) test
and the k > ef_search test in release mode.
#462 / #376 npm-publish-pipeline
npm pack each shipped package and assert every entry referenced
by main/module/types/exports is actually inside the tarball.
#463 / #422 no-npx-execSync-in-mcp-server
Forbids execSync('npx ruvector …') anywhere in the MCP server.
#256 shell-injection-in-mcp-server
Flags any exec*/spawn* call that interpolates ${args.X} without
wrapping in sanitizeShellArg(...).
#267 no-systemtime-in-wasm-crates
Crates named *wasm* with ungated SystemTime::now / Instant::now
calls are rejected (the wasm32-unknown-unknown panic class).
#359 no-hardcoded-workspaces-paths
Devcontainer-only `/workspaces/ruvector` literals are banned
from .github/workflows, .claude/settings*, and scripts/publish/.
Adding the guard surfaced five real, already-present regressions of
these classes — fixed in this commit:
* crates/prime-radiant/src/coherence/engine.rs (3 sites):
self.stats.write().X = self.stats.read().X - 1 in the same
statement — exactly issue #437's shape on a different lock. Bind
the write guard once.
* crates/ruvector-wasm/src/lib.rs:465 (benchmark fn):
used std::time::Instant which panics on wasm32 (issue #267).
Switch to js_sys::Date::now().
* scripts/publish/publish-router-wasm.sh + check-and-publish-router-wasm.sh:
hardcoded /workspaces/ruvector paths (issue #359). Resolve REPO_ROOT
from BASH_SOURCE instead.
Co-Authored-By: claude-flow <ruv@ruv.net>
* ci: narrow scope of two guards to avoid pre-existing-debt false positives
After the first PR run two guards caught existing technical debt rather
than fresh regressions:
* no-npx-execSync-in-mcp-server flagged 10 other execSync('npx
ruvector …') sites (ast-analyze, coverage-route, graph-mincut,
security-scan, git-churn, …) which predate issue #463 and are a
distinct concern (some legitimately need subprocess). Narrow the
guard to the EXACT regression — execSync inside the
hooks_route_enhanced case body — using awk to extract that case's
body before grepping. Rename: no-npx-execSync-in-route-enhanced.
* npm-publish-pipeline failed at npm install (peer-dep ERESOLVE).
Add --legacy-peer-deps. The point of this guard is the tarball
content, not the install graph.
Co-Authored-By: claude-flow <ruv@ruv.net>
* style: cargo fmt --all (mechanical, pre-existing diffs on main + my new code)
Workspace had 11 files with rustfmt diffs predating this branch, plus
one new diff in store.rs from the hydration counters added in
|
||
|---|---|---|
| .. | ||
| benches | ||
| docs | ||
| examples | ||
| fuzz | ||
| src | ||
| tests | ||
| Cargo.toml | ||
| README.md | ||
Ruvector Core
The pure-Rust vector database engine behind RuVector -- HNSW indexing, quantization, and SIMD acceleration in a single crate.
ruvector-core is the foundational library that powers the entire RuVector ecosystem. It gives you a production-grade vector database you can embed directly into any Rust application: insert vectors, search them in under a millisecond, filter by metadata, and compress storage up to 32x -- all without external services. If you need vector search as a library instead of a server, this is the crate.
| ruvector-core | Typical Vector Database | |
|---|---|---|
| Deployment | Embed as a Rust dependency -- no server, no network calls | Run a separate service, manage connections |
| Query latency | <0.5 ms p50 at 1M vectors with HNSW | ~1-5 ms depending on network and index |
| Memory compression | Scalar (4x), Product (8-32x), Binary (32x) quantization built in | Often requires paid tiers or external tools |
| SIMD acceleration | SimSIMD hardware-optimized distance calculations, automatic | Manual tuning or not available |
| Search modes | Dense vectors, sparse BM25, hybrid, MMR diversity, filtered -- all in one API | Typically dense-only; hybrid and filtering are add-ons |
| Storage | Zero-copy mmap with redb -- instant loading, no deserialization |
Load time scales with dataset size |
| Concurrency | Lock-free indexing with parallel batch processing via Rayon | Varies; many require single-writer locks |
| Dependencies | Minimal -- pure Rust, compiles anywhere rustc runs |
Often depends on C/C++ libraries (BLAS, LAPACK) |
| Cost | Free forever -- open source (MIT) | Per-vector or per-query pricing on managed tiers |
Installation
Add ruvector-core to your Cargo.toml:
[dependencies]
ruvector-core = "0.1.0"
Feature Flags
[dependencies]
ruvector-core = { version = "0.1.0", features = ["simd", "uuid-support"] }
Available features:
simd(default): Enable SIMD-optimized distance calculationsuuid-support(default): Enable UUID generation for vector IDs
Key Features
| Feature | What It Does | Why It Matters |
|---|---|---|
| HNSW Indexing | Hierarchical Navigable Small World graphs for O(log n) approximate nearest neighbor search | Sub-millisecond queries at million-vector scale |
| Multiple Distance Metrics | Euclidean, Cosine, Dot Product, Manhattan | Match the metric to your embedding model without conversion |
| Scalar Quantization | Compress vectors to 8-bit integers (4x reduction) | Cut memory by 75% with 98% recall preserved |
| Product Quantization | Split vectors into subspaces with codebooks (8-32x reduction) | Store millions of vectors on a single machine |
| Binary Quantization | 1-bit representation (32x reduction) | Ultra-fast screening pass for massive datasets |
| SIMD Distance | Hardware-accelerated distance via SimSIMD | Up to 80K QPS on 8 cores without code changes |
| Zero-Copy I/O | Memory-mapped storage loads instantly | No deserialization step -- open a file and search immediately |
| Hybrid Search | Combine dense vector similarity with sparse BM25 text scoring | One query handles both semantic and keyword matching |
| Metadata Filtering | Apply key-value filters during search | No post-filtering needed -- results are already filtered |
| MMR Diversification | Maximal Marginal Relevance re-ranking | Avoid redundant results when top-K are too similar |
| Conformal Prediction | Uncertainty quantification on search results | Know when to trust (or distrust) a match |
| Lock-Free Indexing | Concurrent reads and writes without blocking | High-throughput ingestion while serving queries |
| Batch Processing | Parallel insert and search via Rayon | Saturate all cores for bulk operations |
Quick Start
Basic Usage
use ruvector_core::{VectorDB, DbOptions, VectorEntry, SearchQuery, DistanceMetric};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new vector database
let mut options = DbOptions::default();
options.dimensions = 384; // Vector dimensions
options.storage_path = "./my_vectors.db".to_string();
options.distance_metric = DistanceMetric::Cosine;
let db = VectorDB::new(options)?;
// Insert vectors
db.insert(VectorEntry {
id: Some("doc1".to_string()),
vector: vec![0.1, 0.2, 0.3, /* ... 384 dimensions */],
metadata: None,
})?;
db.insert(VectorEntry {
id: Some("doc2".to_string()),
vector: vec![0.4, 0.5, 0.6, /* ... 384 dimensions */],
metadata: None,
})?;
// Search for similar vectors
let results = db.search(SearchQuery {
vector: vec![0.1, 0.2, 0.3, /* ... 384 dimensions */],
k: 10, // Return top 10 results
filter: None,
ef_search: None,
})?;
for result in results {
println!("ID: {}, Score: {}", result.id, result.score);
}
Ok(())
}
Batch Operations
use ruvector_core::{VectorDB, VectorEntry};
// Insert multiple vectors efficiently
let entries = vec![
VectorEntry {
id: Some("doc1".to_string()),
vector: vec![0.1, 0.2, 0.3],
metadata: None,
},
VectorEntry {
id: Some("doc2".to_string()),
vector: vec![0.4, 0.5, 0.6],
metadata: None,
},
];
let ids = db.insert_batch(entries)?;
println!("Inserted {} vectors", ids.len());
With Metadata Filtering
use std::collections::HashMap;
use serde_json::json;
// Insert with metadata
db.insert(VectorEntry {
id: Some("product1".to_string()),
vector: vec![0.1, 0.2, 0.3],
metadata: Some(HashMap::from([
("category".to_string(), json!("electronics")),
("price".to_string(), json!(299.99)),
])),
})?;
// Search with metadata filter
let results = db.search(SearchQuery {
vector: vec![0.1, 0.2, 0.3],
k: 10,
filter: Some(HashMap::from([
("category".to_string(), json!("electronics")),
])),
ef_search: None,
})?;
HNSW Configuration
use ruvector_core::{DbOptions, HnswConfig, DistanceMetric};
let mut options = DbOptions::default();
options.dimensions = 384;
options.distance_metric = DistanceMetric::Cosine;
// Configure HNSW index parameters
options.hnsw_config = Some(HnswConfig {
m: 32, // Connections per layer (16-64 typical)
ef_construction: 200, // Build-time accuracy (100-500 typical)
ef_search: 100, // Search-time accuracy (50-200 typical)
max_elements: 10_000_000, // Maximum vectors
});
let db = VectorDB::new(options)?;
Quantization
use ruvector_core::{DbOptions, QuantizationConfig};
let mut options = DbOptions::default();
options.dimensions = 384;
// Enable scalar quantization (4x compression)
options.quantization = Some(QuantizationConfig::Scalar);
// Or product quantization (8-32x compression)
options.quantization = Some(QuantizationConfig::Product {
subspaces: 8, // Number of subspaces
k: 256, // Codebook size
});
let db = VectorDB::new(options)?;
API Overview
Core Types
// Main database interface
pub struct VectorDB { /* ... */ }
// Vector entry with optional ID and metadata
pub struct VectorEntry {
pub id: Option<VectorId>,
pub vector: Vec<f32>,
pub metadata: Option<HashMap<String, serde_json::Value>>,
}
// Search query parameters
pub struct SearchQuery {
pub vector: Vec<f32>,
pub k: usize,
pub filter: Option<HashMap<String, serde_json::Value>>,
pub ef_search: Option<usize>,
}
// Search result with score
pub struct SearchResult {
pub id: VectorId,
pub score: f32,
pub vector: Option<Vec<f32>>,
pub metadata: Option<HashMap<String, serde_json::Value>>,
}
Main Operations
impl VectorDB {
// Create new database with options
pub fn new(options: DbOptions) -> Result<Self>;
// Create with just dimensions (uses defaults)
pub fn with_dimensions(dimensions: usize) -> Result<Self>;
// Insert single vector
pub fn insert(&self, entry: VectorEntry) -> Result<VectorId>;
// Insert multiple vectors
pub fn insert_batch(&self, entries: Vec<VectorEntry>) -> Result<Vec<VectorId>>;
// Search for similar vectors
pub fn search(&self, query: SearchQuery) -> Result<Vec<SearchResult>>;
// Delete vector by ID
pub fn delete(&self, id: &str) -> Result<bool>;
// Get vector by ID
pub fn get(&self, id: &str) -> Result<Option<VectorEntry>>;
// Get total count
pub fn len(&self) -> Result<usize>;
// Check if empty
pub fn is_empty(&self) -> Result<bool>;
}
Distance Metrics
pub enum DistanceMetric {
Euclidean, // L2 distance - default for embeddings
Cosine, // Cosine similarity (1 - similarity)
DotProduct, // Negative dot product (for maximization)
Manhattan, // L1 distance
}
Advanced Features
// Hybrid search (dense + sparse)
use ruvector_core::{HybridSearch, HybridConfig};
let hybrid = HybridSearch::new(HybridConfig {
alpha: 0.7, // Balance between dense (0.7) and sparse (0.3)
..Default::default()
});
// Filtered search with expressions
use ruvector_core::{FilteredSearch, FilterExpression};
let filtered = FilteredSearch::new(db);
let expr = FilterExpression::And(vec![
FilterExpression::Equals("category".to_string(), json!("books")),
FilterExpression::GreaterThan("price".to_string(), json!(10.0)),
]);
// MMR diversification
use ruvector_core::{MMRSearch, MMRConfig};
let mmr = MMRSearch::new(MMRConfig {
lambda: 0.5, // Balance relevance (0.5) and diversity (0.5)
..Default::default()
});
Performance
Latency (Single Query)
Operation Flat Index HNSW Index
---------------------------------------------
Search (1K vecs) ~0.1ms ~0.2ms
Search (100K vecs) ~10ms ~0.5ms
Search (1M vecs) ~100ms <1ms
Insert ~0.1ms ~1ms
Batch (1000) ~50ms ~500ms
Memory Usage (1M Vectors, 384 Dimensions)
Configuration Memory Recall
---------------------------------------------
Full Precision (f32) ~1.5GB 100%
Scalar Quantization ~400MB 98%
Product Quantization ~200MB 95%
Binary Quantization ~50MB 85%
Throughput (Queries Per Second)
Configuration QPS Latency (p50)
-----------------------------------------------------
Single Thread ~2,000 ~0.5ms
Multi-Thread (8 cores) ~50,000 <0.5ms
With SIMD ~80,000 <0.3ms
With Quantization ~100,000 <0.2ms
Configuration Guide
For Maximum Accuracy
let options = DbOptions {
dimensions: 384,
distance_metric: DistanceMetric::Cosine,
hnsw_config: Some(HnswConfig {
m: 64,
ef_construction: 500,
ef_search: 200,
max_elements: 10_000_000,
}),
quantization: None, // Full precision
..Default::default()
};
For Maximum Speed
let options = DbOptions {
dimensions: 384,
distance_metric: DistanceMetric::DotProduct,
hnsw_config: Some(HnswConfig {
m: 16,
ef_construction: 100,
ef_search: 50,
max_elements: 10_000_000,
}),
quantization: Some(QuantizationConfig::Binary),
..Default::default()
};
For Balanced Performance
let options = DbOptions::default(); // Recommended defaults
Building and Testing
Build
# Build with default features
cargo build --release
# Build without SIMD
cargo build --release --no-default-features --features uuid-support
# Build for specific target with optimizations
RUSTFLAGS="-C target-cpu=native" cargo build --release
Testing
# Run all tests
cargo test
# Run with specific features
cargo test --features simd
# Run with logging
RUST_LOG=debug cargo test
Benchmarks
# Run all benchmarks
cargo bench
# Run specific benchmark
cargo bench --bench hnsw_search
# Run with features
cargo bench --features simd
Available benchmarks:
distance_metrics- SIMD-optimized distance calculationshnsw_search- HNSW index search performancequantization_bench- Quantization techniquesbatch_operations- Batch insert/search operationscomprehensive_bench- Full system benchmarks
Related Crates
ruvector-core is the foundation for platform-specific bindings:
- ruvector-node - Node.js bindings via NAPI-RS
- ruvector-wasm - WebAssembly bindings for browsers
- ruvector-gnn - Graph Neural Network layer for learned search
- ruvector-cli - Command-line interface
- ruvector-bench - Performance benchmarks
Documentation
- Main README - Complete project overview
- Getting Started Guide - Quick start tutorial
- Rust API Reference - Detailed API documentation
- Advanced Features Guide - Quantization, indexing, tuning
- Performance Tuning - Optimization strategies
- API Documentation - Full API reference on docs.rs
Acknowledgments
Built with state-of-the-art algorithms and libraries:
- hnsw_rs - HNSW implementation
- simsimd - SIMD distance calculations
- redb - Embedded database
- rayon - Data parallelism
- memmap2 - Memory-mapped files
License
MIT License - see LICENSE for details.