Role colors are solved per role in isolation, so 18 of 21 role pairs are perceptually identical as backgrounds #358

Closed
opened 2026-08-26 02:51:05 +00:00 by coilyco-ops · 3 comments
Member

Raised from an aterm design pass, coilyco-flight-deck/agentic-os#1245. Landing the fix here rather than in aterm, because presentation every renderer shares belongs beside native-ui, not inside one terminal launcher.

The problem

Each role carries a favorite_color chosen to look good on its own. Nothing checks the set against itself. aterm derives a window background by mixing that accent 8 percent into a shared near-black, and the result is seven windows that are individually tasteful and mutually indistinguishable.

Measured across the seven live roles, OKLab distance x100:

closest pair   1.73   platform / devrel
               1.84   tpm / devrel
               1.88   frontend / gamedev
               1.93   platform / tpm
               2.07   sysadmin / eval
widest pair    3.74   eval / frontend

18 of 21 pairs below the side-by-side JND of ~2.0

Side-by-side JND is the generous threshold. These windows are never side by side.

Why it lands here and not in aterm

There is already a precedent for agent-compose owning rendered presentation: native-ui emits a full Claude Code theme per role plus twelve personality-derived spinner verbs. A separation-solved palette is the same kind of artifact. Solving it here means every renderer inherits it, and aterm stops deriving color at all and just renders what it is handed.

It also has to be solved here for a correctness reason. Separation is a property of the set, not of any one role, so it cannot be computed by a consumer holding one overlay. Only the catalogue sees all the roles at once.

A solve that works

Hold lightness and chroma constant in OKLCH and spread the role hues to equal spacing, choosing the offset that minimises total rotation from each role's own hue:

L 0.235, C 0.055, mean rotation 8.1 degrees per role

min pairwise dE   1.73 -> 3.77   (2.2x)
max pairwise dE   3.74 -> 10.63

Eight degrees is small enough that every role still reads as its own color. A naive constant-L-and-C pass without the hue spread only reaches 1.90, because sysadmin #009895 and eval #3fd7a9 sit at nearly the same hue, so the spread step is the part that matters.

Asks

  • emit a derived, separation-solved background per role alongside the accent, rather than leaving each consumer to tint
  • solve against the live roster rather than a fixed seven, since roles turn over
  • assert a minimum pairwise floor in a test, so adding an eighth role fails loudly instead of quietly colliding
  • keep the authored favorite_color as the accent it already is. This adds a derived value, it does not replace identity
Raised from an aterm design pass, coilyco-flight-deck/agentic-os#1245. Landing the fix here rather than in aterm, because presentation every renderer shares belongs beside `native-ui`, not inside one terminal launcher. ## The problem Each role carries a `favorite_color` chosen to look good on its own. Nothing checks the set against itself. aterm derives a window background by mixing that accent 8 percent into a shared near-black, and the result is seven windows that are individually tasteful and mutually indistinguishable. Measured across the seven live roles, OKLab distance x100: ``` closest pair 1.73 platform / devrel 1.84 tpm / devrel 1.88 frontend / gamedev 1.93 platform / tpm 2.07 sysadmin / eval widest pair 3.74 eval / frontend 18 of 21 pairs below the side-by-side JND of ~2.0 ``` Side-by-side JND is the generous threshold. These windows are never side by side. ## Why it lands here and not in aterm There is already a precedent for agent-compose owning rendered presentation: `native-ui` emits a full Claude Code theme per role plus twelve personality-derived spinner verbs. A separation-solved palette is the same kind of artifact. Solving it here means every renderer inherits it, and aterm stops deriving color at all and just renders what it is handed. It also has to be solved here for a correctness reason. Separation is a property of **the set**, not of any one role, so it cannot be computed by a consumer holding one overlay. Only the catalogue sees all the roles at once. ## A solve that works Hold lightness and chroma constant in OKLCH and spread the role hues to equal spacing, choosing the offset that minimises total rotation from each role's own hue: ``` L 0.235, C 0.055, mean rotation 8.1 degrees per role min pairwise dE 1.73 -> 3.77 (2.2x) max pairwise dE 3.74 -> 10.63 ``` Eight degrees is small enough that every role still reads as its own color. A naive constant-L-and-C pass without the hue spread only reaches 1.90, because sysadmin `#009895` and eval `#3fd7a9` sit at nearly the same hue, so the spread step is the part that matters. ## Asks - emit a derived, separation-solved background per role alongside the accent, rather than leaving each consumer to tint - solve against the live roster rather than a fixed seven, since roles turn over - assert a minimum pairwise floor in a test, so adding an eighth role fails loudly instead of quietly colliding - keep the authored `favorite_color` as the accent it already is. This adds a derived value, it does not replace identity
Author
Member

Confirmed still unshipped as of agent-compose v2.54.0, and the consumer side is unchanged.

Upstream. A live agent-compose overlay --json carries favorite_color and nothing derived beside it. There is no separation-solved background key, so nothing here has partly landed.

Downstream. aterm still derives the background itself. aterm platform claude --dry-run on main at d24e5d5a:

"brand": {
  "accent": "#9c8b31",
  "background": "#1b1c18",
  "selection_text": "#101216"
}

That is the 8-percent tint into near-black the body describes, still living in aterm/brand.go. The "aterm stops deriving color at all and just renders what it is handed" outcome is exactly as far away as when this was filed.

The measured set is also unchanged, so the 18-of-21 finding needs no re-derivation. Seven live roles, same seven accents: platform #9c8b31, sysadmin #009895, eval #3fd7a9, frontend #e583f7, gamedev #637ffd, tpm #e36966, devrel #f7ab5d.

One consumer that appeared after filing. The kitty swap (agentic-os#1266) moved aterm's brand onto background, cursor, selection_background, selection_foreground, and scripts/check-aos-release.sh now gates that the brand survives into the terminal arguments. When aterm switches to a handed-down background, that gate is the thing that has to stay green, so it is worth naming in the downstream issue rather than discovering at release time.

Blocks agentic-os#1261 by choice, per the sequencing recorded there.

Confirmed still unshipped as of `agent-compose v2.54.0`, and the consumer side is unchanged. **Upstream.** A live `agent-compose overlay --json` carries `favorite_color` and nothing derived beside it. There is no separation-solved background key, so nothing here has partly landed. **Downstream.** aterm still derives the background itself. `aterm platform claude --dry-run` on `main` at `d24e5d5a`: ```json "brand": { "accent": "#9c8b31", "background": "#1b1c18", "selection_text": "#101216" } ``` That is the 8-percent tint into near-black the body describes, still living in `aterm/brand.go`. The "aterm stops deriving color at all and just renders what it is handed" outcome is exactly as far away as when this was filed. **The measured set is also unchanged**, so the 18-of-21 finding needs no re-derivation. Seven live roles, same seven accents: platform `#9c8b31`, sysadmin `#009895`, eval `#3fd7a9`, frontend `#e583f7`, gamedev `#637ffd`, tpm `#e36966`, devrel `#f7ab5d`. **One consumer that appeared after filing.** The kitty swap (agentic-os#1266) moved aterm's brand onto `background`, `cursor`, `selection_background`, `selection_foreground`, and `scripts/check-aos-release.sh` now gates that the brand survives into the terminal arguments. When aterm switches to a handed-down background, that gate is the thing that has to stay green, so it is worth naming in the downstream issue rather than discovering at release time. Blocks agentic-os#1261 by choice, per the sequencing recorded there.
Author
Member

Landed, both halves

Upstream on main as 32c972d. The consumer side is agentic-os#1280.

Measured, seven live roles

tinted 8 percent into #101216   closest pair 0.0109 OKLab
solved                          closest pair 0.0386        3.5x
mean rotation 11.3 degrees, worst 28.3 (sysadmin)

The worst rotation is sysadmin, and it is the case the body predicted: sysadmin #009895 and eval #3fd7a9 sit at nearly the same hue, so one of them has to move a long way. Everything else turns between 3.3 and 15.8 degrees.

My mean is 11.3 against the body's 8.1. The difference is the assignment: I hold each role's own hue order and rotate the whole set by the offset that minimises total turn, rather than solving L and C first and spreading afterwards. Recording it rather than rounding it to the predicted number.

platform  #9c8b31 -> #1f2000     sysadmin  #009895 -> #00242e
eval      #3fd7a9 -> #002616     frontend  #e583f7 -> #28152f
gamedev   #637ffd -> #111d38     tpm       #e36966 -> #331119
devrel    #f7ab5d -> #301700

Against the asks

  • A derived background per role beside the accent. background in agent-compose.overlay.v1 and in the role catalogue.
  • Solved against the live roster. ResolveBackgrounds runs over RoleOrder at load, after the accents resolve, so an eighth role is solved with the rest rather than appended to a fixed seven.
  • A minimum pairwise floor. Asserted at roster load, not only in a test, so an eighth role that runs out of hue circle fails where every other roster invariant fails. Floor 0.030 against today's 0.0386. Unit tests cover the solve, the darkness and chroma bounds, the rotation bounds, and an empty or unparsable set.
  • The authored favorite_color stays the accent. Untouched.

The palette snapshot now records both derivations and both floors, so a roster edit still arrives as a reviewable diff:

Closest accent pair: 0.1590
Closest background pair: 0.0386

One thing not done

aterm keeps the 8 percent tint as a fallback, because every installed agent-compose predates this key, v2.54.0 included. "aterm stops deriving color at all" arrives when the release does; the fallback then goes unused and can be deleted without touching anything else. Leaving this issue open or closing it is your call, since the upstream asks are all met and the remaining line is a release away.

## Landed, both halves Upstream on `main` as `32c972d`. The consumer side is agentic-os#1280. ## Measured, seven live roles ``` tinted 8 percent into #101216 closest pair 0.0109 OKLab solved closest pair 0.0386 3.5x mean rotation 11.3 degrees, worst 28.3 (sysadmin) ``` The worst rotation is sysadmin, and it is the case the body predicted: sysadmin `#009895` and eval `#3fd7a9` sit at nearly the same hue, so one of them has to move a long way. Everything else turns between 3.3 and 15.8 degrees. My mean is 11.3 against the body's 8.1. The difference is the assignment: I hold each role's own hue order and rotate the whole set by the offset that minimises total turn, rather than solving L and C first and spreading afterwards. Recording it rather than rounding it to the predicted number. ``` platform #9c8b31 -> #1f2000 sysadmin #009895 -> #00242e eval #3fd7a9 -> #002616 frontend #e583f7 -> #28152f gamedev #637ffd -> #111d38 tpm #e36966 -> #331119 devrel #f7ab5d -> #301700 ``` ## Against the asks - [x] **A derived background per role beside the accent.** `background` in `agent-compose.overlay.v1` and in the role catalogue. - [x] **Solved against the live roster.** `ResolveBackgrounds` runs over `RoleOrder` at load, after the accents resolve, so an eighth role is solved with the rest rather than appended to a fixed seven. - [x] **A minimum pairwise floor.** Asserted at **roster load**, not only in a test, so an eighth role that runs out of hue circle fails where every other roster invariant fails. Floor 0.030 against today's 0.0386. Unit tests cover the solve, the darkness and chroma bounds, the rotation bounds, and an empty or unparsable set. - [x] **The authored `favorite_color` stays the accent.** Untouched. The palette snapshot now records both derivations and both floors, so a roster edit still arrives as a reviewable diff: ``` Closest accent pair: 0.1590 Closest background pair: 0.0386 ``` ## One thing not done aterm keeps the 8 percent tint as a fallback, because every installed agent-compose predates this key, v2.54.0 included. "aterm stops deriving color at all" arrives when the release does; the fallback then goes unused and can be deleted without touching anything else. Leaving this issue open or closing it is your call, since the upstream asks are all met and the remaining line is a release away.
Author
Member

Closed in the 2026-08-26 backlog burn-down (coilyco-bridge/agentic-os-kai#901).

Verified as already landed: Closing on 32c972d. The overlay and role catalogue now carry a separation-solved background per role (closest pair 0.0109 to 0.0386, 3.5x), ResolveBackgrounds solves against the live roster, a 0.030 pairwise floor is asserted at roster load rather than only in a test, and the authored favorite_color is untouched. The remaining aterm fallback is downstream in agentic-os#1280.

This issue was open only because nothing closed it when the work shipped. If the verification is wrong, reopen it. The whole set is recoverable with state:closed label:burndown-2026-08.

Closed in the 2026-08-26 backlog burn-down (coilyco-bridge/agentic-os-kai#901). Verified as already landed: Closing on 32c972d. The overlay and role catalogue now carry a separation-solved `background` per role (closest pair 0.0109 to 0.0386, 3.5x), ResolveBackgrounds solves against the live roster, a 0.030 pairwise floor is asserted at roster load rather than only in a test, and the authored favorite_color is untouched. The remaining aterm fallback is downstream in agentic-os#1280. This issue was open only because nothing closed it when the work shipped. If the verification is wrong, reopen it. The whole set is recoverable with `state:closed label:burndown-2026-08`.
coilyco-ops 2026-08-27 03:07:02 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/agent-compose#358
No description provided.