## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Agents have a persistent visual identity (#13171): a ClipLab character in one of 17 palettes, rendered as cached PNGs in lists and as a live character in larger placements. > - The onboarding wizard is where a person meets that identity first, and it showed a stock ClipLab expression on the previous engine while the rest of the app would show a different character on a newer one. > - The wizard's steps also cut from one screen to the next, so the arc read as separate pages rather than one walk. > - This pull request puts one character on one engine everywhere, gives the wizard's hero the studio's sleepy → wink → idle sequence on Review, and hands the steps over inside one presence. > - The benefit is that what wakes on Review is exactly what the agent looks like on the dashboard afterwards, and the walk to it reads as one screen changing. ## Linked Issues or Issue Description Refs #13171, now merged into master. This PR contains the onboarding and ClipLab update on top of that foundation. Original feature work by @tonio-alucema; merge preparation preserves the original commits. **Problem or motivation** The onboarding hero and the app's avatars were two different characters on two different ClipLab engines. Steps 1 → 4 of the wizard cut between screens, and the wizard mounted cold when a cloud-managed workspace arrived from Cloud's naming screen. **Proposed solution** Vendor ClipLab v0.2.0 as the shared engine and render one studio-exported character from it in every palette, for every pose and size. Play the export's one-shot wake on Review with the palette fading in over the gray dormant loop. Hand steps over inside one presence so the footer slides instead of jumping, and play the arrival half of that hand-off when the wizard opens directly on the agent step. **Alternatives considered** Exporting mp4/webm loops per size: no cursor following, no clean alpha, and the palette "colour in" is a runtime blend. Minting a `cap-v2` character version: nothing had shipped `cap-v1`, so the artwork is regenerated in place instead of migrated. Keeping the separately vendored runtime bundle for the hero: two engines and two characters in one app. ## What Changed - `packages/shared/src/cliplab`: re-vendored from ClipLab v0.2.0 (`987b6db0`) with the Paperclip adaptations replayed (optional graphics backend for the Node SVG snapshot path, supersampled live textures, character framing, deterministic SVG id prefixes); new upstream `particles.ts`. - `packages/shared/src/cliplab/character.ts`: the studio export, mirrored from `ui/src/assets/cliplab/onboarding.character.json` by `scripts/sync-cliplab-character.mjs` (drift caught by `check:token-gates`). `characterDefinition` builds every palette from it; the resting portrait is its idle beat. - `OnboardingCharacter`: gray `sleepy` loop through the agent and connect steps; on Review the one-shot sleepy → wink → idle plays on two lock-step canvases while the palette fades in, then the `idle` loop. Body-follows the pointer, page-scoped. 160px in the wizard. - `OnboardingWizard`: steps 1 → 2 → 3 → 4 hand over inside one `AnimatePresence` (departing content fades and gives its room back; arriving content opens its room then fills); the hero has a room that opens on the walk into the agent step; opening directly on the agent step plays the arrival half; the self-hosted naming step uses the arc's label and field. - Motion vocabulary in `onboarding-motion.ts` (`stepContentMotion`, `ledeMotion`, `heroRoomMotion`, `heroRoomArrival`, `titleSwapMotion`). - Storybook: `Onboarding / Character` (Wake Up), `Onboarding / Agent arc` walkable from the naming step plus `Arrive From Cloud`; the companies fixture answers the wizard's create call with a company. - Uses the shared runtime for onboarding; `doc/agent-personas.md` documents the shared character. - Releases both onboarding canvases after partial startup or transition failure. Registers each canvas before seeking so synchronous render errors can release it. Six component tests cover these failures and palette changes before or during wake. - Refreshes both sleeping canvases when the palette changes, including a palette change in the same render as wake. - Moves choreography values into the CSS token layer and preserves the shared motion catalog drift check across the imported stylesheet. - Repairs the static Storybook avatar route and uses accessible heading names/current button labels in the wizard play functions. - Closes the lazy avatar worker pool during application shutdown. ## Verification - Merge-preparation checks: `pnpm -r typecheck`, `pnpm build`, `pnpm build-storybook`, and `pnpm check:token-gates` pass. The final UI typecheck and 123 focused onboarding, lifecycle, and token catalog tests pass. All 55 checks on final head `b4f5e201a1564083d163abc6f93f5b3da06ccefd` pass, including the full sharded test suite, runner verification, and all eight browser shards ([CI run](https://github.com/paperclipai/paperclip/actions/runs/35445430535)). The duplicate monolithic local `pnpm test:run` was stopped after CI completed; it is not claimed as a separate completed local run. - Chromium walkthrough: palette change, wake, return to sleep, WebGL failure fallback, Review step hand-offs and cloud arrival pass with normal and reduced motion; no browser errors. The signoff happy-path browser test also passes against a disposable instance. - The final CI run confirms the catalog fix and a passing signoff browser shard. The earlier signoff failure was a heartbeat-run availability timeout; the focused local reproduction and final CI passed without signoff code changes. - Original author verification: - `pnpm check:token-gates` (includes the new character sync check); shared, server avatar/persona (17) and UI onboarding/persona (137) suites pass; `pnpm build-storybook` packages all 3,564 avatar PNGs through the worker pipeline. - Storybook: `Agents / Personas` Sizes, Expressions and Palettes render the studio character at every size and pose; `Onboarding / Character → Wake Up` plays the wake on the shared engine; `Onboarding / Agent arc` walks 1 → 4 with the hand-offs, and `Arrive From Cloud` plays the arrival (measured: content room 6 → 65px over 320ms, fade to 1.0 by ~560ms, footer travel continuous). - The original author walked the agent → connect → review flow and wake after a real sign-in on staging. - Not done here: the Linux Storybook visual baselines (`tests/storybook-visual/agent-personas.spec.ts`) need re-baselining for the new engine, hero size and naming-step changes. ## Risks - Every avatar's pixels change (new engine, new character) under the unchanged `cap-v1` name. Stacks that rendered avatars on the previous engine keep those PNGs in their cache (`generated-agent-avatars/cap-v1/...`, served immutable) until cleared; only the two pinned staging stacks ever did. - The one-shot handoff to the idle loop is timed from the sequence's authored duration (the engine reports completion by continuing into idle itself); presentation only, nothing in the wizard's state waits on it. - Reduced motion skips the wake and the hand-offs; jsdom is treated the same way, so the wizard tests see the next step's content immediately. - The committed export differs from the studio by one animation (Loop off, leading idle step removed); a re-export without that fix would play a 5.6s idle before the wake. ## Model Used Original feature: Anthropic Claude Fable 5.1 (`claude-fable-5-1`) in Claude Code, with shell, browser, and file tools. The original context window was not recorded. Merge preparation and lifecycle regression fixes: OpenAI GPT-6 in Codex, with reasoning, shell execution, file editing, GitHub CLI, and automated tests. The session does not expose an exact runtime model ID or context-window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Dotta <bippadotta@protonmail.com> Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
11 KiB
Agent personas
Agents have a persisted visual identity independent of prompts and runtime
configuration: { schemaVersion: 1, characterVersion: "cap-v1", paletteId }.
The cap-v1 library contains 17 permanent palettes and a presentation-only gray
Muted dream palette. The character itself is one ClipLab studio export —
ui/src/assets/cliplab/onboarding.character.json (end cap, custom idle,
sleepy-to-wake) — mirrored into the shared package as
packages/shared/src/cliplab/character.ts by
node scripts/sync-cliplab-character.mjs (--check detects drift); every
palette recolours its body. The onboarding hero and every avatar are the same
character on the same engine (ClipLab v0.2.0, see PROVENANCE.md). New agents get one random assignment; existing rows are
backfilled with the same ID-based mapping used by legacy clients. Duplicating
an agent generates another assignment. Export/import preserves it.
Rendering and URLs
GET /api/agent-avatars/cap-v1/bubblegum-sky/rest.png?size=24&scale=2
The endpoint is public preset artwork, contains no agent/company identifiers,
and works without the UI. Agent.avatarUrl is the 512px resting portrait URL.
Resolve relative URLs against the instance base URL for integrations.
Supported logical sizes: 16, 20, 24, 32, 40, 48, 64, 96, 128, 256, 512. Density is 1 or 2. Poses: rest, idle, listening, thinking, working, success, confused, sleepy, loading. Other inputs receive 400. Display size controls face detail separately from raster dimensions: 24px at 2× remains eyes-only.
A cold request samples ClipLab's deterministic scene, exports SVG using the same geometry and face code as the live renderer, and rasterizes it with sharp in a worker thread. No Chromium, GPU, pre-rendering command, or per-agent asset row is required. Two workers serve a bounded queue, coalesce identical requests within the process, time out failed rendering, and stop after an idle interval.
The configured local-disk/S3 provider stores results in
generated-agent-avatars/<version>/<palette>/<pose>-<size>-<scale>.png.
This preset-only cache intentionally uses the storage provider directly;
company asset APIs retain their existing authorization boundary. Cache data
can be removed and regenerates on demand. Independent replicas can render the
same key safely; successful writes are complete objects. ETags hash PNG bytes.
A small .png.json sidecar stores the content digest and length; it is published
after the complete PNG. Warm requests stream stored PNG bytes without re-rendering
or buffering the image in the API process. Responses are immutable for one year.
Cold renders are limited per client IP (32 outstanding keys and 256 new keys
per minute per process); excess misses return 429 with Retry-After and no-store.
Warm cache hits and requests joining the same in-flight key bypass admission.
The route uses Express trust-proxy configuration, never an untrusted forwarded header. Render/storage failures return 503 with
Retry-After and no-store rather than caching a broken image.
cap-v1 is frozen: change the version when changing palette values, poses, rendering, or dependencies in a way that changes pixels. Keep old versions available. The SVG exporter approximates 3D gradients with a planar gradient; front-facing identity portraits minimize the difference from WebGL.
Components
AgentAvatar: image only; pass the agent record or appearance and a semantic size. No per-agent queries, live-renderer imports, or circle cropping.AgentIdentity: agent avatar and name. Human identities keepIdentity.AgentCharacter: lazy live hero with state and optional tracking-region props, plus an explicittrackingScope="page"for onboarding and agent headers. One live renderer per view; other instances keep their still image. Reduced motion, offscreen content, renderer failure, and static states do not run the animation loop. Pointer tracking defaults to its region and is off for touch.
Onboarding stores the eventual palette in its existing draft and presents gray until verified connection/hiring succeeds. Reconnect never randomizes identity. Names, explicit status badges, and status text remain authoritative.
Development and verification
ClipLab provenance/licenses are under packages/shared/src/cliplab/.
Palette tokens originate in ui/src/index.css. After deliberately introducing
new versioned artwork, node scripts/sync-agent-palette-tokens.mjs synchronizes
the TS palette data; --check detects drift. This does not generate images.
Storybook: Agents / Personas. Run with
PAPERCLIP_STORYBOOK_API_URL=http://localhost:<isolated-port> pnpm storybook.
pnpm build-storybook automatically packages all finite avatar presets (17
palettes plus muted gray, nine poses, eleven logical sizes, both densities).
The build uses the same bounded Node worker pool, SVG renderer, and Sharp pipeline
as the API. Storybook-only URL resolution points to relative PNG paths under the
published build, including branch-prefixed deployments. Production Paperclip
continues to use its on-demand API; no image generation runs during agent creation.
The generated files are build output, never committed. A manifest records image
hashes and pixel dimensions; deployment verification fetches every image and checks
its content type, PNG signature, dimensions, and hash. Dev Storybook still uses the
API proxy for cold-cache and regeneration testing.
The onboarding motion values live in ui/src/motion-tokens.css, imported by
ui/src/index.css. The JavaScript choreography reads these CSS tokens and uses
the same stylesheet for defaults before styles load. Reduced motion removes
transition durations; the connection status hold remains readable.
Focused checks:
pnpm exec vitest run packages/shared/src/agent-appearance.test.ts server/src/__tests__/agent-avatars.test.ts
node scripts/sync-agent-palette-tokens.mjs --check
node scripts/sync-cliplab-character.mjs --check
pnpm check:token-gates
pnpm build-storybook
The 500-avatar story must create zero WebGL contexts and load no live runtime. Use fixed poses/times for screenshots and Linux for authoritative visual baselines. Verify cold and warm URLs, reduced motion, reconnect, and saved appearance after refresh alongside normal typecheck/test/build checks.
Linux visual/performance checks (against the self-contained built Storybook):
pnpm exec playwright test --config tests/storybook-visual/agent-personas.config.ts
Use the Playwright 1.62.1 Noble image for authoritative Linux baselines. The suite covers both themes, palette and size grids, every expression, fixed-pose SVG/WebGL pixel comparisons, repeated mounts, context loss, delayed/failed images, and the 500-avatar no-WebGL/no-live-download/no-frame-loop contract. Baselines follow the existing Storybook visual artifact workflow; they are not application assets.
Acceptance record — 2026-09-10
Implemented in codex/agent-personas, based on 5cb4f061d, with the original
checkout left unchanged. Verification used disposable embedded-Postgres data,
a local MinIO container, and Playwright's Linux Noble image.
| Check | Result |
|---|---|
| Repository typecheck and build | Passed |
| Token gates and palette-token synchronization | Passed |
| Storybook production build | Passed |
| Linux visual/performance suite | 30 passed; exact snapshot comparison passed |
| Static/live agreement | Rest at 16/24/48/256 logical pixels, both densities; all eight animated expression snapshots at 128px/2× |
| Avatar endpoint | Cold/warm requests, concurrency, deletion, retry, ETags and invalid parameters passed |
| Real local-disk and S3-compatible storage | Passed; persisted warm cache reused by a fresh service instance |
| Compiled API-only smoke | Passed with TypeScript stripping disabled, no UI and a native worker |
| Persistence and contracts | Creation, saved draft, SQL backfill, approvals, duplication, portability and revision restoration passed |
| UI/runtime lifecycle | Reduced motion, one live owner, scoped pointers, hidden/offscreen suspension, failures and disposal passed |
| Hands-on app | Stable rename/reload, fresh duplicate assignment, paused static portrait, matching task/list/configuration identities passed |
The broad pnpm test:run verification was completed in its groups/shards after
resource-contention retries. General server (8,347 tests), UI (5,619), CLI (484),
DB (133), and adapter/plugin source suites (2,235) passed. Remaining route suites
and the added persona/contract tests passed after the OpenAPI coverage update.
The queued-comment suite exposed a pre-existing fixture-cleanup failure: run
claims created dependent runtime rows, so swallowed foreign-key errors left the
QUE company behind. Its isolated-database cleanup now truncates the company
fixture graph with cascade; all 17 tests pass together. No production queued-comment
behavior changed. Verification completed across the repository runner's groups
and shards, with targeted reruns after fixes, rather than another monolithic run.
Actual provider sign-in was not completed: the disposable instance had no managed sandbox available for sign-in. Onboarding gray/loading/success transitions were covered in Storybook and the onboarding tests. The app is usable in local-trusted mode; this standalone worktree has no managed issue identity or login handoff.
The reviewed Linux candidate archive is generated at
tests/storybook-visual/baseline-review/snapshots.tgz. It remains a local review
artifact; the existing baseline manifest was not repointed to an unpublished URL.
Placement and sharpness refinement — 2026-09-10
The overview/configuration header owns the live character beside the agent name and follows the pointer across the whole page; there is no second hero in the overview body. The new-agent dialog and setup page use a larger, padded character frame with page-wide mouse tracking. Other placements retain region-scoped tracking. Touch, reduced motion, hidden views, and unmount cleanup still disable tracking and frame scheduling.
Live canvases render at twice the display density (capped at 4×), with a 1024px face texture and padded framing for rotations and expression props. This affects only the live renderer: existing versioned PNG URLs retain their original pixels. The Snapshot Agreement story provides explicit 1×/2× controls and labels the actual PNG and WebGL pixel dimensions for a fair comparison.
Agents / Personas / Full pages includes the actual application shell and route
components for all agents, agent overview, task detail, dashboard, the new-agent
dialog, and the connection page. Fixtures stay in Storybook; these examples do
not read or mutate the running company's data. Dashboard activity rows now use
the same agent avatars as its active-agent and task placements.
Refinement verification: 38 Linux Playwright checks passed, including exact
comparison with the reviewed snapshots for all six full-page stories and
corner-pointer clipping checks at 1×/2× display density. The 500-avatar view
still loads no live renderer or WebGL contexts. All 18 targeted appearance,
component/runtime, and avatar endpoint tests passed, as did shared/UI typechecks,
token gates, UI build, and Storybook build. Hands-on inspection confirmed the
single animated header at /PER/agents/persona-tester-renamed/overview and the
larger onboarding character in the real route components. The repository-wide
checks above were not repeated for this UI refinement.