06.1: Build Context
Assemble the LLM-visible prompt for one persona turn.
File: src/utils/chat/contextPipeline.ts:49-193
Mission
Section titled “Mission”Build the ChatTurnContext closure carried through the rest of the per-turn
loop. Fetch and simplify recent message history, hydrate per-message
annotations (reply/reaction/forward/media/embed), load emoji/sticker assets,
delegate to the context-build pipeline for the LLM-shaped prompt
assembly, then append tail directives. Returns the full ChatTurnContext —
the closure that stages 02–04 read and mutate.
This stage is the thin chat-side wrapper around a much larger inner pipeline. The heavy lifting (mentions, memories, RAG, persona prompt, participants, dialogue history) lives in context-build.
ChatTurn (one element from ChatTurnPlan.turns, produced by stage 05). See
src/utils/chat/types.ts:141-171.
Output
Section titled “Output”ChatTurnContext — the per-turn closure. See src/utils/chat/types.ts:173-212.
Key fields populated here:
contextItems: StructuredContextItem[]— the LLM-shaped prompt, including tail directives.simplifiedMessages: SimplifiedMessageForContext[]— the message-history digest used both for the LLM and for post-turn memory capture.streamingContext: StreamingContext— per-turn flags consumed by the stream orchestrator and tool layer.messageIdMap: MessageIdMap— translation table between Discord message IDs and LLM-visible compact IDs (used for reply targeting).emojiStrings,loadedEmojis,loadedStickers— persona assets.- Carried trigger metadata (
triggererName, channel name/description, etc.).
Side effects
Section titled “Side effects”- Message-history fetch —
channel.messages.fetch({ limit })retrieves up tomessage_fetch_limitrecent messages from Discord. - Voice-transcript pre-hydration — for historical audio messages not in
chat mode, runs STT synchronously before the simplify loop so cache
lookups inside
simplifyMessageare non-async. Writes results to the voice-transcript cache. - Consecutive same-author merge — after simplifying each message, the loop
collapses it into the previous entry when (1) the effective
authorIdmatches, (2) the debug ($:)/normal kind matches (a debug message never merges with a normal one even though they share an authorId), and (3) neither side carries media (media forces a separate turn so per-message media IDs stay unambiguous). Merged entries recordcombinedMessageIds,individualContents, andcombinedCreatedAtssoreveal_message_metadatacan still surface oneref_N+ timestamp per original message. - Persona-asset cache load —
loadEmojiStickerCache(...)may hit Discord if the cache is cold. - Reply-target fetch —
channel.messages.fetch(referenceMessageId)if the message references one that’s not in cache. - Reset/compact-refresh detection — scans message embeds for
"reset"or"compact_refresh"markers and slices history at the marker. - Reminder injection — if the incoming carries
reminderData, injects a synthetic[System: …]message intosimplifiedMessagesso the LLM sees the reminder context. - Media descriptor capture — this stage no longer decides whether the
answering model can see images or videos.
buildContextrecords capability-neutralmediaDescriptorson dialogue items, plus budget-only notices such as rendered-image-limit skips. The per-attempt generation stage resolves those descriptors against the routed attempt model, including personal-provider routing, OpenRouter live capability overrides, and fallback attempts. - Impersonation identity resolution — if
isUserImpersonation, fetches the impersonated user’s nickname/avatar viaresolveImpersonatedIdentity.
Invariants
Section titled “Invariants”After this stage runs:
contextItemshas tail directives appended in the correct priority order: emoji penalty (lower priority, inserted before the latest dialogue pair), stop/reasoning/manual directives (combined into one user message at the tail), queued-reply directive, uncensor directive, and manual-prefill model message (last).simplifiedMessagesexcludes messages from privacy-FULL users.simplifiedMessagescollapses runs of consecutive same-author pure-text messages into a single entry (see the merge rule above); media-bearing or debug-boundary messages remain their own entries.- The
messageIdMapis populated with every message ID the LLM will see. streamingContext.explicitLongTermMemoryIntentreflects whether the triggering message mentions long-term memory phrasing.streamingContext.replyNoticeStateis initialized to{ attempted: false, sent: false }whenincoming.isFromQueueis true and the turn’s persona is an alter. This is the only place wherereplyNoticeStateis set; without it the alter “Replying to…” embed in stage 07 is suppressed (the presence of the object is the enable-switch, not its field values).
Extension points
Section titled “Extension points”This stage is a coordinator over many extension-relevant helpers:
| Helper | File | Plugin-relevance |
|---|---|---|
buildContext | utils/text/contextBuilder.ts | The context-build pipeline’s public API — the main extension surface for memories, RAG, persona prompt assembly |
simplifyMessage + sub-helpers (withReplyContext, withReactionContext, buildForwardContext) | this file | Per-message annotation pipeline; new annotation types hook here |
processEmbedsFromMessage | contextEmbeds.ts | Embed classification + content extraction; new embed type plugins hook here |
appendSupportedMediaFromMessage, appendStickersFromMessage, etc. | contextMedia.ts | Media attachment extractors; new media kinds hook here |
buildReactionContextAnnotation, buildReplyReferenceContextAnnotation | contextAnnotations.ts | Annotation builders; reaction/reply formatting hooks here |
appendTailDirectives | this file | Tail-directive assembly; new directive kinds insert here |
The stage itself is a thin coordinator. Most plugin work for “show the LLM something different” goes either into the inner context-build pipeline (memories/RAG/persona) or into one of the per-message helpers above. The appropriate seam depends on whether the change is per-message (annotation/media) or per-prompt (directive/persona/memory).
Related docs
Section titled “Related docs”- Inner pipeline: → context-build
- Tail directive priorities: → folded into context-build docs
- Embed classification: → no dedicated doc;
embedClassifier.tshelper only - Voice transcripts: → no dedicated doc yet