* feat(ui): save appearance preferences per user profile When a Control UI connection is bound to an authenticated user profile (trusted-proxy, Tailscale, GitHub identity), theme, theme mode, and accent become per-profile: stored in the existing user_preferences KV store via users.prefs.set, overriding gateway-wide ui.prefs, with live cross-device updates over a new additive users.prefs.changed event scoped to the same merged profile. Restore default deletes only the profile key and falls back to the gateway-wide value. talk.config projects the caller's profile accent for native clients. Profile-less connections (token/password/none auth) keep the existing gateway-config behavior byte-identically. Release-note context: on multi-person gateways appearance choices are now personal and follow you across devices; an admin restyling their own UI no longer reskins the whole team. * chore(protocol): allowlist users.prefs.changed for mobile clients iOS and Android resolve the profile accent through talk.config on connect and config refresh; live per-profile appearance push on natives is a named follow-up. * chore(protocol): regenerate Swift/Kotlin models for users.prefs.changed * fix(ui): honor explicit defaults and identity switches for profile appearance Addresses the three ClawSweeper review findings: - resetValue for profile-bound appearance keys is the deletion fallback (gateway value), so an explicit product-default selection persists as a profile write instead of being misclassified as a reset. - An identity switch between two known scopes forces a full appearance reconcile and clears appearance keys the returning identity never set, so a shared browser cannot keep the previous profile's look (boot keeps the last-seen shortcut). - talk.config joins the profile-dependent dispatch gate so a pending GitHub identity sync completes before the profile accent is projected; token clients pass through untouched. * chore(ui): satisfy assertion-safety ratchet and docs formatting * refactor(ui): extract server-prefs storage primitives server-prefs.ts crossed the 700-line cap after the review fixes; the stateless localStorage persistence primitives move to server-prefs-storage.ts (no behavior change). Shrink-prunes the assertion-safety baseline for the moved casts. * fix(ui): keep imported custom themes out of profile storage Custom palettes are browser-local by contract, so a profile must never carry theme=custom to a browser that cannot render it. The wire contract drops custom from the storable theme values (readers self-heal any stored value), a profile-bound custom selection stays retained browser-local (including the offline-queued path), and the exhaustive theme test now encodes the exception.
9.0 KiB
summary, title, read_when
| summary | title | read_when | ||||
|---|---|---|---|---|---|---|
| Manage durable user preferences and your Gateway profile identity | User model |
|
USER.md is the optional user-model artifact in an agent workspace. It stores stable preferences, communication style, relationships, and active-project context as directives that can guide future sessions.
OpenClaw loads USER.md beside MEMORY.md at session start. It has a separate small bootstrap budget, and edits are picked up on later turns in a long-lived session. If the file is absent, startup continues without it.
Gateway profile and GitHub credit
Your authenticated Gateway profile is separate from USER.md. Open Settings → Profile → Identity to set the display name and avatar shown to other people on the Gateway. A custom OpenClaw avatar remains authoritative when a GitHub account is verified.
GitHub-backed sign-in is supported through Cloudflare Access and Tailscale Serve. For Cloudflare Access, the Gateway accepts identity enrichment only after successful trusted-proxy authentication with the standard Access email header and a required Access assertion header. It calls the Access identity endpoint, requires the returned email to match the authenticated proxy principal and the identity provider to be GitHub, then resolves the canonical GitHub login from the returned numeric account id. For Tailscale Serve, the Gateway resolves the verified GitHub-backed Tailscale login through GitHub. Both paths record the immutable numeric account id plus the current canonical login.
The GitHub account row is read-only. Generic trusted proxies, token, password, and unauthenticated connections cannot claim a GitHub account, and agent or tool GitHub credentials are never used for this identity. The forwarded Cloudflare Access assertion is connection-scoped: OpenClaw does not persist, export, log, or expose it to the UI or model.
Identity lookup runs after WebSocket sign-in, so connection status and other identity-independent reads remain available. Profile and session work waits for the lookup; a Cloudflare or GitHub rate limit or network failure returns retryable unavailability without exposing a mutable alias or erasing a previously verified account. A later request, connection, or Profile refresh retries the lookup. GitHub login renames are reconciled by numeric account id so profile history and preferences stay attached to one person.
Public commit metadata is a separate choice. Git co-author credit defaults off. Enabling it adds the verified account's public GitHub noreply address to commits created from shared sessions; OpenClaw never requests or stores a private GitHub email for this feature. Signing in as a different numeric GitHub account resets the choice, so one account cannot inherit another account's consent.
When your authenticated profile has prompted a session before an agent run, commits created from that run receive your exact Co-authored-by trailer and commits and pull requests visibly credit who worked on the session. Profile-backed human participants with verified GitHub identity and Git co-author credit enabled are eligible; channel-only identities, agents, bots, and the configured primary Git author are excluded. Contributors appear by their number of authenticated profile prompts, highest first; ties use the earliest profile prompt, then immutable GitHub account id. Contributions from merged profiles remain attached to their surviving verified account. The participant set is bounded to 32 and recorded best-effort. The run tells the model when a profile has no enabled credit or the bound may be incomplete; it never guesses an identity from transcript names.
OpenClaw supplies exact trailers and the ordered contributor list in the model context for that turn and instructs coding agents to retain them through amendments, rebases, and squash commits so credit reaches the final commit merged to the default branch. The Gateway publication broker enforces the same credit directly in its generated commits and pull requests. When the Gateway exposes an external HTTPS session URL, pull requests end with a link to that exact team session. The trailers are not exported through the process or shell environment. Direct Git commands remain ordinary shell execution: OpenClaw does not replace git or install repository hooks, so agent instructions and post-commit verification remain their enforcement boundary.
Turning Git co-author credit off stops attribution for future runs. It does not rewrite commits that already contain the public trailer.
Profile appearance preferences
When a Control UI connection is bound to an authenticated Gateway profile, its theme, theme mode, and accent color are stored per profile in the existing user_preferences table in the shared state database. Those choices follow that person across devices without changing appearance for other people on the same Gateway.
Profile theme and theme mode preferences override their gateway-wide ui.prefs settings and otherwise fall back to the active theme's defaults. The imported custom theme is the exception: its palette lives only in the browser that imported it, so selecting it stays browser-local and never follows the profile. Accent precedence is the profile's ui.accent preference, gateway-wide ui.prefs.accent, ui.seamColor, and finally the active theme's default accent. Restoring a default clears only the profile preference. Connections without an authenticated profile, including token, password, or unauthenticated connections, keep the existing gateway-wide appearance behavior unchanged. Language, chat preferences, and sidebar entries continue using gateway configuration.
Write directives, not observations
Each entry has a metadata line followed by one imperative directive:
<!-- observed: 2026-07-27 | status: active -->
- Prefer concise progress updates during implementation work.
Use these rules:
- Begin with an imperative such as
Always,Never, orPrefer. - Record the date the preference was observed.
- Use only
activeorsupersededfor status. - Keep one behavioral instruction per directive.
- Store only details that improve assistance. Do not turn the file into a dossier.
PrefEval found that preference following degrades sharply in longer conversations, even with retrieval and prompting (arXiv:2502.09597). Restating a stable preference as a directive makes the expected behavior explicit at the point where the agent uses it.
Supersede in place
When a preference changes, update its existing section. Do not append a second active directive elsewhere in the file.
Before:
<!-- observed: 2026-05-10 | status: active -->
- Prefer detailed explanations for every code change.
After:
<!-- observed: 2026-05-10 | status: superseded -->
- Prefer detailed explanations for every code change.
<!-- observed: 2026-07-27 | status: active -->
- Prefer concise implementation summaries unless more detail is requested.
Keep the superseded entry next to its replacement so the current directive is unambiguous. HorizonBench reports that systems often select an originally stated preference after the user has changed it (arXiv:2604.17283); append-only contradictory history recreates that failure mode.
Choose the right file
| Information | Store it in |
|---|---|
| Stable preference or communication style | USER.md |
| Relationship or active-project fact that changes how the user should be assisted | USER.md |
| Durable non-profile fact, decision, or lesson | MEMORY.md |
| Detailed observation or running context | memory/YYYY-MM-DD.md |
| Event-conditioned future action | Standing intents |
| Exact-time or recurring action | Scheduled task |
Keep it compact
USER.md has a deliberately smaller bootstrap budget than general workspace files. When it becomes crowded, remove stale superseded entries and move project detail that does not alter behavior into daily memory or MEMORY.md.