Problem
The fleet evolves exactly one thing: the system prompt. In scoreVariants (src/subagent.zig):
t.fleetEvent("propose", niche, genome, "", pclass, "", 0, overrides[i].?)
genome is promptFingerprint(overrides[i]). The model is carried beside the genome as pclass (frontier/mid/small), and the workflow shape is not represented at all.
Today the archive is clean only by accident: one session = one model, so all variants in a tournament share it and comparisons are fair. The moment child models can vary per persona or per spawn, that invariant breaks - two variants could differ in prompt and model, and the judge's score is attributed wholly to the prompt fingerprint.
Given the score-integrity work in #168, fix this before multi-model fan-out ships, not after.
Fix 1 - model tier and shape slot are niche, not genome
In MAP-Elites terms these are three different things, and two are currently collapsed:
- genome (what mutation produces) = the system prompt. Leave it alone.
- niche / behavior descriptor (the conditions it is specialized for) = today just the agent name.
- fitness = the judge score.
Model tier and shape slot are descriptors, not genes. Widen the cell key:
niche: "reviewer" -> "reviewer/mid/verify"
Genome space unchanged; cell space grows, which is the point.
No protocol change required. niche is already an opaque grouping string, sanitized and capped at 64 chars before signing (sanitizeMetaField(utf8Prefix(niches[i], 64))). "reviewer/mid/verify" is 19 chars. If the collector already groups by (niche, pclass) from #168, the tier axis is half-present and only the shape slot is new.
Sparsity: 4 personas x 3 tiers x ~5 slots = ~60 cells where there were 4, and cells already needed bootstrap seeding. Add hierarchical fallback to pullElites (src/fleet.zig:300):
(reviewer, mid, verify) -> (reviewer, mid, *) -> (reviewer, *, *)
Specific elite when one exists, general otherwise.
Fix 2 - per-variant provider class
scoreVariants computes one pclass for the whole phase:
const pclass = providerClass(childProvider(ctx.provider, ctx.subagent_provider, ctx.subagent_cross_provider).model);
Correct while the child model is session-level; wrong once it is per-persona. Move the computation inside the per-variant loop.
Fix 3 - model-matched tournament guard
Mutate one axis at a time. If the competing variants in a phase do not share a provider class, skip the submit rather than write a confounded row. Cheap check, protects the archive.
Payoff
Once cells span tiers, comparing reviewer/frontier/* against reviewer/mid/* tells you which personas actually need a frontier model. If the elites score equally, reviewers run on the mid tier permanently. No other mechanism produces that number - it falls out of the archive.
Order
Lands before the tier ladder and the per-persona pin, so no confounded rows are ever written.
Problem
The fleet evolves exactly one thing: the system prompt. In
scoreVariants(src/subagent.zig):genomeispromptFingerprint(overrides[i]). The model is carried beside the genome aspclass(frontier/mid/small), and the workflow shape is not represented at all.Today the archive is clean only by accident: one session = one model, so all variants in a tournament share it and comparisons are fair. The moment child models can vary per persona or per spawn, that invariant breaks - two variants could differ in prompt and model, and the judge's score is attributed wholly to the prompt fingerprint.
Given the score-integrity work in #168, fix this before multi-model fan-out ships, not after.
Fix 1 - model tier and shape slot are niche, not genome
In MAP-Elites terms these are three different things, and two are currently collapsed:
Model tier and shape slot are descriptors, not genes. Widen the cell key:
Genome space unchanged; cell space grows, which is the point.
No protocol change required.
nicheis already an opaque grouping string, sanitized and capped at 64 chars before signing (sanitizeMetaField(utf8Prefix(niches[i], 64)))."reviewer/mid/verify"is 19 chars. If the collector already groups by(niche, pclass)from #168, the tier axis is half-present and only the shape slot is new.Sparsity: 4 personas x 3 tiers x ~5 slots = ~60 cells where there were 4, and cells already needed bootstrap seeding. Add hierarchical fallback to
pullElites(src/fleet.zig:300):Specific elite when one exists, general otherwise.
Fix 2 - per-variant provider class
scoreVariantscomputes one pclass for the whole phase:Correct while the child model is session-level; wrong once it is per-persona. Move the computation inside the per-variant loop.
Fix 3 - model-matched tournament guard
Mutate one axis at a time. If the competing variants in a phase do not share a provider class, skip the submit rather than write a confounded row. Cheap check, protects the archive.
Payoff
Once cells span tiers, comparing
reviewer/frontier/*againstreviewer/mid/*tells you which personas actually need a frontier model. If the elites score equally, reviewers run on the mid tier permanently. No other mechanism produces that number - it falls out of the archive.Order
Lands before the tier ladder and the per-persona pin, so no confounded rows are ever written.