Architecture: How should Vitto deliver proactive notifications across mult...

Documentation for Sessions

🏗️ Architecture: How should Vitto deliver proactive notifications across mult...

Date: 2026-03-13
Time: 00:09:40 UTC
Session: gspace1-210.6
Agent: backend
Confidence: 95%
Status: ✅ Documented


📋 Decision Context

Question:

How should Vitto deliver proactive notifications across multiple channels (WhatsApp, Slack, Web) with user-controlled safety guards?

Task: S210.4: Implement Dossier Enhancement + ProactiveDispatcher for WhatsApp↔Vitto Jarvis loop


🎯 Decision Made

Chosen:

Create a centralized ProactiveDispatcherService that sits between ALL trigger sources and the existing ChannelGatewayService. Dispatcher reads user preferences from UserDossier (proactivePreferences) and UserNotificationPrefs (channelId), applies 5 safety guards (master toggle, per-trigger opt-out, quiet hours, frequency cap, dedup), then fans out by severity: critical→all channels, warning→preferred, info→web only. Existing delivery infrastructure (TwilioService, ChannelGatewayService) untouched.

Options Considered:

  1. Option A: Modify each trigger source (HeartbeatService, ProactiveMonitorService, etc.) to individually call ChannelGatewayService with guard logic duplicated in each
  2. Option B: Create a centralized ProactiveDispatcherService as a governance layer between triggers and delivery
  3. Option C: Extend ChannelGatewayService directly with safety guard logic

💭 Reasoning

Option B was chosen because: (1) DRY — safety guards (quiet hours, frequency cap, dedup) need to be consistent across ALL trigger types, not duplicated in each service; (2) Single Responsibility — ChannelGatewayService handles delivery protocol (Twilio API, Slack API), not business rules about when to send; (3) Backward compatible — existing services continue working with @Optional() injection, falling back to legacy direct-send if dispatcher unavailable; (4) The pre-S210.4 infrastructure already had full WhatsApp/Slack delivery working (since S117/S128) — this is a governance layer, not plumbing. Root cause of the design decision: ProactiveMonitorService had its own notifyViaChannel() with severity routing, but HeartbeatService only emitted SSE events, and MilestoneNotificationService went through ScheduledMessageQueue — three different paths with no unified safety controls.


📁 Files Involved


🔗 Related


Auto-generated by MCP Smart Consolidation