feat(color): derive role favorites from the whole roster so they stay tellable apart #300
No reviewers
Labels
No labels
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/agent-compose!300
Loading…
Reference in a new issue
No description provided.
Delete branch "aos/claude/favorite-color-spread"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes the colour half of the personality work. #296 fixed the prose, #299 fixed the roster, and neither could touch this, because the cause was the derivation.
Why a per-role function could not work
Favoritetook one role's three personality colours and returned their OKLab centroid. It never sees the other roles, so it cannot avoid colliding with them, and the mean is the worst possible estimator for this: qa and ai both meld meticulous and skeptical, whose colours sit 0.045 apart, and two means over nearly the same components land nearly the same place regardless of the third. The shipped palette's closest pair was 0.0347, tighter than any two personalities in the catalogue.Two stages
Stage one weights by distinctiveness. Each component is discounted by how many groups share it, so whatever makes a role that role leads its colour. This is the roster's own semantics doing the work rather than an arbitrary nudge. Alone it takes the closest pair from 0.0347 to 0.0808.
Stage two spreads globally. Every colour walks away from its nearest neighbour to grow the smallest pairwise distance, bounded by a drift cap so none leaves its own blend, then clamps back into the legible band and sRGB gamut. Fixed iteration order, no randomness, so one roster always derives one palette.
Stage one is what makes stage two honest. Without it the spread step would push colours apart in arbitrary directions. With it, the direction each colour moves is already the direction of its own differentiator.
Palette
#b39258to#a88840#cb7471to#d26c74#62a5d9to#56aeed#229ca7to#2d8ea4#ac8fd7to#ca7fef#b39e50to#b1ba65#da9395to#eea5a1#60abc8to#0dc8b0The floor replaces a check the spread step broke
Roster loading asserted that no two roles share a favourite colour. The spread step makes that unreachable: two roles melding identical personalities no longer land on one colour, they land ~0.06 apart. Equality can no longer catch the case the check existed for, so it is now a floor of 0.08 on the closest pair.
TestFavoritesCannotRescueIdenticalGroupspins the ~0.06 figure the floor depends on.Note the effective bound is a shade over twice the drift cap, not exactly it, because band and gamut repair runs after the projection. My first test asserted the tight bound and failed at 0.0613, which is how I found it.
Eight call sites became one
overlay,resolver,palette,nativeui,roster(twice),person, andperson/snapshoteach derived the role favourite independently from the same inputs. That duplication is why the collision was invisible from any one of them, and it would have let a roster-wide derivation disagree with itself per surface. They now readRole.FavoriteColor, resolved once at load byPerson.ResolveFavoriteColors.SnapshotRolecarried its ownFavoriteColorfield which, onceRolegained one, shadowed the embedded field on unmarshal and silently emptied it. Dropped.Snapshot via pre-commit
Because the derivation is global, editing one role can move every colour.
internal/palette/role-palette.txtis the committed record, regenerated by a repo-local pre-commit hook, so a roster edit arrives as a reviewable palette diff rather than silent drift across statuslines, identity cards, and bundles.The hook is repo-local rather than an agentic-os catalogue validator because it needs this repo's own colour math, which cannot live upstream. It regenerates rather than only checking, so pre-commit's own modified-files detection forces the diff into the commit under review.
--checkexists for CI.The artifact is a
.txtunderinternal/palette/rather than a docs page becausedocs/is exactly at its 40-page cap and every page is exactly at its 120-line cap. The explanation that would have gone indocs/personality.mdis in the artifact's own header instead, where it is also the thing a reviewer is already looking at.Verification
go build ./...,go vet ./...,go test ./...,gofmt -l, andpre-commit run --all-filesall pass. New tests cover determinism across repeated runs, legibility, drift bounds, the identical-groups case, single-group equivalence with the plain mean, empty input, and snapshot freshness and drift detection. I verified the hook fails on a hand-edited snapshot and auto-repairs it.One thing for you
color.Favoritekeeps the plain-mean behaviour and now has no callers outside the colour package's own tests, where it is the baseline the new derivation is measured against. Worth keeping for that, but easy to unexport if you would rather.