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:
- Option A: Modify each trigger source (HeartbeatService, ProactiveMonitorService, etc.) to individually call ChannelGatewayService with guard logic duplicated in each
- Option B: Create a centralized ProactiveDispatcherService as a governance layer between triggers and delivery
- 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
APIs/backend-vitto-api/src/app/proactive/proactive-dispatcher.service.tsAPIs/backend-vitto-api/src/app/proactive/proactive-monitor.service.tsAPIs/backend-vitto-api/src/app/proactive/heartbeat.service.tsAPIs/backend-vitto-api/src/app/engine/vitto-engine.service.ts
🔗 Related
- Decision ID:
dev_1773360580817_puqxwa1 - Session: 210
- Workspace: gspace1
Auto-generated by MCP Smart Consolidation