# Human, Agent, And Service Identity Spec

> Status: canonical-spec
> Canonical path: `specs/identity/identity.md`
> Scope: human + agent + service(App) identity display plus the Service Account target identity contract, target semantics, projection contracts, compatibility parsing, and inspection rules
> Non-goals: no database schema changes, no historical data rewrites, no App lifecycle / grant / webhook semantics in this spec version

## Goal

Make identity handling boring and explicit:

- default user-facing surfaces always show the right display label
- routing and mention logic always use the right stable target
- detail and debug surfaces can still show ids when that is useful
- compatibility for historical aliases is accepted only at the input boundary and does not leak back into default UI

This spec exists to stop the repeated class of bugs where `displayName`, `users.name`, `agents.name`, `member.id`, and principal ids drift into the wrong surface and become impossible to reason about.

## Why This Spec Exists

The current repo already points in the right direction, but the contract is not enforced in one place:

- human-visible web UI is intended to show `displayName`
- human routing still relies on `member.id`
- agent routing still relies on `agents.name`
- service/App routing relies on installation handle, while UI still needs `displayName`
- some projections still overload `name`-like fields with ids or legacy aliases
- some frontend call sites still use fallback chains like `displayName || memberId || name`

As long as these rules are spread across message, DM, mention, task, and diagnostics code paths, the same bug will keep reappearing on new surfaces.

This spec makes the contract explicit and mandatory.

## Design Principles

### 1. Input compatibility, output canonicalization

Historical aliases may still be accepted as input, but default output must always use canonical display fields and canonical targets.

### 2. Default surfaces and inspection surfaces are different products

Default surfaces optimize for readability. Inspection surfaces optimize for clarity and debugging. They should not share the same display contract.

### 3. Display and target are different concerns

If one string can be both a human-visible label and a routing target, that is an implementation accident, not a design goal.

### 4. Compatibility belongs at one boundary

Legacy names, sync aliases, and historical mention forms must be normalized in resolver or projector code. UI components must not each invent their own compatibility logic.

## Canonical Identity Model

### Human

- `displayName`: the canonical default label shown in human-visible UI
- `targetId`: the stable human routing target; this is `member.id`
- `principalId`: the stable human principal id; this is `user.id`
- `memberId`: the workspace-scoped membership id; for humans this is the same value as `targetId`
- `legacyAliases`: compatibility-only aliases such as `users.name`

### Agent

- `displayName`: the canonical default label shown in human-visible UI; in the current model this comes from the workspace member display projection, typically `members.display_name`
- within one workspace, agent `displayName` values must be unique across agents after trimming leading/trailing whitespace and comparing case-insensitively; internal whitespace is preserved and this rule does not apply to humans or service/App members
- `targetId`: the stable agent routing target; in the current model this is `agents.name`
- `principalId`: the stable agent principal id; this is `agents.id`
- `memberId`: the hidden workspace-scoped membership id
- `legacyAliases`: compatibility-only aliases such as historical agent `member.id` references

### Service / App

- `displayName`: the canonical default label shown in human-visible UI; for App v1 this is the installation-owned service member display projection
- `targetId`: the stable service routing target; for App v1 this is the service member handle and defaults to the App slug
- `principalId`: the stable service principal id; for App v1 this is the service definition id, which mirrors the App id while the App registry remains the authoring facade
- `memberId`: the workspace-scoped service member id owned by the service installation
- `legacyAliases`: compatibility-only aliases or historical protocol handles, if any later exist

### Service Account target identity

- `displayName`: the canonical service-account name shown in human-visible UI
- `targetId`: the typed identity target `service-account:{handle}`
- `principalId`: the stable service-account principal id; for a workspace-owned account it equals `service_accounts.id`, and for a platform-owned account it equals `platform_service_accounts.id`; it equals every named workspace installation's `instance_key`
- `memberId`: the workspace-scoped author member owned by the service member
- `serviceKind`: `service_account`
- profile authority: `service_accounts` for a workspace-owned account and `platform_service_accounts` for a platform-owned account; the workspace-local `service_accounts` projection and `service_members` carry the instance, author-member, and handle projection

## Required Identity Fields

Every UI-facing identity projection must be able to answer these questions explicitly:

1. What should a human normally see?
2. What stable target should the system route to?
3. What principal does this actor correspond to?
4. What extra ids may be useful in inspect or debug views?

For implementation guidance, the canonical view shape is:

```ts
type ActorIdentityView = {
  actorType: "human" | "agent" | "service"
  serviceKind?: string
  principalId: string
  targetId: string
  displayName: string
  memberId?: string
  legacyAliases?: string[]
}
```

This exact TypeScript type does not need to exist everywhere, but every UI-facing projection should map cleanly onto this model.

## Surface Taxonomy

### A. Default human-visible surfaces

These are the places where users are primarily reading content, not debugging identity internals:

- channel message rows
- DM headers and lists
- reply thread lists and thread headers
- task cards, task badges, task detail summaries
- member lists and member pickers
- mention chips and rendered mentions
- hover cards and profile cards when first opened
- notifications, activity summaries, and other everyday UI labels

Rules:

- human display uses `displayName`
- agent display uses `displayName`
- service/App display uses `displayName`
- ids must not be used as the main label

### B. Detail surfaces

These are still end-user surfaces, but the user has explicitly asked for more information:

- expanded profile cards
- human details panels
- agent details panels
- task inspect drawers
- DM peer details

Rules:

- the primary label is still the canonical display label
- ids are allowed as secondary metadata
- ids must be labeled explicitly, for example `Mention target`, `User ID`, `Agent ID`, `Member ID`
- the presence of ids does not change the main label contract

### C. Inspection and debugging surfaces

These are operational or troubleshooting-oriented surfaces:

- diagnostics panels
- raw payload viewers
- inspector cards
- audit and support views

Rules:

- both display labels and ids may be shown
- display label remains the title
- ids may be shown freely as long as they are clearly labeled
- raw ids must never masquerade as `name`

### D. Protocol and routing surfaces

These are not display surfaces:

- `/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/*`
- daemon protocol payloads
- mention serialization
- DM targets
- event payloads
- store adapters and routing contracts

Rules:

- canonical targets may appear here
- these fields are not default UI labels
- any UI rendering based on these payloads must pass through display projection logic first

## Mandatory Invariants

- Default UI labels and routing targets are separate concepts.
- Human default UI labels use `displayName`.
- Human routing targets use `member.id`.
- Agent default UI labels use `displayName`.
- Agent `displayName` uniqueness is defined on trimmed, case-insensitive values within a workspace's agent set; this is separate from handle uniqueness.
- Agent routing targets use `agents.name` in the current model.
- Service/App default UI labels use `displayName`.
- Service/App routing targets use installation-scoped public handles.
- Human `users.name` is compatibility-only and must not be used as the default visible label.
- Agent `member.id` is compatibility-only or inspection-only and must not be used as the default visible label.
- Service/App `member.id` is compatibility-only or inspection-only and must not be used as the default visible label.
- If a payload field is rendered directly as a default UI label, that field must contain a display label, not an id.
- If compatibility requires a legacy `...Name` field to carry a protocol target, that payload must also expose an explicit display field and the docs must state clearly that the legacy field is not a UI label.
- App payloads that need App-specific rendering should expose additive metadata such as `authorProfile.serviceKind` / `authorProfile.serviceSlug`, rather than requiring UI inference from `targetId`.
- No caller may infer identity semantics from string shape.

## Projection Contract

### UI-facing payloads

UI-facing payloads must model display and target separately.

Preferred field semantics:

- `displayName`: the default visible label
- `targetId`: the stable routing target
- `principalId`: the stable principal id
- `memberId`: the workspace member id, when relevant
- `legacyAliases`: compatibility-only aliases

Existing wire fields may remain at explicitly named compatibility adapter boundaries, but their semantics must be fixed:

- legacy daemon message payloads may retain derived author target fields only when paired with canonical `authorProfile`
- if a UI directly renders `createdByName`, it must be a display label
- new payloads must not continue the pattern of using a bare `...Name` field for mixed display and routing semantics

### Protocol-facing payloads

Protocol-facing payloads may continue to carry canonical targets such as:

- human target = `member.id`
- agent target = `agents.name`
- service/App target = installation-scoped public handle

But the docs must state clearly that these are protocol targets, not user-facing display labels.

## Rendering Rules

### Human

- message author label shows `displayName`
- DM peer label shows `displayName`
- task actor label shows `displayName`
- mention visible text shows `@displayName`
- member list and picker labels show `displayName`

### Agent

- message author label shows `displayName`
- DM peer label shows `displayName`
- task actor label shows `displayName`
- mention visible text shows `@displayName`

### Service / App

- message author label shows `displayName`
- mention visible text shows `@displayName`
- detail panels may show `targetId`, `principalId`, and `memberId` as labeled metadata

### Service Account target UI

- service-account message author label shows `displayName`
- author navigation opens the service-account conversation
- detail panels show the service-account handle as labeled metadata
- inspect panels may show `targetId`, `principalId`, and `memberId`

### Detail and inspect

- title shows canonical display label
- secondary metadata may include `targetId`, `principalId`, `memberId`, and compatibility aliases
- every id must be labeled
- copy buttons are allowed and encouraged for operational clarity

## Parsing And Compatibility Rules

### Human parsing

- canonical human target is `member.id`
- historical human aliases such as `users.name` may still resolve
- once resolved, the UI must display the canonical `displayName`

### Agent parsing

- canonical agent target is `agents.name`
- historical agent `member.id` aliases may still resolve
- once resolved, the UI must display the canonical agent `displayName`

### Service / App parsing

- canonical service/App target is the installation-scoped public handle
- once resolved, the UI must display the canonical service/App `displayName`
- App installation / visibility / subscription semantics are defined in `specs/app/app-system.md`, not repeated here

### Service Account target parsing

- canonical service-account identity target is `service-account:{handle}`
- resolution uses the current workspace and returns the service-account principal, workspace-local account instance, service installation, service member, and author member
- service-account `accountId` route parameters carry the principal id; `(workspaceId, principalId)` resolves the unique local instance, while conversation ids continue to use the local message scope id
- resolved UI uses the canonical service-account `displayName`
- target capabilities expose profile and conversation navigation

### Compatibility boundary rule

Compatibility is allowed only in resolvers, parsers, and input projectors.

Compatibility is not allowed in:

- everyday UI render code
- component-local fallback chains
- new payload naming

## Fallback Rules

### Default human-visible fallback

Fallback order:

1. canonical display projection
2. server-side display source
3. compatibility alias only if no canonical display exists

`member.id` and `user.id` are not valid default-label fallbacks.

### Default agent-visible fallback

Fallback order:

1. canonical display projection
2. server-side display source
3. canonical public handle `agents.name` only if no display label exists

`member.id` and `agent.id` are not valid default-label fallbacks.

### Default service-visible fallback

Fallback order:

1. canonical display projection
2. server-side display source
3. canonical public handle only if no display label exists

`member.id` and principal ids are not valid default-label fallbacks.

### Detail and inspect fallback

If canonical display data is unavailable:

- show `Unknown user` or `Unknown agent` as the title
- show raw ids only as labeled metadata

## Task Surface Rules

Task surfaces are identity-sensitive and must follow the same contract as chat and DM surfaces.

- `createdByProfile` is the canonical Task creator identity
- nullable `assigneeProfile` is the only canonical Task responsibility identity and may represent a Human or Agent member
- Task mutation uses `assigneeRef` as a routing target; it is not a display label
- `reviewerRef`, `requestedByRef`, and `decidedByRef` are actor identity keys for task/review semantics; they are not display fields
- `createdByName`, if retained, must contain a display label
- `reviewerName`, `requestedByName`, and `decidedByName`, if retained, must contain display labels
- `currentAgent`、`claimedBy*`、`suggestedAssignee` 与 Task-row `reviewer` are removed responsibility aliases and must not be rendered or used as fallback

If task UI later needs a mention or DM target, it must add a separate explicit target field rather than overloading these actor ids or name fields.

Task detail or inspect views may show ids, but only as labeled metadata.

## Message And DM Rules

Internal protocol fields such as `channel_name` and `parent_channel_name` may continue to carry canonical targets for compatibility. Legacy daemon author-target fields are derived only at the daemon adapter boundary.

However:

- these fields are protocol or routing fields
- they are not the default UI label contract
- UI-facing message projections must provide a display projection explicitly

## Naming Rules

New code must prefer explicit names:

- `displayName`
- `targetId`
- `principalId`
- `memberId`
- `legacyAliases`

New identity-sensitive payloads must not introduce bare `name` as the only identity field.

If an old payload still has a `name` field, the doc for that payload must state clearly whether it is:

- a protocol target
- a display label
- a compatibility alias

Legacy compatibility fields may remain when they are already part of a daemon contract, but they must be paired with canonical `authorProfile` and must not be reused as default labels in new code.

Ambiguous `name` semantics are no longer acceptable.

## Testing Requirements

Identity-sensitive changes must use fixtures where these values are intentionally different:

- human `user.id`
- human `member.id`
- human `users.name`
- human `displayName`
- agent `agent.id`
- agent `member.id`
- agent `agents.name`
- service `apps.id`
- service `member.id`
- service/App public handle
- service/App `displayName`

Tests must prove:

- human default UI shows `displayName`
- agent default UI shows `displayName`
- agent routing and mention targets still use `agents.name`
- service/App default UI shows `displayName`
- service/App routing and mention targets still use the public handle
- task panels do not leak ids into default labels
- historical aliases still resolve
- inspect or detail surfaces may show ids as labeled metadata

Without intentionally different fixtures, this class of bug is not meaningfully tested.

## Documentation Alignment

The following docs must stay aligned with this spec:

- `SPECS.md`
- `specs/app/app-system.md`
- `docs/tech_design.md`
- `AGENTS.md`

If a doc example shows ids inside name-like UI fields, that example must be corrected or explicitly labeled as protocol-only.

## Enforcement

Any change touching these areas must read this spec first:

- message projections
- DM projections
- mention parsing or rendering
- task projections
- member lists and people pickers
- diagnostics and inspection UI
- app or internal payload naming for actors

This includes files under:

- `web/src/agentteam/workspace/*`
- `web/src/types.ts`
- `internal/store/*`
- `internal/appapi/*`
- `internal/api/agent/*`
- `internal/store/message/*`
- `internal/store/taskboard/*`
- `desktop/*`

## Acceptance Criteria

This design is considered successfully implemented only when:

- default user-visible surfaces no longer show human ids as names
- default user-visible surfaces no longer show agent member ids as names
- default user-visible surfaces no longer show service/App ids or handles as names unless falling back from missing display data
- ids remain available in details and inspect flows where useful
- compatibility for historical aliases continues to work
- UI payloads and docs no longer overload `name` with mixed semantics
- future contributors can determine the correct behavior by reading one mandatory spec instead of reverse-engineering scattered conventions
