Skip to content

fix(memory): add dimension-mismatch guard to EmbeddingRegistry::search_raw#3420

Merged
bug-ops merged 2 commits intomainfrom
3418-qdrant-grpc-cosine-scores
Apr 25, 2026
Merged

fix(memory): add dimension-mismatch guard to EmbeddingRegistry::search_raw#3420
bug-ops merged 2 commits intomainfrom
3418-qdrant-grpc-cosine-scores

Conversation

@bug-ops
Copy link
Copy Markdown
Owner

@bug-ops bug-ops commented Apr 25, 2026

Summary

Fixes #3418: skill injection non-functional due to Qdrant gRPC returning near-zero cosine scores (~0.022) on dimension mismatch.

Root cause: EmbeddingRegistry::search_raw was issuing gRPC searches without validating that the query vector dimension matches the collection's stored dimension. Qdrant gRPC silently returns near-zero cosine scores on mismatch (unlike REST which returns 400), so all skill candidates were dropped below the min_injection_score=0.20 threshold.

Fix:

  • Added cached_dim: Arc<tokio::sync::RwLock<Option<u64>>> to EmbeddingRegistry — populated by ensure_collection at sync time
  • search_raw reads the cache and returns Err(DimensionProbe) with a clear message (collection name, expected dim, actual dim) before issuing any gRPC call
  • On cache miss (first search before sync), probes Qdrant once via get_collection_vector_size and populates the cache
  • Made QdrantOps::get_collection_vector_size pub(crate) to support the cache-miss path
  • Removed semantically incorrect From<TryFromIntError> impl; replaced with safe as u64 casts

Test plan

  • 3 new unit tests: dimension mismatch returns DimensionProbe error, matching dimension passes guard, live integration test (#[ignore])
  • All 8525 workspace unit tests pass
  • cargo +nightly fmt --check clean
  • cargo clippy -p zeph-memory --lib -- -D warnings clean

Notes

The same root cause pattern affects #3382 (ReasoningBank embed dim mismatch). That issue is tracked separately.

@github-actions github-actions Bot added bug Something isn't working memory zeph-memory crate (SQLite) rust Rust code changes size/M Medium PR (51-200 lines) labels Apr 25, 2026
…h_raw

Qdrant gRPC silently returns near-zero cosine scores (~0.022) when the
query vector dimension does not match the stored collection dimension,
unlike the REST API which returns a 400 error. This caused all skill
candidates to be dropped below the min_injection_score threshold,
making skill injection non-functional.

Add a dimension guard in search_raw that:
- Reads a cached_dim (Arc<tokio::sync::RwLock<Option<u64>>>) populated
  by ensure_collection at sync time
- On cache miss, probes Qdrant once via get_collection_vector_size and
  populates the cache for subsequent calls
- Returns DimensionProbe error with collection name, expected dim, and
  actual dim if a mismatch is detected, before issuing any gRPC call

Make QdrantOps::get_collection_vector_size pub(crate) to support the
cache-miss fallback path. Remove semantically incorrect
From<TryFromIntError> impl and replace with safe as u64 casts.

Closes #3418
@bug-ops bug-ops enabled auto-merge (squash) April 25, 2026 22:17
@bug-ops bug-ops force-pushed the 3418-qdrant-grpc-cosine-scores branch from c7df1c2 to 0f672a3 Compare April 25, 2026 22:17
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Apr 25, 2026
@bug-ops bug-ops merged commit bf2e42f into main Apr 25, 2026
32 checks passed
@bug-ops bug-ops deleted the 3418-qdrant-grpc-cosine-scores branch April 25, 2026 22:25
bug-ops added a commit that referenced this pull request Apr 26, 2026
…te at Qdrant boundary

Add EmbeddingVector<State> typestate newtype to zeph-common with
Normalized and Unnormalized markers to prevent silent dimension/
normalization mismatches (recurring bug class: #3421, #3382, #3420).

- EmbeddingVector::<Unnormalized>::new(v) wraps raw model output
- .normalize() L2-normalizes and returns EmbeddingVector<Normalized>
- EmbeddingVector::<Normalized>::new_normalized(v) trust-caller path
- CodeStore::search now requires EmbeddingVector<Normalized>
- Retriever normalizes before passing to store.search
- agent_setup.rs: wraps Vec<f32> with .normalize() at embed call site
bug-ops added a commit that referenced this pull request Apr 26, 2026
…nticMemory bool→enum (#3436)

* refactor(common,index): introduce EmbeddingVector<Normalized> typestate at Qdrant boundary

Add EmbeddingVector<State> typestate newtype to zeph-common with
Normalized and Unnormalized markers to prevent silent dimension/
normalization mismatches (recurring bug class: #3421, #3382, #3420).

- EmbeddingVector::<Unnormalized>::new(v) wraps raw model output
- .normalize() L2-normalizes and returns EmbeddingVector<Normalized>
- EmbeddingVector::<Normalized>::new_normalized(v) trust-caller path
- CodeStore::search now requires EmbeddingVector<Normalized>
- Retriever normalizes before passing to store.search
- agent_setup.rs: wraps Vec<f32> with .normalize() at embed call site

* refactor(memory): replace 5 bool params in SemanticMemory with two-variant enums

Replace bool flags in SemanticMemory search/recall API with named enums
to enforce the project no-bool-params rule and eliminate call-site
swap bugs (addresses TODO at semantic/mod.rs:129):

  TemporalDecay, MmrReranking, ImportanceScoring,
  QueryBiasCorrection, HebbianReinforcement

Each enum has Enabled/Disabled variants, #[must_use] is_enabled(),
and From<bool> for config bootstrap compatibility. All callers,
builder methods, and test fixtures updated across the workspace.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation memory zeph-memory crate (SQLite) rust Rust code changes size/M Medium PR (51-200 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

skill injection non-functional: Qdrant gRPC search returns near-zero cosine scores (~0.022) while REST search returns correct scores (~0.45)

1 participant