Skip to content

[Feature]: First-class user identity for shared gateways (presence, viewers, attribution, profiles) #111133

Description

@steipete

Summary

Make people first-class in a shared (multiplayer) gateway: thread the trusted-proxy-authenticated user identity through connections, presence, and prompt attribution; add durable user profiles (stable id, email aliases, display name, avatar); show who is online and who is viewing which session. Identity is strictly optional — token/password/no-auth gateways behave exactly as today.

Problem to solve

A team can already share one gateway behind Cloudflare Access + trusted-proxy auth (gateway.auth.trustedProxy): the proxy verifies a GitHub-backed email and the gateway checks it against allowUsers. But the verified identity is dropped immediately after the allow/deny decision (src/gateway/auth.ts returns user from the trusted-proxy/tailscale paths; nothing consumes it). Consequences for shared instances:

  • Presence is device-flavored only — no notion of which person is online.
  • No way to see who is viewing a session (Google-Docs-style collaboration is blind).
  • Prompts from different teammates are indistinguishable in transcripts, even though the persisted user-turn schema already supports opt-in sender attribution (src/sessions/user-turn-transcript.types.ts).
  • No audit line on the gateway tying connections to the authenticated email.
  • Emails are login claims, not durable keys — people change emails, so any identity layer keyed on raw email would corrupt history.

Proposed solution

A PR train, each independently landable, identity optional throughout:

  1. Identity threading + presence — carry the auth result's user into WS connection state; add optional user: { id, email?, name?, avatarUrl? } to PresenceEntry; log identity on connect/disconnect. id is opaque: email today, profile id once profiles land. Clients group presence entries by user.id (gateway keeps one truthful entry per connection).
  2. Durable profiles — additive tables in the shared state SQLite DB: user_profiles (stable id, display name, avatar blob, merged_into tombstone) + user_profile_emails (lowercased alias → profile). Resolver: ensureProfileForEmail lazily creates on first login. Email change: new email auto-creates a profile; admin users.linkEmail re-points the alias to the person's real profile and tombstones the orphan (merged_into, one-hop resolution) so prior attribution still resolves. RPCs (users.list/linkEmail/setDisplayName/setAvatar) + minimal CLI.
  3. Who's-viewing presence — piggyback session-scoped event subscriptions: optional sorted watchedSessions on presence entries; Control UI sidebar renders viewer avatars per session row.
  4. Prompt attribution — stamp the existing UserTurnInput.sender field from connection identity for identified clients. Prompts only; tool calls and agent actions remain the agent's.
  5. Settings identity UI — avatar upload + display name in Control UI settings; optional identity chip bottom-left.
  6. Per-identity scopes (deferred) — to be specced as an extension of the trustedProxy config surface after deviceAutoApprove (in flight separately) lands, so there is one decision surface for proxy-user scopes, not two.

Alternatives considered

  • Keying identity on email everywhere: rejected — email changes would orphan history; stable opaque profile id with email aliases survives them.
  • Gateway-side presence merging (one entry per person): rejected — gateway reports per-connection facts; grouping is a client rendering concern and stays backward compatible.
  • A dedicated presence.focus client message for viewing state: deferred — subscription piggyback gives ~90% with no new protocol messages.

Impact

Shared team gateways (the documented company-shared-agent pattern). No behavior change for single-user or token/password gateways.

Evidence/examples

  • src/gateway/auth.ts (~line 525): trusted-proxy auth returns { ok, method, user }; user unused past the gate.
  • packages/gateway-protocol/src/schema/snapshot.ts:14: PresenceEntrySchema (closed object) — device fields only.
  • src/sessions/user-turn-transcript.types.ts: existing durable sender/senderIsOwner attribution seam, currently channel-only.

Do you plan to open a PR for this?

Yes, I plan to implement this myself

Additional information

Maintainer-driven program (requested by @steipete for the shared team instance). PRs land as they go green; this issue tracks design decisions and cross-PR coordination.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.enhancementNew feature or requestimpact:auth-providerAuth, provider routing, model choice, or SecretRef resolution may break.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🌊 off-meta tidepoolIssue quality rating does not apply to this item.maintainerMaintainer-authored PR

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions