跳转到内容

Chat Pipeline

此内容尚不支持你的语言。

The chat pipeline turns a single Discord messageCreate event into zero, one, or many persona replies. It is the primary coordinator of TomoriBot: context construction, tool execution, provider streaming, and memory persistence are all reached through this pipeline.

  • Entry point: tomoriChat() in src/events/messageCreate/tomoriChat.ts
  • Triggered by: Discord messageCreate events, empty-response retries, queue replays, stop-response generation, cross-channel boomerang follow-ups, and command invocations.

Stage flow

tomoriChat(TomoriChatInput)
│
▼
[01] normalizeChatInvocation → ChatIncoming
│
▼
[02] evaluateChatAdmission → ChatAdmission
│ (run | ignore | queued | blocked | error)
│ disposition === "run"?
│ no ─────────────────────────────→ [03] handleChatDisposition → end
│ yes
▼
[04] runWithChannelLock { ← concurrency wrapper
│
▼
[05] planChatTurns → ChatTurnPlan { turns: ChatTurn[] }
│
│ turns.length === 0? ────────────────→ release lock, replay queue, end
│ else: for each turn:
│ │
│ ▼
│ [06-per-turn]
│ ├─ [01] buildChatTurnContext → ChatTurnContext
│ ├─ [02] createChatResponseSink → ChatResponseSink
│ ├─ [03] runGenerationTurn → GenerationTurnResult
│ └─ [04] runPostTurnEffects
│
} ← lock released, queued messages replayed

Stage index

#StageGuideOwnership
01normalizeChatInvocation01: Input NormalizationSets defaults for optional input parameters.
02evaluateChatAdmission02: Admission CheckFilters suppressed messages, checks permissions, and gates queue entry.
03handleChatDisposition03: Chat DispositionEmits terminal logging and notifies caller of rejected or non-run outcomes.
04runWithChannelLock04: Channel LockEnforces channel mutex, keeps typing active, and replays queued turns.
05planChatTurns05: Turn PlanningEvaluates triggers and quotas, selects personas, and queues multi-persona turns.
06Per-turn loop06: Per-Turn LoopRuns context build, response sink setup, generation, and post-turn effects.

Cross-references

Concurrency and re-entrancy

  • Per-channel serialization: runWithChannelLock permits exactly one active turn sequence per channel at a time.
  • Queueing and interruption: messages arriving while a channel is busy are either enqueued for FIFO replay after lock release, converted to a follow-up interrupt if eligible, converted to a natural stop request, or ignored.
  • Re-entrant invocations:
    • Empty-response retries bypass the channel lock using skipLock=true to reuse the existing turn lock.
    • Natural stop responses execute through handleStopResponse via setImmediate after outer lock release.
    • Cross-channel boomerang turns execute through suppressNextSelfReply via setImmediate after lock release.

Troubleshooting files

The /troubleshoot chat command produces a private JSON diagnostic file for a user’s recent message in the current channel. It inspects in-memory traces recorded by diagnosticTimeline.ts across the past hour.

The diagnostic file records UTC timestamps, relative millisecond offsets, admission results, planned persona turns, context message counts, attempt model labels, tool iterations, and Discord send events. It excludes raw message content, prompts, user IDs, and raw channel IDs. The in-memory trace store retains at most 5,000 traces for one hour.

The timeline also records typing_started after the initial typing request completes, context_built after context assembly (history REST duration, reply fetch count, participant preparation duration, and native build duration), and provider_first_chunk on the first stream progress callback for each provider call. The progress callback runs when a chunk reaches the stream consumer, before chunk processing, and also refreshes the existing stream watchdog.

Turn latency metric

log.metric("chat_turn_latency", fields) emits once when each started persona turn ends, including failed turns and bot or webhook triggers without a diagnostic owner. Admission refusals and plans with no persona turns emit no sample. A queued replay or persona job is a separate invocation; personas in one invocation share its start, admission duration, and planning duration. The async scope isolates concurrent channels and stops accepting updates when its turn ends.

All durations use performance.now() except gateway_ms, which compares wall clocks. Every sample has the same fields. An unstarted duration or an unreached milestone is -1; counts and summed image time start at zero. A failed stage that started still records its elapsed time. Durations overlap and must not be added to reconstruct total latency.

NumberMeaning
gateway_msInvocation entry wall time minus Discord’s trigger createdTimestamp; includes upstream delay and clock skew and can be negative.
admission_msTime in evaluateChatAdmission.
plan_msTime in planChatTurns.
context_msThis persona’s full context build, including intent preflight.
dispatch_msInvocation entry to the first streamToDiscord adapter entry in this turn. Later personas include time spent on earlier personas.
provider_ttfb_msFirst adapter entry to its first stream progress callback; -1 if that call produces no progress, even if a fallback later succeeds.
first_message_msInvocation entry to the first confirmed send recorded by recordChatMessageSent, including held response delivery.
history_fetch_msSum of channel history messages.fetch durations.
assets_msPersona emoji, sticker, and custom expression loading.
history_build_msThe whole buildSimplifiedHistory call, including history_fetch_ms and reply fetches.
participant_msThe prepareParticipantContext call; hydration inside buildContext belongs to native_ms.
naming_msThe history naming projection.
reunion_msReunion note resolution; -1 when a borrowed trigger skips it.
native_msSum of buildContext durations, including native assembly, participant hydration, RAG, and any preset reassembly.
reaction_fetchesCalls to reaction.users.fetch during metadata reveal; ordinary turns use cached reaction counts and record zero.
reply_fetchesReply target REST calls in withReplyContext, after history-window and message-cache misses, including failed calls.
image_fetchesNetwork download attempts at the safeDownload seam in fetchAndOptimizeImage, including failed downloads but excluding data URIs and policy-blocked results.
image_msSum of those download durations, through body download and excluding image optimization; overlapping downloads each contribute their wall time.

The dispatch boundary precedes adapter-side image preparation and the provider HTTP request. Image download time therefore falls after dispatch_ms and inside provider_ttfb_ms when the first call downloads images. The download counter lives immediately around safeDownload in imageProcessor.ts, so a cache returning before that seam skips both the counter and download timing. It counts download operations, rather than individual redirect requests. No URLs, message text, model names, or endpoint identifiers enter this metric.

DimensionFixed values and meaning
provideranthropic, custom, deepseek, google, novelai, nvidia, openrouter, vertex, vertexexpress, zai, zaicoding, or unknown; the first dispatched provider, otherwise the context’s planned provider when known. Custom endpoint IDs collapse to custom.
humanizernone, light, medium, or heavy when the toggles match that /setup preset, custom for any other combination, or unknown before context configuration is available.
sourcemessage, queue, persona_job, retry, manual, or stop, using the diagnostic timeline’s source precedence.
ragyes when built context contains server document retrieval content, otherwise no; it does not claim every retrieval attempt injected content.
historyreused for a busy-stretch window, otherwise rest.
draftingyes when response review enabled holding response text during this turn, otherwise no.

For example, compare dispatch_ms for the same provider and source before inspecting the stage and download fields for the source of a change. context_ms minus the context stage fields (assets_ms, history_build_ms, participant_ms, naming_ms, reunion_ms, native_ms) leaves intent preflight, forced mentions, channel prompt lookups, sprite checks, and prefill.

Source pointers

  • src/events/messageCreate/tomoriChat.ts: main chat entry point and stage coordinator.
  • src/utils/chat/channelQueue.ts: channel locking, queue storage, and re-entry helpers.
  • src/utils/chat/diagnosticTimeline.ts: per-turn event recording and diagnostic trace store.
  • src/utils/chat/turnLatency.ts: independent per-turn latency accumulator and fixed metric shape.
  • src/commands/troubleshoot/chat.ts: troubleshooting diagnostic command implementation.