feat(aterm): draw the identity card from form.geometry, animated on form.motion (#1255) #1276

Merged
coilyco-ops merged 1 commit from aos/claude/mu55-identity-card into main 2026-08-26 07:56:47 +00:00
Member

Closes #1255. Tier 2 of the #1245 dispatch, built on #1251.

What the window opens with

╲╲     ╱╱   ⋅   ◝  ⋅     Angie [she] (Agentic Platform Engineer)
 ╲╲╲ ╱╱╱       ◜◝◝       claude // frontier // acting
   ───˖      ◟◟◞◞◞       agentic-os@aos/claude/mu55-identity-card
 ╱╱╱ ╲╲╲    ◟◟◟◞◞◞◞      /Users/kai/projects/coilyco-flight-deck/agentic-os
╱╱   ˖ ╲╲  ◟◟◟◟◞◞◞◞◞     leased session shadow
⛏️ ⟐ tenacious    🪨 ◆ grounded

Left figure is interlocking-lines, the grapnel. Right is stacked-rounds, the cairn. Both in their own personality color, both with the emoji beside the glyph, and the card printed by the _session stage before it starts the harness.

The figure is generated, not drawn

The issue's insistence is the important part, so the plotter reads the grammar rather than matching the ten shipped values. form.geometry is <arrangement>-<shape>: the arrangement becomes a field predicate over the grid, the shape becomes the glyph, and terminal cells being twice as tall as wide is handled once in the offset.

That means a value nobody has shipped composes today. radial-arcs is a real picture, and it is not the fallback picture, which the tests assert. An unknown word draws something rather than leaving a role with a blank card.

motif picks a sparse background stipple from a hashed palette, so a new motif also gets a texture without a table entry. emblem.emoji joins the glyph in the legend.

The motion

Nine distinct form.motion values ship and none had been animated. Each is a reveal order over the figure's own cells:

  • spinning by angle, settling downward, pulling upward, unfolding outward, bracing inward, scanning across, severing on the diagonal, titrating upward in steps
  • glowing has nothing to reveal in order, so it arrives dim and brightens in place

Ten frames over 400 ms, cursor-up between them, both personalities advancing under their own motion in lockstep. Verified inside a pty: 9 cursor-up frames, 287 truecolor sequences, the grapnel arriving from the bottom and the cairn from the top, which is pulling against settling.

The no-animation escape

Off for --no-motion, off for ATERM_NO_MOTION, and off whenever stdout is not a character device. Measured: 0 frames when piped, 9 in a pty.

How the card reaches the window

_session grew --card <base64 json>, built by buildLaunchPlan and carried in the terminal argv alongside --hold. The launcher cannot draw it, because the window is the child. parseSessionArgs now returns a sessionOptions rather than two positional returns, which is what made room for --card and --no-motion without another bare bool.

--dry-run renders the finished card as well, since a dry run that cannot show it cannot answer whether the identity looks right.

One thing the issue asked for that is not there

The session id. _session runs before aos _native-shadow, which is what assigns the id, so at card time it does not exist. The card names the lease state instead (leased session shadow / unleased, this window shares the canonical checkout), and the shadow prints its own id a moment later. Reordering to get the id would mean drawing the card after the harness has the screen, which defeats the point.

Verification

  • just aterm-fmt, just aterm-lint, just aterm-test clean; uv run pytest 724 passed; pre-commit run --all-files clean
  • new card_test.go: every geometry the fixture ships draws inside the grid with no blank ink and no two alike, an unshipped geometry composes and is distinct from the fallback, six motions produce at least four distinct orders and drop no cells, the card round-trips launcher to session stage with its geometry intact, the card grows with progress, motion is off for each of the three escapes, and a malformed card is refused
  • the pty run above, and aterm tpm --dry-run for a second role's figures

Docs

Nothing added to docs/aterm.md: it is at 7,995 of 8,000 chars with docs/ at 40 of 40 files, so there is no room for the walkthrough this earns. docs/FEATURES.md carries the one-line boundary change. Recorded as the second instance on #1274, which holds the decision.

Closes #1255. Tier 2 of the #1245 dispatch, built on #1251. ## What the window opens with ``` ╲╲ ╱╱ ⋅ ◝ ⋅ Angie [she] (Agentic Platform Engineer) ╲╲╲ ╱╱╱ ◜◝◝ claude // frontier // acting ───˖ ◟◟◞◞◞ agentic-os@aos/claude/mu55-identity-card ╱╱╱ ╲╲╲ ◟◟◟◞◞◞◞ /Users/kai/projects/coilyco-flight-deck/agentic-os ╱╱ ˖ ╲╲ ◟◟◟◟◞◞◞◞◞ leased session shadow ⛏️ ⟐ tenacious 🪨 ◆ grounded ``` Left figure is `interlocking-lines`, the grapnel. Right is `stacked-rounds`, the cairn. Both in their own personality color, both with the emoji beside the glyph, and the card printed by the `_session` stage before it starts the harness. ## The figure is generated, not drawn The issue's insistence is the important part, so the plotter reads the **grammar** rather than matching the ten shipped values. `form.geometry` is `<arrangement>-<shape>`: the arrangement becomes a field predicate over the grid, the shape becomes the glyph, and terminal cells being twice as tall as wide is handled once in the offset. That means a value nobody has shipped composes today. `radial-arcs` is a real picture, and it is not the fallback picture, which the tests assert. An unknown word draws something rather than leaving a role with a blank card. `motif` picks a sparse background stipple from a hashed palette, so a new motif also gets a texture without a table entry. `emblem.emoji` joins the glyph in the legend. ## The motion Nine distinct `form.motion` values ship and none had been animated. Each is a reveal order over the figure's own cells: * `spinning` by angle, `settling` downward, `pulling` upward, `unfolding` outward, `bracing` inward, `scanning` across, `severing` on the diagonal, `titrating` upward in steps * `glowing` has nothing to reveal in order, so it arrives dim and brightens in place Ten frames over 400 ms, cursor-up between them, both personalities advancing under **their own** motion in lockstep. Verified inside a pty: 9 cursor-up frames, 287 truecolor sequences, the grapnel arriving from the bottom and the cairn from the top, which is `pulling` against `settling`. ## The no-animation escape Off for `--no-motion`, off for `ATERM_NO_MOTION`, and off whenever stdout is not a character device. Measured: 0 frames when piped, 9 in a pty. ## How the card reaches the window `_session` grew `--card <base64 json>`, built by `buildLaunchPlan` and carried in the terminal argv alongside `--hold`. The launcher cannot draw it, because the window is the child. `parseSessionArgs` now returns a `sessionOptions` rather than two positional returns, which is what made room for `--card` and `--no-motion` without another bare bool. `--dry-run` renders the finished card as well, since a dry run that cannot show it cannot answer whether the identity looks right. ## One thing the issue asked for that is not there The session id. `_session` runs **before** `aos _native-shadow`, which is what assigns the id, so at card time it does not exist. The card names the lease state instead (`leased session shadow` / `unleased, this window shares the canonical checkout`), and the shadow prints its own id a moment later. Reordering to get the id would mean drawing the card after the harness has the screen, which defeats the point. ## Verification * `just aterm-fmt`, `just aterm-lint`, `just aterm-test` clean; `uv run pytest` 724 passed; `pre-commit run --all-files` clean * new `card_test.go`: every geometry the fixture ships draws inside the grid with no blank ink and no two alike, an unshipped geometry composes and is distinct from the fallback, six motions produce at least four distinct orders and drop no cells, the card round-trips launcher to session stage with its geometry intact, the card grows with progress, motion is off for each of the three escapes, and a malformed card is refused * the pty run above, and `aterm tpm --dry-run` for a second role's figures ## Docs Nothing added to `docs/aterm.md`: it is at 7,995 of 8,000 chars with `docs/` at 40 of 40 files, so there is no room for the walkthrough this earns. `docs/FEATURES.md` carries the one-line boundary change. Recorded as the second instance on #1274, which holds the decision.
feat(aterm): draw the identity card from form.geometry, animated on form.motion (#1255)
All checks were successful
ci / aos-eval-tests (pull_request) Successful in 7s
ci / ward-doctor (pull_request) Successful in 8s
ci / aos-cli-tests (pull_request) Successful in 26s
ci / gate (pull_request) Successful in 57s
7051db3d09
The window now opens on a card the `_session` stage prints before it starts the
harness: name and pronoun, role, seat and tier, expression, workspace, working
directory, and whether the shadow is leased. Color-only identity fails a
colorblind operator, a grayscale screenshot, and every recording; a figure and
a glyph do not.

The figure is generated, not drawn. `form.geometry` is already an instruction
and its grammar is <arrangement>-<shape>, so the plotter reads the two halves
rather than matching the ten shipped values. `radial-arcs` composes today even
though nothing ships it, and an unknown word falls back to a figure rather than
a blank. `motif` picks a sparse background stipple from a hashed palette, so a
new motif gets a texture too, and `emblem.emoji` joins the glyph in the legend.

`form.motion` ships nine distinct values and had never been animated. Each one
is a reveal order over the figure's own cells: spinning by angle, settling down,
pulling up, unfolding out, bracing in, scanning across, severing diagonally,
titrating up in steps, and glowing by brightening in place. Ten frames over
400 ms, both personalities advancing under their own motion, then the harness
gets the screen.

Motion is off wherever it would land in a recording, a log, or CI: `--no-motion`,
`ATERM_NO_MOTION`, or stdout not being a character device.

`--dry-run` renders the finished card too, since a dry run that cannot show it
cannot answer whether the identity looks right.

Nothing was added to docs/aterm.md, which is at 7,995 of 8,000 chars with
docs/ at 40 of 40 files. Recorded in #1274.

Closes #1255

Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: platform
Sign in to join this conversation.
No reviewers
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/agentic-os!1276
No description provided.