AttentionCoordinator: coordinateAgents/hierarchicalCoordination return all-NaN without erroring; mechanism field fabricated (3 of 5 silently run flash); routeToExperts unranked
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(perdist/coordination/attention-coordinator.d.ts:14) requires bothembedding: Float32Arrayandvalue: any. OmittingvaluethrowsCannot read properties of undefined (reading 'value')fromweightedConsensus(attention-coordinator.js:299) — after the attention call has already run.routeToExperts(task, agents, topK)readstask.embeddingandagents.map(a => a.specialization). Passing a task string and an array of agent-name strings — the natural reading of the signature — throwsCannot read properties of undefined (reading 'length')from insidestackEmbeddings(: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
- 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/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