# User Identity & Context System

How GoClaw tells an agent who it is talking to: the person's display name and role are resolved from the pairing store and the request metadata, and the session context carries them, with the person's personal memory, into the prompt.

`internal/agent/user_context.go` + the user and personal-memory sections of `GetSessionContext` (`internal/agent/agent.go`)

## Architecture

```mermaid
flowchart TD
    RES["<b>resolveUserDisplayInfo(meta)</b><br/>• Web admin? → DisplayName='Admin', Role='admin'<br/>• Has UnifiedUserID? → pairing.Store lookup (username + GoClawRole)<br/>• Fallback → AuthorName, then PlatformID<br/>• Role: request role, else 'guest'"]

    FMT["<b>formatUserContextBlock(f, info)</b><br/>• &lt;user label='CURRENT USER'&gt; (Ollama: ### CURRENT USER)<br/>• Paired: '**Name** (role: X, via channel)'<br/>• Unpaired: 'channel user platformID (unpaired guest)'"]

    MEM["<b>Personal Memory</b> (left out of cron, SOP and webhook runs)<br/>• DB records, scope=personal: up to 5, matches first<br/>• Legacy user_&lt;id&gt;.md, only when there are no records (max 5000 chars)"]

    RES --> FMT --> MEM
```

## Feature Comparison

| # | Feature | OpenClaw | GoClaw | Status |
|---|---------|----------|---------|--------|
| 3.1 | Cross-platform identity resolution | ✅ | ✅ `resolveUserDisplayInfo` — pairing store lookup | Done |
| 3.2 | System prompt user context | ✅ | ✅ `formatUserContextBlock` — `CURRENT USER` section of the session context | Done |
| 3.3 | **Per-user personal memory** | — | ✅ Database records written by `memory_write` (`scope: personal`); a legacy `user_<id>.md` is read only when there are none | **GoClaw only** |
| 3.4 | Role resolution (admin/user/guest) | ✅ | ✅ From `pairing.Store`, else the request's role, else `guest` | Done |
| 3.5 | Unified user ID | ✅ | ✅ `UnifiedUserID` via `GetUserByAccount(channel, authorID)` | Done |
| 3.6 | Web admin identity | ✅ | ✅ `channel=web` + `role=system/admin` → "Admin" | Done |
| 3.7 | Display name fallback | ✅ | ✅ Paired username → platform display name → PlatformID | Done |
| 3.8 | User management CLI | ✅ via SDK | ✅ `goclaw pairing` (`list`, `pending`, `approve`, `link`, `revoke`) | Done |
| 3.9 | **Path traversal protection** | — | ✅ `/` and `\` → `_` in user IDs of legacy file names | **GoClaw only** |
| 3.10 | Auto-merge across platforms | ✅ | — | **TODO** |

### `UserDisplayInfo` struct

```go
type UserDisplayInfo struct {
    DisplayName    string  // "Alice"
    Role           string  // "admin", "user", "guest"
    Channel        string  // "discord", "msteams", "web"
    UnifiedUserID  string  // Paired user ID
    PlatformID     string  // Raw platform account ID
    PersonalMemory string  // not set: personal memory is added by GetSessionContext
}
```

### Prompt Output

The block goes into the session context. Every provider but Ollama gets it as XML:

```
<user label="CURRENT USER">
You are talking to **Alice** (role: admin, via discord).
Address them by name when appropriate. Do NOT confuse platform IDs (numbers) with the user's actual name.
</user>
```

An Ollama agent gets the same text under a `### CURRENT USER` heading.

**Coverage:** cross-platform identity, personal memory and path safety are done; auto-merge across platforms is not.
