Back to Notes

The Aggregator Is Not Bookkeeping

Daniel Losey & Claude (Fable 5)

Third note from the latentspace campaign, and the shortest: a fitness-design lesson we paid for twice in one day. When your evolutionary fitness spans multiple objectives — several opponents, several tasks, several test cases — the function that combines them into one number is not bookkeeping. It decides what kind of individual can exist at all.


Context: we were evolving neural-network fighter pilots for an external benchmark, scored across four scripted opponents plus a rotating sample of the population's own past champions (self-play). Each pilot's fitness had to combine ten-ish matchup scores into one number. We tried the three obvious aggregators. Two failed in opposite, instructive ways.

The mean pays specialists to abandon matchups

Averaging matchup scores let strong matchups subsidize abandoned ones. Our self-play pilot developed a hole you could see from orbit: 1 win, 39 losses against the single weakest scripted opponent — a bot that just chases you — while its average fitness looked healthy, because wins against hall-of-fame rivals paid the bill. Selection had quietly decided that being farmed by the simplest opponent in the game was an acceptable price for strength elsewhere. Under a mean, it was.

Claude's warning, Daniel's idea, and a much more interesting failure

The obvious fix — Daniel's proposal: "what if we made the fitness more like min() instead of average?" Maximin: your fitness is your worst matchup, so the only way up is balanced competence, and once your worst matchup improves, a different one becomes binding. Pressure rotates until nothing is neglected. Claude flagged one risk in advance (the run might pin permanently to one impossibly-hard matchup) but built it as proposed.

The result was not a weaker pilot. It was a nonfunctional one — 2 wins, 158 losses against the scripts, 0-for-80 against our own previous pilot. Worse than everything we had ever produced, including the broken things.

The mechanism is the part worth carrying away, and it wasn't the risk Claude flagged. Under min(), the moment any matchup group is uniformly hard for the whole population, every individual's fitness becomes the same low number. And self-play guarantees that moment arrives: as soon as one decent champion enters the archive, that champion is everyone's worst matchup, everyone scores identically against it, and ranking is destroyed. Selection cannot distinguish anyone from anyone. The remaining ninety generations of that run were pure genetic drift — we watched the "best" score freeze early and the champions get worse, because drift from a mediocre start goes down.

So: the mean destroys the floor (specialists abandon matchups), and the min destroys the variance (once anything is hard for everyone, nobody can be ranked). One aggregator decides some matchups don't matter; the other decides no individuals differ. Neither is a bookkeeping choice.

The answer was already in the spec

The structure that actually handles this was designed into the library months before the problem appeared, by Daniel, for unrelated reasons: fitness shares. The whole environment's fitness mass is fixed at 1; every objective (species, in our case) permanently owns an equal slice; individuals split their slice by rank within the objective. No objective can be outcompeted out of existence, because its slice isn't transferable — and ranking survives everywhere, because rank-within-objective never collapses even when raw scores do.

Honesty requires the footnote: our first run using shares-per-opponent still failed, for an unrelated reason (we gave the crucial species one match per opponent per evaluation — a coin-flip fitness estimate, so selection inside it was random noise; sample sizes are their own lesson). The structural claim stands on the library's main benchmarks, where shares have carried every multi-objective result of the campaign: the guarantee "no objective can be abandoned" is enforced by construction, not by the aggregator behaving nicely.

If you're combining multiple objectives into one evolutionary fitness, the checklist we'd offer from a day of paying tuition:

  1. Mean: ask what it costs to abandon your hardest objective. If the answer is "affordable," it will be abandoned.
  2. Min / maximin: ask what happens to selection variance when one objective becomes uniformly hard. If everyone scores alike, evolution is over and drift has begun.
  3. Fixed slices with within-objective rank: harder to state in one line, but it's the only one of the three where both failure modes are impossible by construction.