ruvnet / ruvnet/agentic-flow

AttentionCoordinator: coordinateAgents/hierarchicalCoordination return all-NaN without erroring; mechanism field fabricated (3 of 5 silently run flash); routeToExperts unranked

Open
#216 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
812
Forks
175
Avg merge
2m
Merged PRs (30d)
3

Description

Summary

All four documented AttentionCoordinator methods fail on 2.1.2. Two throw; the other two return well-formed objects full of NaN/null with no error signal, which makes them the more dangerous pair — a caller cannot tell a coordination result from a failure.

Cross-references rather than duplicates: #190 (routeToExperts unranked fallback), #193 (graphRoPEAttention missing), #191/#215 (values ignored).

Environment: agentic-flow 2.1.2 (global install), Node 22, macOS 15.6 arm64. Coordinator constructed with the wrapper's own service: new AttentionCoordinator(wrapper.getAttentionService()).


0. Input shapes are not what the documented signatures suggest

Worth stating first, because it cost me a wrong first reading and will cost others the same.

  • AgentOutput (per dist/coordination/attention-coordinator.d.ts:14) requires both embedding: Float32Array and value: any. Omitting value throws Cannot read properties of undefined (reading 'value') from weightedConsensus (attention-coordinator.js:299) — after the attention call has already run.
  • routeToExperts(task, agents, topK) reads task.embedding and agents.map(a => a.specialization). Passing a task string and an array of agent-name strings — the natural reading of the signature — throws Cannot read properties of undefined (reading 'length') from inside stackEmbeddings (:242).

Neither method validates its input; both fail deep in a helper with a message that doesn't name the missing field. All findings below use the correct shapes.

1. coordinateAgents returns NaN for every mechanism, without erroring

Three agents with values 0, 50, 100 and distinct embeddings:

coordinate/flash        reported="flash"       w=[NaN,NaN,NaN]  sum=NaN  consensus=NaN
coordinate/multi-head   reported="multi-head"  w=[NaN,NaN,NaN]  sum=NaN  consensus=NaN
coordinate/linear       reported="linear"      w=[NaN,NaN,NaN]  sum=NaN  consensus=NaN
coordinate/hyperbolic   reported="hyperbolic"  w=[NaN,NaN,NaN]  sum=NaN  consensus=NaN
coordinate/moe          reported="moe"         w=[NaN,NaN,NaN]  sum=NaN  consensus=NaN

The promise resolves, the returned object has the documented shape, and every number in it is NaN. There is no success: false, no error field, and no throw. A weighted consensus over values in [0, 100] must land in [0, 100]; NaN fails that invariant while passing every truthiness check a caller is likely to write.

2. The mechanism field is fabricated

coordinateAgents (:76-84) has cases for only flash and multi-head:

switch (mechanism) {
    case 'flash':      attentionResult = await this.attentionService.flashAttention(Q, K, V); break;
    case 'multi-head': attentionResult = await this.attentionService.multiHeadAttention(Q, K, V); break;
    default:           attentionResult = await this.attentionService.flashAttention(Q, K, V);
}

So linear, hyperbolic and moe — three of the five documented mechanisms — silently run flash, while the result reports the mechanism the caller asked for. And the value is never validated:

coordinate/TOTALLY-INVENTED   reported="TOTALLY-INVENTED"

An invalid mechanism is accepted, echoed back, and silently executed as flash. Either implement the three missing cases or reject unknown values — echoing an unimplemented mechanism as if it ran is the worst of the three options.

3. routeToExperts returns the first K in array order, unranked

Five agents, with the obviously-correct one planted last (its specialization is bit-identical to the task embedding):

agents:   wrong1, wrong2, wrong3, wrong4, BEST
returned: wrong1 > wrong2 > wrong3
routingScores: [null, null, null]
mechanism: "moe"

routingScores are null, so sort((a,b) => b.score - a.score) compares NaN and is a no-op — the result is input order. The perfect match ranks last and is never selected.

Confirms #190 on 2.1.2. One detail differs from that report: the scores surface as null, not NaN, so a caller checking Number.isNaN(score) will not catch it either.

⚠️ For anyone re-testing: this is invisible if the best candidate is first or the list is short. Plant the known-best last.

4. topologyAwareCoordination throws for every topology

mesh          → this.attentionService.graphRoPEAttention is not a function
hierarchical  → this.attentionService.graphRoPEAttention is not a function
ring          → this.attentionService.graphRoPEAttention is not a function
star          → this.attentionService.graphRoPEAttention is not a function

Unconditional — the method delegates to graph-RoPE regardless of the topology argument, and the service has no such method. Confirms #193 on 2.1.2.

5. hierarchicalCoordination builds a ranking out of NaN

w=[NaN,NaN,NaN]  sum=NaN  consensus=NaN  topAgents=["c","a","b"]

Same NaN pathology as §1, but it additionally returns a confident-looking ranking: the queen boost (w * 1.5) and normalisation (w / sumWeights) both propagate NaN, then topAgents is produced by sorting on those weights and slicing the top 3. A caller reading topAgents gets a plausible ordered list with no indication it means nothing.

Also, hierarchicalCoordination hardcodes const V = K (:205) — the same values-ignored pattern as #191/#215, here written directly into the caller rather than inherited from the attention layer.

6. weightedConsensus and the attention path read unrelated fields

coordinateAgents builds Q/K/V from output.embedding (:70) and then passes the same array to weightedConsensus, which reads output.value (:283-299). Nothing checks the two describe the same thing — an AgentOutput whose embedding and value disagree entirely will produce a confident consensus. Worth a note in the interface docs at minimum.

Reproduction

const { AttentionCoordinator } = await import('agentic-flow/dist/coordination/attention-coordinator.js');
const { EnhancedAgentDBWrapper } = await import('agentic-flow/dist/core/agentdb-wrapper-enhanced.js');
const w = new EnhancedAgentDBWrapper({ enableAttention: true });
await new Promise(r => setTimeout(r, 1500));
const c = new AttentionCoordinator(w.getAttentionService());

const D = 384, emb = v => Float32Array.from({length: D}, () => v);
const outputs = [0, 50, 100].map((v, i) => ({ agentId: 'abc'[i], agentType: 't', embedding: emb(v), value: v }));

console.log(await c.coordinateAgents(outputs, 'flash'));          // all NaN
console.log(await c.coordinateAgents(outputs, 'TOTALLY-INVENTED')); // echoed back, runs flash

const agents = [1,2,3,4].map(i => ({agentId:'wrong'+i, specialization: emb(i)}))
                        .concat([{agentId:'BEST', specialization: emb(999)}]);
console.log(await c.routeToExperts({embedding: emb(999)}, agents, 3)); // wrong1 > wrong2 > wrong3

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with dist/coordination/attention-coordinator.js and its declaration file, then run the supplied Node reproduction against coordinateAgents, routeToExperts, and hierarchicalCoordination. Trace the attention-service calls, input fields, weighting, and ranking behavior; done means documented methods either return finite, correctly ranked results or report invalid and unsupported cases explicitly.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
ai
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.