Skip to content

Chat Pipeline

The chat pipeline turns a single Discord messageCreate event into zero, one, or many persona replies. It is the spine of TomoriBot — most other AI subsystems (context build, tool loop, provider streaming, memory capture) are reached from inside this pipeline.

Entry point: src/events/messageCreate/tomoriChat.ts:tomoriChat()

Triggered by: every Discord messageCreate event (including bot/webhook messages — filtering happens inside evaluateChatAdmission), plus internal re-invocations from retries, queue replays, stop-response generation, boomerang follow-ups, and command-driven manual triggers.

This folder is numbered. Read the stage files top to bottom; the per-turn loop lives in 06-per-turn/.

tomoriChat(TomoriChatInput)
[01] normalizeChatInvocation → ChatIncoming
[02] evaluateChatAdmission → ChatAdmission
│ (run | ignore | queued | blocked | error)
│ disposition === "run"?
│ no ─────────────────────────────→ [03] handleChatDisposition → end
│ yes
[04] runWithChannelLock { ← concurrency wrapper (not a transform)
[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
#StageFileMission
01normalizeChatInvocation01-normalize-invocation.mdDefensive input normalization.
02evaluateChatAdmission02-evaluate-admission.mdDecide if/how this message turns into a generation.
03handleChatDisposition03-handle-disposition.mdTerminal handler for non-run dispositions.
04runWithChannelLock04-channel-lock.mdPer-channel mutex + typing keepalive + queue replay.
05planChatTurns05-plan-turns.mdPersona selection + per-turn state assembly.
06per-turn loop06-per-turn/Iterated once per responding persona.
  • Per-turn stage 01 (build context) delegates to the context-build pipeline.
  • Per-turn stage 03 (generation) delegates to the tool-loop pipeline and the provider pipeline.
  • Per-turn stage 04 (post-turn effects) writes to memory and may schedule cross-channel work via the boomerang mechanism in crossChannelMessageTool.
  • One channel ⇄ one active turn-sequence at a time. Enforced by runWithChannelLock.
  • Messages arriving while a channel is locked are either enqueued for replay after lock release, converted to a follow-up interrupt (if eligible), converted to a natural-stop signal (if matching stop phrasing), or dropped — full decision tree in 04-channel-lock.md.
  • Three recursive re-entries into tomoriChat() are by design: empty-response retry (with skipLock=true), stop-response generation (after lock release, via handleStopResponse), and boomerang follow-up (with suppressNextSelfReply).
  • tomoriChat.ts is the coordinator only — all stage implementations live under src/utils/chat/. The event-loader scans src/events/messageCreate/*.ts shallowly, so helper modules colocated with chat logic must not sit in that folder (they would be auto-registered as additional handlers).