Skip to content

fix: SonaTrajectoryService correctly uses native @ruvector/sona API#121

Open
Andycodeman wants to merge 2 commits intoruvnet:mainfrom
Andycodeman:fix/sona-trajectory-service-native-api
Open

fix: SonaTrajectoryService correctly uses native @ruvector/sona API#121
Andycodeman wants to merge 2 commits intoruvnet:mainfrom
Andycodeman:fix/sona-trajectory-service-native-api

Conversation

@Andycodeman
Copy link

@Andycodeman Andycodeman commented Feb 28, 2026

Summary

The SonaTrajectoryService in agentdb has three critical bugs that completely prevent native SONA engine integration. As a result, all trajectory learning silently falls back to a basic JavaScript frequency counter — the Micro-LoRA, EWC++, and pattern clustering features of @ruvector/sona are never invoked.

This PR fixes all three bugs and includes a comprehensive test suite.

Source File Not Found in Git

During investigation, we discovered that SonaTrajectoryService.ts does not exist anywhere in the git source — not on main, not on any of the 18 feature branches, and not in the commit history. The file first appeared in the npm-published dist/ starting at agentdb@3.0.0-alpha.4 and has been present through alpha.10, but the TypeScript source was never committed.

This is part of a broader pattern: the published npm package contains files that diverge from the git source tree:

  • 4 service files in npm dist have no git source: GNNService, GraphTransformerService, SemanticRouter, SonaTrajectoryService
  • 5 controller files in npm dist have no git source: HierarchicalMemory, MemoryConsolidation, QUICConnection, QUICConnectionPool, StreamingEmbeddingService
  • Only LLMRouter exists in both git source and npm dist under services/

Since no TypeScript source exists to patch, this PR adds the fixed JavaScript file at packages/agentdb/src/services/SonaTrajectoryService.js — the correct location for the build system (tsc with rootDir: ".", outDir: "./dist", include: ["src/**/*"]).

The maintainer may want to:

  1. Convert this to TypeScript to match the rest of the codebase
  2. Add the other missing source files (GNNService.ts, etc.) that are also only in npm dist
  3. Ensure the npm publish pipeline builds from git source rather than a divergent local tree

What is broken today (current version — broken since alpha.4)

The Silent Failure Chain

The current SonaTrajectoryService appears to work but actually does nothing with the native engine. These bugs have existed since the file first appeared in alpha.4 and are present in every version through alpha.10.

The broken method names (recordStep || record || addStep, predict || selectAction) appear to be guesses at the @ruvector/sona API rather than calls to its actual methods (beginTrajectory, addTrajectoryStep, endTrajectory, findPatterns).

  1. initialize() stores the module, not an engine instance

    // Current code — BROKEN
    const SONA = mod.SONA || mod.Sona || mod.default?.SONA || mod.default;
    if (SONA && typeof SONA === 'object') {
        this.sona = SONA; // Stores the MODULE OBJECT, not a SonaEngine!
    }

    The @ruvector/sona module exports { SonaEngine: [class] }. The current code looks for SONA/Sona (wrong class name), the constructor lookup fails, then the typeof === 'object' branch stores the raw module. Now this.sona is the module, not an engine instance.

  2. recordTrajectory() calls nonexistent methods — silently catches and drops

    // Current code — BROKEN
    if (typeof this.sona.recordStep === 'function') { ... }      // doesn't exist on SonaEngine
    else if (typeof this.sona.record === 'function') { ... }      // doesn't exist
    else if (typeof this.sona.addStep === 'function') { ... }     // doesn't exist
    // All typeof checks fail, no native recording happens
    // catch {} silently swallows any errors

    Since none of these methods exist on SonaEngine, every trajectory bypasses the native engine entirely. The empty catch {} block ensures no error is ever reported.

  3. predict() calls nonexistent methods — silently falls back

    // Current code — BROKEN
    if (typeof this.sona.predict === 'function') { ... }          // doesn't exist
    else if (typeof this.sona.selectAction === 'function') { ... } // doesn't exist
    // Both fail, falls through to frequency-based JS prediction

    Predictions always come from the simple frequency counter, never from learned SONA patterns.

Real-world impact

  • SONA learning is completely non-functional — no trajectories are recorded in the native engine
  • No pattern extractionfindPatterns() is never called
  • All Micro-LoRA weight updates are skipped — the native engine never sees any data
  • EWC++ catastrophic forgetting prevention is unused — no training means nothing to protect
  • Silent failure — the service reports engineType: 'native' and available: true while doing nothing with the native engine
  • Users believe SONA is learning when it's just counting action frequencies

What this PR fixes

Fix 1: initialize() — Proper engine instantiation

const exports = mod.default || mod;
const SonaEngine = exports.SonaEngine || exports.SONA || exports.Sona;
if (SonaEngine && typeof SonaEngine === 'function') {
    this.sona = SonaEngine.withConfig({
        hiddenDim: this.hiddenDim,
        qualityThreshold: 0.1,
        patternClusters: 10,
        trajectoryCapacity: 10000
    });
}
  • Uses SonaEngine.withConfig() for full control over learning parameters
  • qualityThreshold: 0.1 (vs default 0.5) ensures trajectories aren't filtered out during k-means clustering
  • Accepts options.hiddenDim parameter for caller configuration

Fix 2: recordTrajectory() — Correct native pipeline

const trajectoryId = this.sona.beginTrajectory(queryEmbedding);
for (const step of steps) {
    this.sona.addTrajectoryStep(trajectoryId, activations, attentionWeights, step.reward);
}
this.sona.setTrajectoryRoute(trajectoryId, agentType);
this.sona.endTrajectory(trajectoryId, quality);
this.sona.tick();
  • Uses the real beginTrajectory -> addTrajectoryStep -> endTrajectory pipeline
  • Generates embeddings from state objects via new stateToEmbedding() method
  • Calls tick() to trigger the instant learning loop
  • Still maintains in-memory storage for backward compatibility

Fix 3: predict() — Uses findPatterns() for real predictions

const patterns = this.sona.findPatterns(queryEmbedding, 3);
if (patterns && patterns.length > 0) {
    const best = patterns[0];
    return {
        action: best.patternType || 'default',
        confidence: best.avgQuality * Math.min(1, best.clusterSize / 10),
        source: 'native-sona'
    };
}
  • Uses findPatterns() which is the actual SonaEngine query API
  • Returns pattern metadata (patternId, clusterSize, avgQuality)
  • Includes source: 'native-sona' to distinguish from JS fallback

New methods added

  • stateToEmbedding(state) — Deterministic hash-based embedding generator
  • forceLearn() — Triggers a background learning cycle on the native engine
  • getNativeStats() — Returns native engine statistics

Testing

Unit tests: 54/54 passing

Test suite: packages/agentdb/tests/sona-trajectory-native-api.test.mjs

Test Assertions Description
1. Native Engine Init 10 Verifies SonaEngine.withConfig() creates proper engine instance
2. Single Trajectory 6 Records one trajectory, verifies native stats and in-memory storage
3. 120 Trajectories + Learning 3 Bulk recording above 100-trajectory minimum, forceLearn() produces stored patterns
4. Pattern Search 7 findPatterns() returns JsLearnedPattern objects with centroid, clusterSize, avgQuality
5. Predict After Learning 4 Predictions return source: 'native-sona' with pattern-derived confidence
6. Embedding Determinism 4 Same input produces same embedding, L2 normalized
7. Backward Compatibility 4 In-memory storage and frequencyPredict() still work
8. New Methods 6 forceLearn() and getNativeStats() return expected types
9. Error Recovery 2 Bad inputs (null, undefined, empty) handled without crash
10. RL Methods 8 All existing RL methods still work

Live MCP tool verification

Also verified end-to-end through the claude-flow MCP tools:

  • trajectory-start -> trajectory-step -> trajectory-end all working correctly
  • SONA learning triggers on trajectory end (confirmed via stats)
  • pattern-store and pattern-search with 384-dim ONNX embeddings working
  • Semantic similarity search returning relevant patterns

Version history of the bug

agentdb version SonaTrajectoryService Bugs present
alpha.0alpha.3 Not present N/A
alpha.4 (first appearance) 230 lines All 3 bugs from day 1
alpha.5alpha.8 Grew to 538 lines (RL methods added) Same 3 bugs
alpha.9 (current installed) 538 lines Same 3 bugs
alpha.10 (latest) Unknown Likely same

Breaking changes

None. Full backward compatibility maintained.

Related issues

Fixes ruvnet/ruflo#1243

Files changed

  • packages/agentdb/src/services/SonaTrajectoryService.jsNew file (source was never committed to git; only existed in npm-published dist/). Contains the fixed native API integration.
  • packages/agentdb/tests/sona-trajectory-native-api.test.mjs — New test suite (54 assertions)

🤖 Generated with claude-flow

Fix three bugs preventing native SONA engine integration:

1. initialize() - stored module object instead of SonaEngine instance.
   Now uses SonaEngine.withConfig() for full learning parameter control.

2. recordTrajectory() - called nonexistent methods (recordStep, record,
   addStep). Now uses beginTrajectory → addTrajectoryStep → endTrajectory
   pipeline.

3. predict() - called nonexistent methods (predict, selectAction).
   Now uses findPatterns() to query learned patterns.

Added stateToEmbedding() for deterministic hash-based embeddings when
no external embedding is provided.

Added forceLearn() and getNativeStats() for monitoring/testing.

All changes maintain full backward compatibility with in-memory storage
and frequency-based prediction fallback.

Tested with 54 assertions across 10 test categories - all passing.

Fixes ruvnet/ruflo#1243
54 assertions across 10 test categories:
- Native engine initialization with SonaEngine.withConfig()
- Single trajectory recording
- 120-trajectory bulk recording with forceLearn()
- Pattern search after learning
- Prediction with native source
- Embedding determinism and L2 normalization
- Backward compatibility
- New methods (forceLearn, getNativeStats)
- Error recovery with bad inputs
- RL methods backward compat
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: SonaTrajectoryService does not use native @ruvector/sona API correctly

1 participant