All five attention mechanisms return K and ignore V entirely: native classes live on .default but are read off the namespace, so every constructor is undefined and it silently falls back to an unnormalized JS stub
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 812
- Forks
- 175
- Avg merge
- 2m
- Merged PRs (30d)
- 3
Description
Summary
Every attention mechanism on EnhancedAgentDBWrapper returns K and ignores V entirely. Changing V has no effect on the output at all. This affects flashAttention, linearAttention, multiHeadAttention, hyperbolicAttention and moeAttention identically.
The underlying cause is that the native @ruvector/attention classes are imported off the module namespace when they actually live on its default export, so every native constructor is undefined, every mechanism silently degrades to the JS fallback, and that fallback does not implement attention correctly.
Version: agentic-flow 2.1.2, @ruvector/attention 0.1.32, Node v22.23.0, macOS 15.
Reproduce
const w = new EnhancedAgentDBWrapper({ enableAttention: true, attentionConfig: { type: 'flash' } });
await w.initialize(); // dimension 384
const D = 384, fill = v => Float32Array.from({ length: D }, () => v);
const Q = fill(1), K = fill(7), V = fill(3); // maximally distinguishable
for (const m of ['linearAttention','flashAttention','multiHeadAttention','hyperbolicAttention','moeAttention']) {
const r = await w[m](Q, K, V);
console.log(m, Array.from(r.output).slice(0, 3));
}
linearAttention out[0:3]=[7,7,7] → K, not V
flashAttention out[0:3]=[7,7,7] → K, not V
multiHeadAttention out[0:3]=[7,7,7] → K, not V
hyperbolicAttention out[0:3]=[7,7,7] → K, not V
moeAttention out[0:3]=[7,7,7] → K, not V
V has no influence whatsoever:
const a = await w.flashAttention(Q, K, fill(3));
const b = await w.flashAttention(Q, K, fill(999));
// outputs byte-identical: [7,7,7] and [7,7,7]
Attention must return a weighted combination of V. Returning K means the values are never consulted.
Note this is easy to miss in a smoke test: if you pass the same array as both K and V (the natural thing to do when testing), the output looks correct because K === V. It only shows up when they differ.
Root cause 1 (confirmed): the native module is imported off the wrong object
dist/core/attention-native.js:
nativeAttention = await import('@ruvector/attention'); // namespace import
...
this.nativeInstance = new nativeAttention.MultiHeadAttention(this.hiddenDim, this.numHeads); // line 49
this.nativeInstance = new nativeAttention.FlashAttention(this.hiddenDim); // line 98
this.nativeInstance = new nativeAttention.LinearAttention(this.hiddenDim, this.hiddenDim); // line 139
this.nativeInstance = new nativeAttention.HyperbolicAttention(this.hiddenDim); // line 169
this.nativeInstance = new nativeAttention.MoEAttention(this.hiddenDim); // line 205
But @ruvector/attention@0.1.32 exposes everything on default:
const ns = await import('@ruvector/attention');
Object.keys(ns) // [ 'default' ] ← nothing else
typeof ns.MultiHeadAttention // 'undefined'
typeof ns.default.MultiHeadAttention // 'function'
typeof ns.default.MoEAttention // 'function'
typeof ns.default.AdamOptimizer // 'function'
So every new nativeAttention.X(...) throws, the constructors are caught, and the wrapper falls through to the JS implementation. The console output confirms this — it prints ✅ Loaded @ruvector/attention NAPI module and then, on the very next line, AttentionService initialized (runtime: js). The success message refers to the import resolving, not to any class being usable.
One-line fix: nativeAttention = (await import('@ruvector/attention')).default;
This also explains #186, where hooks route logs TypeError: MoEAttention is not a constructor and TypeError: AdamOptimizer is not a constructor before falling back and reporting NaN% factors. Both classes exist — on .default.
Root cause 2 (partial): the JS fallback's core is not softmax attention
dist/core/attention-fallbacks.js, scaledDotProductAttention (line 11):
let score = 0;
for (let i = 0; i < dk; i++) score += query[i] * key[i];
score /= Math.sqrt(dk);
const expScore = Math.exp(score);
const weight = expScore; // "Simplified for single K,V pair"
const output = value.map(v => v * weight);
The softmax is never normalized — weight is raw exp(score) rather than exp(score) / Σexp(...), which for a single key must be exactly 1. With 384-dimensional inputs the dot product is large and exp() overflows the result by many orders of magnitude.
I have not isolated the exact line that produces K rather than a scaled V; the MultiHeadAttention.forward path projects per-head and reconcatenates, so more tracing is needed. Flagging that as unfinished rather than guessing. The empirical behaviour above is reproducible regardless of which line is responsible.
Impact
enableAttention: true is the one advanced feature of this wrapper that appears to work — it initialises cleanly, reports timings (AttentionService initialized in 7.28ms), and returns finite non-zero output. But the output is not a function of the values being attended over, so anything built on it (reranking, attentionSearch, coordinateAgents, the documented recommendation and research examples) is operating on a result unrelated to its inputs.
It also fails silently in the most convincing possible way: no error, plausible numbers, correct-looking metadata (mechanism: 'flash', runtime: 'js', executionTimeMs).
Suggested fix
(await import('@ruvector/attention')).defaultinattention-native.js, and assert the constructors are functions before use — anundefinedconstructor should fail loudly, not degrade.- Distinguish "native unavailable" from "native loaded" in the log. Printing
✅ Loaded @ruvector/attention NAPI modulewhileruntime: jsis actively misleading. - Fix
scaledDotProductAttentionto normalize (for a single key the weight is1), and add a test asserting that changingVchanges the output — that single assertion would have caught this. - Verify the fallback returns a combination of
Vfor every mechanism, notK.
Related
- #186 —
hooks routeNaN%factors; theMoEAttention/AdamOptimizerconstructor failures there have the same namespace-vs-default cause. - #189 —
agentic-flow/coreand/coordinationshipped but not exported;enableGNN: truesilently degrades. - #190 —
routeToExpertssilently returns first-K when dimensions mismatch. That method routes throughmoeAttention, so it sits downstream of this.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with dist/core/attention-native.js and inspect how the dynamic import is used before each native attention constructor is created. Then read dist/core/attention-fallbacks.js and run the reported Q/K/V reproduction, adding coverage that changes V for every mechanism. Done means usable native constructors are detected, fallback logging distinguishes native from JS execution, and each path returns output influenced by V rather than K.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- backend, machine-learning, testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100