Four distinct coach identities, the first-run selection, in-app presence, and the
change-coach handover. Drawn in the language the app actually ships —
packages/sana-ui on origin/develop exports DeskRoot,
Masthead, ContextBar, ReviewLane, Proposal,
rendering omhead / rulebar / ctxbar / orient.
Those are the Desk classes verbatim, so this pack uses the real stylesheet rather than a
lookalike.
A coach signals style, never authority. So none of the four has a financial background — not a softened one, none. Their crafts are story editing, teaching argument, wire editing and archiving: chosen for how a person listens and presses, and chosen because they make a credential claim structurally impossible rather than merely discouraged. There is no CFA to omit if nobody was ever in finance.
This sits on OwnerMind's standing rule — show, structure, compare, calculate, journal, question; never score, rank, recommend or size. A coach changes how the questions sound, never what the answer is. Two users with different coaches and identical inputs must reach identical arithmetic.
Worth stating before the roster, because the app already has ten named agents and these four are not more of them:
| Existing agents | New coaches | |
|---|---|---|
| Axis | Topic — evidence, accounting, moat, valuation | Style — how they talk to you |
| How many | Many; you consult whichever fits the question | One, permanent, until you change it |
| Chosen | Per question, in the Review Lane | Once at first sign-in |
| Examples | Mira Chen · Elias Ward · Priya Raman · Tessa Morgan · Julian Park | Nadia Halim · Daniel Osei · June Sato · Arthur Lindqvist |
Keeping these axes separate is what stops the roster becoming fourteen faces with no organising idea. Your coach is the voice that greets you and holds your history; the specialists are who your coach brings in.
Spent ten years as a documentary story editor, sitting with hundreds of hours of footage until the through-line showed itself. She is used to long projects that only make sense in retrospect, and to resisting the cut that would feel satisfying too early.
Taught argumentation for a decade and ran a university debate programme where students had to argue the side they disagreed with. He is more interested in whether reasoning holds than in whether he agrees with the conclusion.
Worked the wire desk of a daily newsroom, where a story had to be accurate and eighty words. She learned to find the one operative fact quickly and to say when something is not yet worth writing.
Spent his career as an archivist of personal papers, reading how people's thinking changed across decades of letters and notebooks. He is unusually good at noticing when someone has quietly changed their mind without saying so.
Tavus-ready by construction, not by retrofit. Every portrait sits in a fixed circular crop with a flat, low-contrast field behind the head and no detail near the jaw, so a still image and a talking-head loop occupy exactly the same frame. Swapping one for the other changes nothing else on the page — no reflow, no second layout to maintain. The CourseVenture companion faces are the direct precedent.
The coloured fields on this page are placeholders standing in for real portraits. What is being specified is the frame, the safe area, the sizes and the states:
| Size | Where | Video |
|---|---|---|
xl 132px | First-run selection, handover | Loop on hover / on select |
lg 84px | Roster cards | Still, loop on hover |
md 52px | Coach dock header | Loop while speaking |
sm 34px | Review Lane roster | Still |
xs 26px | Inline in a proposal | Still |
The field colours are deliberately outside the --pos / --neg /
--warn scale, so a coach's identity can never be misread as a status signal.
Roster, selection and handover stand as delivered. The panel is rebuilt to the order asked for: coach face at the top as the identity anchor, specialist faces directly beneath (hover or keyboard focus reveals the role; on touch the first tap reveals and a second brings them in, so one control teaches itself), one always-on box holding the coach’s standing read, then the chat lane with the dock at the bottom and responses streaming above it.
Four decisions, and all four have to survive the build or the feel goes:
min-height:1.7em so
the first token cannot shove the dock. Verified: the dock sits at the same pixel before, during
and after a stream.scroll-behavior:smooth: a smooth animation running against ~30 appends a second
lags behind the text.requestAnimationFrame, 2–4 words
at a time so it reads as speech rather than a typewriter.Added on Khoa’s follow-up. The mechanism was already shipped —
.app3 transitions its grid columns and .app3.drawer-closed collapses
the third to zero — so it is reused, not reinvented. Three things are new:
The app streams with useChat from @ai-sdk/react and renders
through AgentMarkdown — i.e. prose. So a draft card must not be parsed
back out of that prose. It has to arrive as a structured stream part (an AI SDK
data part or tool result) carrying field, value, provenance and specialist, which the panel
renders as a card.
Regexing markdown for something that looks like a proposal is how “the contract lives inline, never loose text” quietly stops being true — and it would fail silently, which is the worst kind. This is the difference between the constraint being enforced and merely intended.
Both files ship in /assets alongside a manifest and the CSS to consume them.
They are two independent changes and should land as two commits, so the defect fix is
visible on its own rather than buried inside a feature.
Sofia Bennett and Lena Ortiz both render avatar: 5 today, so two agents wear
the same face in the Review Lane. Lena moves to a new index 9.
The live sheet is not touched. Not re-laid, not reordered, not re-encoded — the nine shipped portraits keep their exact bytes and their exact indices. A separate 1.6 KB strip carries the new cells.
I tried the obvious thing first and it was wrong: rebuilding one larger sheet re-encodes every existing portrait. My own check caught it — a lossy round-trip changed every pixel of all nine faces, so "append-only" was false. Saving losslessly fixed the pixels but took the file from 117 KB to 818 KB. Appending a small strip costs nothing and risks nothing.
A separate 2 KB 2×2 sheet with its own c0–c3
namespace. Coaches are a different axis from specialists, so they get their own asset and
their own numbering — continuing the agent sequence is how a coach ends up rendering a
specialist's face after a later insert.
The existing nine are photorealistic head-and-shoulders portraits. I have no image generation in this environment, so I cannot produce four matching faces — and I will not ship a fabricated likeness dressed up as finished work.
What I have produced instead is silhouettes at the measured framing: 418 px cells, head top at 7.3% of cell height, shoulders spanning 29.5–70.8% at 62% height, all measured off the live sheet rather than guessed. The crop is therefore already correct and a real still is a pure asset swap with zero layout change.
My recommendation on how the real ones should be made: take them as stills from each coach's Tavus replica, not from a separate generation pass. Generate the portraits first and the still and the video are two different people the moment Tavus is wired — which is exactly the mismatch this brief asked us to avoid. That makes replica casting the real blocker, and it is a cheaper decision to make now than to unpick later.
The existing shape on origin/develop
(apps/web/components/MemoWorkspaceApp.tsx) is:
{ name, role, avatar: number, expertise, useWhen, bio }
These four fit it directly. role takes the anchor (“Steady guide”),
bio takes the background, useWhen takes the choosing sentence.
Two notes worth having before you start:
c0–c3 sheet. Both files and the
CSS are in /assets with coach-manifest.json.
Sana · design · 31 Jul 2026 · clickable pack, not a build spec ·
schema and roster read from origin/develop, faces are placeholders