Skip to content

fix(memory): expose vectorBackend via direct property access in ControllerRegistry#1611

Open
pauloeduardo wants to merge 3 commits intoruvnet:mainfrom
pauloeduardo:fix/controller-registry-vector-backend
Open

fix(memory): expose vectorBackend via direct property access in ControllerRegistry#1611
pauloeduardo wants to merge 3 commits intoruvnet:mainfrom
pauloeduardo:fix/controller-registry-vector-backend

Conversation

@pauloeduardo
Copy link
Copy Markdown

Summary

Fixes vectorBackend and its 7 dependent controllers always failing in ControllerRegistry (ADR-053).

Closes #1610
Related to #1207, #1228


Root Cause

ControllerRegistry.initAgentDB() was initializing AgentDB with only { dbPath }, causing AgentDB to fall back to HNSWLib and not expose a vectorBackend controller.

The createController() case for 'vectorBackend' then called agentdb.getController('vectorBackend'), but AgentDB.getController() only supports reflexion, skills, and causalGraph — throwing Unknown controller: vectorBackend for anything else.

AgentDB does expose its vector backend as a direct property (agentdb.vectorBackend), but this was never accessed.

Changes

1. RuntimeConfig — add vectorBackend field

export interface RuntimeConfig {
  dbPath?: string;
  dimension?: number;
  vectorBackend?: 'auto' | 'rvf' | 'hnswlib';  // new
  // ...
}

2. initAgentDB() — pass vectorBackend and vectorDimension to AgentDB

// Before
this.agentdb = new AgentDBClass({ dbPath });

// After
this.agentdb = new AgentDBClass({
  dbPath,
  vectorBackend: config.vectorBackend ?? 'auto',
  vectorDimension: config.dimension || 384,
});

Using 'auto' as default preserves backward compatibility — AgentDB tries ruvector first and falls back to HNSWLib gracefully.

3. createController('vectorBackend') — access direct property instead of getController()

// Before: throws "Unknown controller: vectorBackend"
return this.agentdb.getController(name) ?? null;

// After: access direct property first
if (name === 'vectorBackend' && this.agentdb.vectorBackend) {
  return this.agentdb.vectorBackend;
}
if (typeof this.agentdb.getController === 'function') {
  return this.agentdb.getController(name) ?? null;
}

Test Results

Before this fix:

Activated: 15  Failed: 8
failed: vectorBackend, mutationGuard, gnnService, attestationLog,
        semanticRouter, guardedVectorBackend, rvfOptimizer, graphAdapter

After this fix (with vectorBackend: 'rvf'):

Activated: 16  Failed: 7  (vectorBackend now active)
failed: mutationGuard, gnnService, attestationLog,
        semanticRouter, guardedVectorBackend, rvfOptimizer, graphAdapter

The remaining 7 failures are separate issues — they depend on vectorBackend being active, but have their own initialization problems that are outside the scope of this fix.

Environment

  • ruflo@3.5.51, agentdb@3.0.0-alpha.11
  • Node.js v22.22.2, Docker ARM64 (aarch64)
  • ruvector installed in agentdb/node_modules/ for correct ESM resolution

…ollerRegistry

- Add vectorBackend field to RuntimeConfig interface
- Pass vectorBackend and vectorDimension to AgentDB constructor in initAgentDB()
- Access agentdb.vectorBackend directly in createController() since
  agentdb.getController() does not support 'vectorBackend' name

AgentDB exposes its vector backend instance as a direct property
(agentdb.vectorBackend) but getController() only supports reflexion,
skills and causalGraph. The previous code called getController('vectorBackend')
which always threw 'Unknown controller: vectorBackend', causing the
vectorBackend controller and its 7 dependents to always fail.

Fixes: ruvnet#1610
Related: ruvnet#1207, ruvnet#1228 (ADR-053)
Copy link
Copy Markdown

@xkonjin xkonjin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, focused change. A couple of observations:

  1. Nullish coalescing for vectorBackend: config.vectorBackend ?? 'auto' is good, but vectorDimension uses config.dimension || 384. If dimension is explicitly set to 0 (edge case), it will fall back to 384. Consider config.dimension ?? 384 for consistency.

  2. Property access bypass: The comment explains why vectorBackend is read directly from this.agentdb.vectorBackend instead of via getController(). This is fine, but it creates a leaky abstraction: callers now depend on an internal property. If AgentDB ever changes how it stores this, the proxy here will break. Consider adding a lightweight getter upstream in AgentDB so the registry can stay decoupled.

  3. No test updates: The PR changes the constructor contract of ControllerRegistry (new vectorBackend option) and the proxy behavior, but I don't see any test changes. A small unit test verifying that vectorBackend round-trips correctly through the registry would close the gap.

LGTM otherwise.

Paulo Eduardo Budal added 2 commits April 14, 2026 15:20
- Use ?? instead of || for vectorDimension to handle explicit 0 correctly
- Improve comment on vectorBackend direct property access noting the
  leaky abstraction and adding TODO for upstream getController() support
- Add unit test verifying vectorBackend round-trips through ControllerRegistry

354 tests passing
@pauloeduardo
Copy link
Copy Markdown
Author

All three review points have been addressed in the latest commits:

  1. ?? instead of || for vectorDimension — changed to config.dimension ?? 384 to correctly handle an explicit 0 value.
  2. Leaky abstraction comment — improved the comment on the direct property access to explicitly note the coupling risk and added a TODO for requesting getController('vectorBackend') support upstream in agentdb.
  3. Unit test — added a test in controller-registry.test.ts that mocks AgentDB with a vectorBackend property and verifies it round-trips correctly through ControllerRegistry.get('vectorBackend'). All 355 tests passing.

Ready for re-review.

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

Labels

None yet

Projects

None yet

3 participants