Skip to content

RuVectorBackend silently returns empty search results: async API, result fields and lifecycle contract mismatch #33

Open

Description

@sparkling

Reproduction (published packages)

Fresh disposable npm project on macOS arm64 / Node 24.14.1:

{"dependencies":{"agentdb":"3.0.0-alpha.20"},"overrides":{"ruvector":"0.3.3","@ruvector/rvf":"0.3.4"}}

Versions were verified using createRequire(require.resolve("agentdb")), not just the root manifest: AgentDB resolves RuVector 0.3.3 and RVF 0.3.4. The same async-search defect also reproduced with RuVector 0.2.41.

In a fresh temporary working directory:

import { RuVectorBackend } from './node_modules/agentdb/dist/src/backends/ruvector/RuVectorBackend.js';
const b = new RuVectorBackend({dimension: 4, metric: 'cosine'});
await b.initialize();
const vector = new Float32Array([1, 0, 0, 0]);
// Await native insertion to eliminate insertion timing as a cause.
await b.db.insert({id: 'fixture', vector, metadata: {label: 'keep'}});
console.log(await b.db.search({vector, k: 1})); // fixture, score: 0, metadata retained
console.log(b.search(vector, 1));              // []
console.log(b.getStats());                    // throws: this.db.count is not a function

Separately, b.remove('fixture') returns false, and await b.db.get('fixture') still finds the record.

Causes in RuVectorBackend

  • search() treats the Promise returned by VectorDB.search as an array; results.length | 0 becomes zero and silently reports no matches.
  • insert() ignores the returned Promise, so callers cannot reliably observe persistence/failure.
  • Native search returns score (the fixture exact-match value is 0), while this adapter consumes distance.
  • Native metadata is returned with search results, but this adapter uses only its local Map, which is empty after a process restart.
  • The adapter calls remove, count, save, and load; the current high-level VectorDB exposes delete, len, and path-bound persistence instead. Its native binding also has no explicit close method.
  • Constructor config drops storagePath (tracked separately in AgentDB creates ruvector.db and agentdb.rvf in the process cwd instead of beside dbPath #28).

Adding one await is insufficient: AgentDB controllers including ReasoningBank, ReflexionMemory, SkillLibrary, CausalRecall, CausalMemoryGraph, HybridSearch, and attention consumers currently use the synchronous VectorBackend contract. The integration needs a reviewed async contract, awaited controller calls, correct native result mapping, honest lifecycle behavior, and compatible statistics.

Requested regression coverage

  • Real native insert -> awaited retrieval with exact ID, finite distance/similarity, and exact metadata.
  • Delete -> absence; count before/after.
  • Metadata retention and retrieval in a second process, using an explicit storagePath.
  • Controller-level pattern, episode, skill, and causal retrieval (not only direct backend tests).
  • Native failures propagate instead of empty matches or fire-and-forget success.
  • Preserve synchronous backend compatibility where applicable.

Separate RVF boundary

Switching to the latest RVF adapter is not a metadata-safe workaround. With AgentDB resolving RVF 0.3.4, RvfBackend.insertAsync(..., {label:'keep'}) rejects with Per-vector metadata is not yet supported by this SDK (see issue #704). Earlier SDKs silently accepted/dropped metadata. See ruvnet/RuVector#704. Do not strip metadata or introduce a lossy migration to make adapter tests pass.

All reproduction stores were disposable fixtures; no project database or sidecars were modified.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions