Skip to content

fleet: model tier and shape slot belong in the MAP-Elites niche, not the genome #290

Description

@justrach

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions