AssistantIdentity — agentId, name, avatar, emojiAssistant Identity
Name, Avatar & Emoji Resolution
The assistant identity subsystem resolves the display name, avatar, and emoji for each assistant in a session. It follows a priority chain — workspace configuration files take precedence over global config, which falls back to built-in defaults. Strict size limits and normalization ensure consistent rendering across all clients.
{agentId: "main", name: "Assistant", avatar: "A"}resolveAssistantIdentity(params)Table of Contents
Overview
When the Gateway renders a message from an assistant, it needs to know what name to display, what avatar image (or letter) to show, and optionally what emoji to use as a status indicator. These values are not hardcoded — they come from a resolution chain that checks multiple sources in priority order.
This design allows workspace-level customization (e.g., a project can define a custom assistant name and avatar in its configuration files) while ensuring that every assistant always has a valid identity, even if no customization is provided.
Core Types
AssistantIdentity
The resolved identity object that fully describes how an assistant should be presented in the UI.
interface AssistantIdentity {
/** Unique agent identifier (e.g., "main", "code-review", "docs") */
agentId: string;
/** Display name shown in the chat UI (max 50 characters) */
name: string;
/**
* Avatar value. Can be:
* - A single uppercase letter (e.g., "A")
* - A URL pointing to an image (max 200 characters)
* - An emoji character used as the avatar
*/
avatar: string;
/** Optional emoji displayed next to the assistant name */
emoji?: string;
}
DEFAULT_ASSISTANT_IDENTITY
The built-in fallback identity used when no configuration overrides are found.
const DEFAULT_ASSISTANT_IDENTITY: AssistantIdentity = {
agentId: 'main',
name: 'Assistant',
avatar: 'A',
// emoji is intentionally undefined by default
};
Size Limit Constants
| Constant | Value | Applied To |
|---|---|---|
MAX_ASSISTANT_NAME | 50 | AssistantIdentity.name |
MAX_ASSISTANT_AVATAR | 200 | AssistantIdentity.avatar |
MAX_ASSISTANT_EMOJI | 16 | AssistantIdentity.emoji |
Resolution Priority
The resolveAssistantIdentity() function checks sources in the following priority order. The first source that provides a value for each field wins.
flowchart TD
A[resolveAssistantIdentity called] --> B{workspaceDir provided?}
B -- Yes --> C[Read workspace config files]
C --> D{Workspace has identity config?}
D -- Yes --> E[Extract name, avatar, emoji from workspace]
D -- No --> F{agentId provided?}
B -- No --> F
E --> G[Apply size limits and normalization]
F -- Yes --> H[Lookup agentId in global config]
H --> I{Config has entry for agentId?}
I -- Yes --> J[Extract name, avatar, emoji from config]
I -- No --> K[Use DEFAULT_ASSISTANT_IDENTITY]
F -- No --> K
J --> G
K --> G
G --> L[normalizeAvatarValue]
L --> M[normalizeEmojiValue]
M --> N[Return AssistantIdentity]
resolveAssistantIdentity
The main resolution function takes configuration, an optional agent ID, and an optional workspace directory, then returns a fully-resolved identity.
Function Signature & Parameters
The function accepts a single params object to keep the API clean and extensible.
interface ResolveAssistantIdentityParams {
/** Global gateway configuration */
cfg: GatewayConfig;
/** Agent ID to resolve (defaults to "main") */
agentId?: string;
/** Workspace directory for project-level overrides */
workspaceDir?: string;
}
function resolveAssistantIdentity(
params: ResolveAssistantIdentityParams
): AssistantIdentity {
const { cfg, agentId = 'main', workspaceDir } = params;
// 1. Check workspace-level overrides first
if (workspaceDir) {
const wsIdentity = readWorkspaceIdentity(workspaceDir, agentId);
if (wsIdentity) {
return normalizeIdentity(wsIdentity);
}
}
// 2. Check global config for agent-specific identity
const configIdentity = cfg.assistants?.[agentId];
if (configIdentity) {
return normalizeIdentity({
agentId,
name: configIdentity.name,
avatar: configIdentity.avatar,
emoji: configIdentity.emoji,
});
}
// 3. Fall back to defaults
return { ...DEFAULT_ASSISTANT_IDENTITY, agentId };
}
Workspace File Reading
The workspace reader looks for identity configuration in well-known file locations within the project directory.
function readWorkspaceIdentity(
workspaceDir: string,
agentId: string
): Partial<AssistantIdentity> | null {
const configPaths = [
path.join(workspaceDir, '.assistant', `${agentId}.json`),
path.join(workspaceDir, '.assistant', 'identity.json'),
path.join(workspaceDir, '.config', 'assistant.json'),
];
for (const configPath of configPaths) {
try {
const raw = fs.readFileSync(configPath, 'utf-8');
const parsed = JSON.parse(raw);
if (parsed.name || parsed.avatar || parsed.emoji) {
return { agentId, ...parsed };
}
} catch {
// File not found or invalid JSON — try next path
}
}
return null;
}
Normalization Pipeline
Every resolved identity passes through normalization to enforce size limits and validate avatar values.
function normalizeIdentity(
raw: Partial<AssistantIdentity> & { agentId: string }
): AssistantIdentity {
return {
agentId: raw.agentId,
name: (raw.name ?? DEFAULT_ASSISTANT_IDENTITY.name)
.slice(0, MAX_ASSISTANT_NAME),
avatar: normalizeAvatarValue(
raw.avatar ?? DEFAULT_ASSISTANT_IDENTITY.avatar
),
emoji: raw.emoji
? normalizeEmojiValue(raw.emoji)
: undefined,
};
}
Avatar Normalization
The normalizeAvatarValue() helper validates and constrains avatar values. URLs are checked for validity, and all values are truncated to the size limit.
function isAvatarUrl(value: string): boolean {
try {
const url = new URL(value);
return url.protocol === 'https:' || url.protocol === 'http:';
} catch {
return false;
}
}
function normalizeAvatarValue(value: string): string {
const trimmed = value.trim();
if (trimmed.length === 0) {
return DEFAULT_ASSISTANT_IDENTITY.avatar;
}
if (isAvatarUrl(trimmed)) {
// URL avatars: truncate to limit, warn if truncated
if (trimmed.length > MAX_ASSISTANT_AVATAR) {
console.warn(`Avatar URL truncated from ${trimmed.length} to ${MAX_ASSISTANT_AVATAR} chars`);
}
return trimmed.slice(0, MAX_ASSISTANT_AVATAR);
}
// Non-URL avatars: take first character (letter or emoji)
return trimmed.charAt(0).toUpperCase();
}
Emoji Normalization
The normalizeEmojiValue() helper ensures emoji values are within size limits and contain valid Unicode characters.
function normalizeEmojiValue(value: string): string | undefined {
const trimmed = value.trim();
if (trimmed.length === 0) return undefined;
// Truncate to MAX_ASSISTANT_EMOJI to prevent oversized values
const normalized = trimmed.slice(0, MAX_ASSISTANT_EMOJI);
// Verify at least one emoji-like character exists
const emojiRegex = /\p{Emoji}/u;
if (!emojiRegex.test(normalized)) {
console.warn(`Invalid emoji value: "${normalized}"`);
return undefined;
}
return normalized;
}
Identity Class Diagram
The following diagram shows the relationships between the identity types, the resolver, and the normalization helpers.
classDiagram
class AssistantIdentity {
+string agentId
+string name
+string avatar
+string emoji
}
class DEFAULT_ASSISTANT_IDENTITY {
+agentId = "main"
+name = "Assistant"
+avatar = "A"
}
class ResolveAssistantIdentityParams {
+GatewayConfig cfg
+string agentId
+string workspaceDir
}
class GatewayConfig {
+Record assistants
}
class SizeLimits {
+MAX_ASSISTANT_NAME = 50
+MAX_ASSISTANT_AVATAR = 200
+MAX_ASSISTANT_EMOJI = 16
}
class Helpers {
+isAvatarUrl(value) bool
+normalizeAvatarValue(value) string
+normalizeEmojiValue(value) string
+normalizeIdentity(raw) AssistantIdentity
+readWorkspaceIdentity(dir, id) Partial
}
ResolveAssistantIdentityParams --> GatewayConfig : uses
ResolveAssistantIdentityParams --> AssistantIdentity : produces
DEFAULT_ASSISTANT_IDENTITY --> AssistantIdentity : implements
Helpers --> SizeLimits : enforces
Helpers --> AssistantIdentity : returns
Helpers --> DEFAULT_ASSISTANT_IDENTITY : fallback
Edge Cases & Error Handling
| Scenario | Behavior | Result |
|---|---|---|
| Workspace config is malformed JSON | Silently skipped, falls through to next source | Uses global config or defaults |
| Name exceeds 50 characters | Truncated to 50 characters | Warning logged |
| Avatar URL exceeds 200 characters | Truncated to 200 characters | Warning logged, may break URL |
| Empty name string | Falls back to "Assistant" | Default name used |
| Empty avatar string | Falls back to "A" | Default avatar used |
| Invalid emoji (no Unicode emoji chars) | Emoji field set to undefined | No emoji shown |
| Non-HTTPS avatar URL | HTTP URLs are allowed but not recommended | Works in development; blocked by CSP in prod |
| Unknown agentId | No config entry found | Uses DEFAULT_ASSISTANT_IDENTITY with provided agentId |
| Workspace dir does not exist | readFileSync throws, caught silently | Falls through to global config |
DEFAULT_ASSISTANT_IDENTITY is always the last resort, guaranteeing a valid return value.
Workspace Configuration
Projects can customize assistant identity by placing JSON files in their workspace directory. The resolver checks these locations in order:
| Priority | Path | Scope |
|---|---|---|
| 1 (highest) | .assistant/{agentId}.json | Agent-specific override |
| 2 | .assistant/identity.json | Default identity for all agents |
| 3 | .config/assistant.json | Legacy configuration path |
Example workspace config
// .assistant/main.json
{
"name": "ProjectBot",
"avatar": "https://example.com/bot-avatar.png",
"emoji": "🚀"
}
// .assistant/code-review.json
{
"name": "CodeReviewer",
"avatar": "R",
"emoji": "🔍"
}
Integration Points
The assistant identity resolver is called from several points in the Gateway.
| Caller | When | Purpose |
|---|---|---|
message.send handler | Before streaming response | Attach identity to assistant messages |
session.create handler | On new session | Set initial assistant display info |
assistant.resolve handler | On explicit resolve request | Let clients preview identity before use |
| Session snapshot derivation | On snapshot generation | Include identity in lifecycle snapshots |
Related Topics
- Routing Overview — full routing subsystem
- Message Dispatch — how RPC requests reach handlers
- Session Management — session lifecycle and state tracking
- Auth — authentication layer
Directory Map
Source files for this topic
src/gateway/
├── identity/
│ ├── index.ts ← AssistantIdentity, DEFAULT_ASSISTANT_IDENTITY
│ ├── resolve.ts ← resolveAssistantIdentity(), ResolveAssistantIdentityParams
│ ├── normalize.ts ← normalizeAvatarValue(), normalizeEmojiValue()
│ ├── constants.ts ← MAX_ASSISTANT_NAME, MAX_ASSISTANT_AVATAR, MAX_ASSISTANT_EMOJI
│ └── workspace.ts ← readWorkspaceIdentity()
├── config/
│ └── types.ts ← GatewayConfig (includes assistants map)
└── handlers/
└── assistant.ts ← assistant.resolve handler