02.6: Participants
The densest single contributor: list every conversation participant with per-user details (presence, roles, personal memories, reminders), mention aliases with conflict detection, and the closing channel/time-of-day footer.
File: src/utils/text/context/participants.ts:38-236
Mission
Section titled “Mission”For every user ID in userList (collected from message history by the
chat pipeline), emit a rich detail block: display name, mention aliases
(unique-resolution computed), online/presence status, server roles,
per-user personal memories (with tag-filtering against the conversation
corpus, like server memories in stage 03), pending reminders, and public
Physical Appearance tags for image generation. Then fold in Matrix bridge users and
synthetic webhook users (persona-flavored). Close with channel name +
current time-of-day (timezone-aware).
The output is one context item — all participants live in a single
[System: The following users are having a conversation: ...] block.
Substantial — see signature in participants.ts:38-58. Notable:
userList: string[]— Discord user IDs from historytriggererName,botName,personaLineageIdtomoriState,tomoriConfig(providespersonal_memories_enabled,timezone_offset)isDMChannel,isUserImpersonation,impersonatedUserId,impersonatedIdentityNamematrixUsers: Map<string, string>— Matrix user ID → stripped display namesyntheticUsers: Map<string, { displayName, type: "persona" | "webhook" }>conversationCorpus— for personal-memory tag filteringsnapshot,convertMentions
Output
Section titled “Output”Promise<StructuredContextItem | null> — null if userList is empty,
otherwise one user-role item tagged KNOWLEDGE_USERS_IN_CONVERSATION.
Also populates conversationUsers: ConversationUserReference[] on the
context item — a structured list used downstream for mention resolution by
the streaming pipeline.
Content shape:
[System: The following users are having a conversation:
If {botName} wants to ping any of these users, prepend an "@" symbol to a uniquemention handle shown below (case-insensitive). [...]
{botName} (This is you!)- Status: Online - Currently active and responding to messages- Physical Appearance: blue hair, red eyes, white hoodie
UserA (Mention: @{UserA}; Aliases: @{aliceA}, @{alice_global})- Physical Appearance: short white hair, red eyes- Status: Online - Playing Stardew Valley- Server Roles: Mod, Member- Memories: [id:42] Likes cats (tags: pets, animals)- Reminders: - ID:42 "Take meds" (scheduled for Tue, May 21, 2026 10:00 AM (UTC-7), repeats every 24 hour(s))
Conversation context: #general (ID: 1234...).Current time: May 21, 2026 18:30 UTC+09:00 (JST), evening.]Side effects
Section titled “Side effects”- DB / cache reads (per user):
userRepository.loadByDiscordId(userId)— load ornull- If missing and the user is in the guild:
userRepository.register(...)auto-registers them userRepository.isBlacklisted(cached viauserCache)userRepository.getPrivacyLevel(cached)personalMemoryRepository.loadForUserLineageif eligibleserverScheduleRepository.getPendingRemindersForUserfor each user; pending reminders includeID:Nso the LLM can target them withupdate_taskfor requester-scoped edits/deletes
- Discord fetches:
guild.members.fetch(userId)for role / display-name resolutionclient.users.fetch(userId)fallback for users not in guildgetUserPresenceDetailsfor online status + activities (requiresGuildPresencesintent)
- Synthetic persona enrichment —
getCachedAllPersonasto populate image-appearance tags for synthetic persona entries. - Mention alias collection — addresses, server nicknames, global names,
usernames, and custom nicknames are collected per user;
aliasCountstracks duplicates across users to detect conflicts. - Final mention conversion — assembled text passes through
convertMentions.
Invariants
Section titled “Invariants”After this stage runs:
- Returns
nullonly whenuserListis empty. - Every user entry has a
displayName(falls back to<@id>for missing data). - Mention aliases marked as
uniqueare exactly those that appear once acrossaliasCounts— duplicates are silently dropped from the mention handle list (the LLM is told “mention requires clarification” instead). - Each entry’s
aliases(server nickname, global name, username, custom nickname) plus itsdisplayLabelare emitted asconversationUsersmetadata for tool-side user resolution (resolveUserTarget). The conversation stage of that resolver matches input against the full alias set, but breaks ties by preferring a single candidate whosedisplayLabel(primary name) equals the input over candidates that only matched a secondary alias — so one user’s server-nickname alias colliding with another user’s actual name no longer forces a needless clarify round-trip. - Personal memories are filtered by privacy (
PrivacyLevel.MINIMALrequired) AND blacklist ANDpersonal_memories_enabledAND conversation-corpus tag match (ifmemory_tagging_enabled). - Physical Appearance tags are public for users/personas present in chat and remain separate from public attributes.
- Matrix and synthetic users are appended after normal users and are
marked non-mentionable (
mentionable: false). - The closing footer always emits, even with one participant.
Configuration
Section titled “Configuration”| Source | Field | Effect |
|---|---|---|
tomoriConfig | personal_memories_enabled | Master switch for per-user memories + nickname usage |
tomoriConfig | memory_tagging_enabled | (Set upstream in nativeBuilder) Drives conversationCorpus tag filter for personal memories |
tomoriConfig | timezone_offset | Hours offset for current-time footer |
| Client intent | GuildPresences | Required for online/activity status; without it, only static info is shown |
| User row | personal_dtm, privacy_level | Privacy controls — FULL users are skipped entirely from listing |
| User row | physical_appearance_tags | Public physical appearance image tags |
Extension points
Section titled “Extension points”This is the single richest plugin surface in the context-build pipeline. Multiple plugin-relevant seams:
| Surface | Plugin-relevance |
|---|---|
Personal memories per user (personalMemoryRepository.loadForUserLineage) | The “personal memory type” plugin category — a sister to server memories (stage 03). |
Matrix-user folding (matrixUsers map) | A bridge plugin emits its users via this map; the contributor handles them uniformly. A Telegram/Slack bridge plugin would extend the same map. |
| Synthetic users (persona / webhook) | The chat pipeline pre-populates syntheticUsers; a plugin shipping a new “fake participant” type would extend the map. |
Pending reminders (serverScheduleRepository.getPendingRemindersForUser) | Reminder system is core, not plugin — but a plugin adding “scheduled events” might want a parallel display block here. → plugin plan candidate. |
Physical Appearance tags (physical_appearance_tags) | Coupled to image-generation tooling; a plugin adding a different image-gen tag scheme would extend the normalizeImageAppearanceTags path. |
| Channel + time-of-day footer | Internal — coupled to timezoneHelper. |
A plugin extension for “alternate participant rendering” (e.g. collapse-when-many-users, show-roles-only-for-mods) would either:
- (a) Wrap this contributor with a post-processor on the emitted text. Brittle — text format is not a contract.
- (b) Replace this contributor entirely with the plugin’s own. Cleaner — if a “register contributor” mechanism is built. → plugin plan candidate.
Related docs
Section titled “Related docs”- Server memories (parallel):
03-server-memories.md - User presence (helper,
history.ts): covered in native-assembly README. - Display-name resolution: → no dedicated doc;
src/utils/discord/displayName.tshelper only - Reminder system: → no dedicated doc;
serverScheduleRepositoryAPI only - Image-generation Physical Appearance tags: → no dedicated doc;
physical_appearance_tagsis documented inline in the persona/user schemas