extractAttentionWeights divides by sum instead of softmax: negative weights make weighted consensus leave the input range (values in [0,100] → consensus 508)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 812
- Forks
- 175
- Avg merge
- 2m
- Merged PRs (30d)
- 3
Description
Summary
AttentionCoordinator.extractAttentionWeights() normalizes by dividing by the sum rather than applying a softmax. Attention output can be negative, so the resulting "weights" can be negative or greater than 1 while still summing to 1. They are not a probability distribution, and weightedConsensus() then uses them as one.
The observable consequence: a weighted consensus over values in [0, 100] returns 508. A weighted average cannot leave the convex hull of its inputs; this one does, silently, with no error.
All four public coordination methods route through it: coordinateAgents, topologyAwareCoordination, hierarchicalCoordination, and routeToExperts (via extractRoutingScores).
Version: agentic-flow 2.1.2, Node v22.23.0, macOS 15.
Reproduce
const w = new EnhancedAgentDBWrapper({ enableAttention: true, attentionConfig: { type: 'flash' } });
await w.initialize();
const c = new AttentionCoordinator(w.getAttentionService());
const D = 384, vec = f => Float32Array.from({ length: D }, (_, i) => f(i));
const mk = (id, f, v) => ({ agentId: id, agentType: 't', value: v, embedding: vec(f) });
const pi = [mk('pi-1', i => Math.sin(i/10), 100), mk('pi-2', i => Math.cos(i/10), 0)];
const ra = [mk('ra-1', i => Math.sin(i/7), 100), mk('ra-2', i => Math.cos(i/7), 0), mk('ra-3', i => Math.sin(i/3), 100)];
const r = await c.hierarchicalCoordination(pi, ra, -1.0);
weights = [2.7798, -2.3772, 0.943, -1.703, 1.3574] ← 2 of 5 negative, one > 2.7
values = [100, 0, 100, 0, 100]
consensus = 508.0183032788398 ← must lie within [0, 100]
The weights do sum to 1, which is what makes this survive a casual check. But two are negative, so the combination extrapolates far outside the range of the agents' own values.
Note a test that looks reasonable and proves nothing: give every agent the same value (say 100). Any weights summing to 1 then return exactly 100, negative or not. The inputs must differ for the defect to be visible — that is why this is easy to miss.
Cause
extractAttentionWeights(output, numAgents) {
// Simplified: average across dimensions
const dim = output.length / numAgents;
const weights = [];
for (let i = 0; i < numAgents; i++) {
const slice = output.slice(i * dim, (i + 1) * dim);
const avgWeight = slice.reduce((a, b) => a + b, 0) / slice.length;
weights.push(avgWeight);
}
// Normalize to sum to 1
const sum = weights.reduce((a, b) => a + b, 0);
return weights.map((w) => w / sum);
}
Two problems:
- Divide-by-sum is not softmax. It enforces
Σw = 1but preserves sign and admits magnitudes above 1. Attention output over embeddings with negative components routinely produces negative slice means, as above. - No guard on
sum. If the positive and negative means roughly cancel,sum → 0and every weight explodes. There is no epsilon and no check; the failure would beInfinity/NaNpropagating into the consensus rather than an error.
Then weightedConsensus consumes them as if they were a distribution:
if (typeof outputs[0].value === 'number') {
return outputs.reduce((sum, output, i) => sum + output.value * weights[i], 0);
}
if (Array.isArray(outputs[0].value)) { /* element-wise, same issue */ }
topAgents is likewise ranked by these values, so "top contributor" can be an agent whose weight is merely the largest signed number, not the largest contribution.
Affected call sites
coordinateAgents → extractAttentionWeights (line 86)
topologyAwareCoordination → extractAttentionWeights (line 167)
hierarchicalCoordination → extractAttentionWeights (line 209)
routeToExperts → extractRoutingScores → extractAttentionWeights (line 272)
extractRoutingScores partly masks the problem by applying Math.exp(s * 10) afterwards, which restores non-negativity — but it exponentiates values that have already been distorted by the bad normalization, and with temperature 10 the distortion is amplified before the softmax.
Suggested fix
Replace the divide-by-sum with a numerically stable softmax over the slice means:
const maxW = Math.max(...weights);
const exp = weights.map(w => Math.exp(w - maxW));
const sum = exp.reduce((a, b) => a + b, 0);
return exp.map(e => e / sum);
That guarantees w ∈ (0, 1) and Σw = 1, so the consensus stays inside the convex hull of the inputs and topAgents ranks by genuine contribution. extractRoutingScores can then keep its temperature term without compounding an existing distortion.
A regression test worth adding: assert that a consensus over values in [lo, hi] lands in [lo, hi]. That single assertion catches this class of bug permanently — and, importantly, uses differing values so it cannot pass vacuously.
Related
- #190 —
routeToExpertsreturns first-K unranked on dimension mismatch. Same file, different failure; both make routing output that looks plausible and isn't. - #191 — the wrapper discards
V. Independent of this:hierarchicalCoordinationcallsattentionService.hyperbolicAttentiondirectly, so itsconst V = Kis legitimate self-attention, unaffected by the wrapper bug.
Separately, in the documented example for this method
The README-style snippet for hierarchicalCoordination builds agents as { agentId, output, embedding }, but AgentOutput is { agentId, agentType, embedding, value }. Run as written:
consensus.consensus = undefined // .value is undefined, falls to the object branch
topAgents.map(a => a.agentId) = [undefined × 3] // topAgents is already string[]
Both logged lines print undefined with no error. Renaming output: → value: and adding agentType makes it return "Hypothesis A" correctly. Worth fixing in the docs alongside the examples in #188/#189.
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 by locating AttentionCoordinator.extractAttentionWeights and the four affected coordination entry points, then run the supplied hierarchicalCoordination reproduction. Add a regression test using differing values that verifies consensus remains within the input range and weights form a valid distribution. Done means the reported extrapolation and divide-by-sum failure are covered across the relevant paths.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- ai
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 70/100