S210.4 Dossier Proactive Dispatcher
Documentation for Sessions
S210.4: Making Vitto a 24/7 Jarvis-Like Super Agent
What This Is
This document explains, in plain language, what was built in Session 210.4 and why it matters. It's written for anyone on the team — developers, product, leadership — to understand the progress toward making Vitto a true "Jarvis" that's always available, always proactive, and reaches users wherever they are.
The Vision
Imagine this: you ask Vitto to build a new module for your platform. You close your laptop and go grab lunch. Ten minutes later, your phone buzzes with a WhatsApp message from Vitto: "Your player-management module is ready. 4 files generated, quality score 0.85." You reply on WhatsApp: "Great, now change the sidebar color to blue." Vitto understands, executes, and confirms — all through WhatsApp, without you ever opening the web app.
That's the Jarvis vision. Session 210.4 makes it real.
Timeline of Changes
Phase 1: Foundation — Dossier Enhancement (Tasks 1-3)
What changed: We taught Vitto to remember each user's notification preferences, skill preferences, subscription tier, and sync status — all stored in their "dossier" (Vitto's knowledge file about each user).
Why it matters: Before this, Vitto knew about users (their name, expertise, work patterns) but didn't know how they wanted to be contacted. Now, each user's dossier includes:
- Notification preferences: preferred channel (WhatsApp, Slack, web), quiet hours, daily alert limit, which alerts to disable
- Skill preferences: which AI skills to always use or never use
- Subscription tier: for future cost budget enforcement
- Sync tracking: when data was last exchanged with the main platform
We also implemented:
- Space brief in prompts: When a user works in a specific "space" (project), Vitto now injects rich context about that space into every AI response — what modules exist, recent generations, business type. This makes Vitto's answers space-aware.
- Dialectic enrichment: During the first 3 conversations with a new user, Vitto naturally asks for key missing info (what technologies they use, their timezone, how they prefer to work). This builds the dossier organically, without a settings form.
Phase 2: The Dispatcher — Centralized Notification Governance (Tasks 4-5)
What changed: We created the ProactiveDispatcherService — a single gateway that ALL Vitto notifications pass through before reaching the user.
Why it matters: Before this, different parts of Vitto sent notifications through different paths with no unified rules. Health alerts went one way, milestone celebrations another, scheduled digests a third. There were no quiet hours, no frequency limits, no deduplication.
Now, every notification passes through 5 safety guards:
- Master toggle: User can disable all proactive notifications
- Per-trigger opt-out: User can disable specific alert types (e.g., "don't tell me about cost spikes")
- Quiet hours: Respect the user's sleep schedule (e.g., no alerts between 10 PM and 8 AM)
- Frequency cap: Maximum alerts per day (default: 5) to prevent spam
- Deduplication: Same alert can't fire twice within 1 hour
Critical alerts (production failures, build breaks) bypass quiet hours and frequency caps — because those truly need immediate attention.
We also built a Quality Drop Trigger — an hourly check that compares the last 24 hours of task success rates against the 7-day average. If success rate drops more than 15%, Vitto alerts the team. This is the first "self-aware" monitoring trigger.
Phase 3: The Sync — Bidirectional Data Flow (Task 6)
What changed: Created DossierSyncService — a 15-minute cron job that pushes enrichment data from Vitto back to the main GoGrab platform.
Why it matters: Vitto learns things about users that the main platform doesn't know — what technologies they prefer, their communication style, facts about their work. This service pushes those learnings back so the platform can personalize its own UX. Platform data always wins on conflicts (platform is source of truth for identity; Vitto is source of truth for behavioral insights).
Phase 4: Wiring Everything Together (Tasks 7-8)
What changed: Connected all the dots:
- Space brief and dialectic hints wired into the LLM prompt assembly
- Health check triggers and heartbeat alerts now route through the dispatcher
- The
ProactiveModuleregisters all new services
Phase 5: The Critical Missing Link (Post-plan fixes)
What was caught: After building all the pieces, we traced the full end-to-end flow and found the most important connection was missing: when Vitto finishes a task (like generating a module), the engine wasn't calling the dispatcher. Notifications would never reach WhatsApp.
What we fixed:
- Engine → Dispatcher wiring: Task completions and failures now route through
ProactiveDispatcherService - Smart severity: Module generation completions are "warning" severity (reaches WhatsApp/Slack). Quick chat replies are "info" severity (web toast only). Failures are always "warning".
- Auto-setup notification preferences: When a linked WhatsApp user sends their first message, Vitto automatically creates their notification preferences — no manual setup needed. Same zero-friction pattern as the existing magic link account linking.
What Works Now (After Deploy)
| Scenario | Before S210.4 | After S210.4 |
|---|---|---|
| User starts module generation, closes laptop | Nothing happens until they reopen the app | WhatsApp message: "Your module is ready" |
| System health check fails at 2 AM | Alert logged, maybe email | Respects quiet hours — holds until morning (unless critical) |
| Same alert fires 10 times in an hour | 10 notifications | 1 notification (dedup suppresses repeats) |
| User hasn't set up WhatsApp notifications | Manual curl command needed | Auto-created on first WhatsApp message |
| Vitto doesn't know what the user works with | Never asks | Naturally asks during first 3 conversations |
| Task success rate drops sharply | Nobody notices | Hourly quality monitor alerts the team |
How the WhatsApp Loop Works (End-to-End)
1. USER LINKS ACCOUNT (one-time, already existed before S210.4):
User sends WhatsApp message → Vitto sends magic link → User taps link →
Logs into web app → Account linked → Done
2. AUTO-SETUP (NEW in S210.4, happens automatically):
Next WhatsApp message from linked user →
ensureNotificationPrefs() checks if prefs exist →
If not: creates with preferredChannel='whatsapp', channelId=phone →
Sends confirmation: "I'll now send you important alerts here" →
Never runs again
3. OUTBOUND NOTIFICATIONS (NEW in S210.4):
Vitto finishes task → VittoEngineService calls ProactiveDispatcherService →
Dispatcher reads dossier preferences →
Checks safety guards (quiet hours, frequency cap, dedup) →
Resolves channel (WhatsApp) + phone number from UserNotificationPrefs →
ChannelGatewayService.sendResponse() → TwilioService → WhatsApp delivery
4. INBOUND RESPONSES (already existed):
User replies on WhatsApp → Twilio webhook → ChannelGatewayService →
ChannelProcessingService → Agent loop → Response back via WhatsApp
Files Created/Modified
New Files (7)
| File | Purpose |
|---|---|
proactive-dispatcher.service.ts |
Centralized notification dispatch with 5 safety guards |
triggers/quality-drop.trigger.ts |
Hourly task success rate regression detection |
dossier-sync.service.ts |
15-minute outbound enrichment sync to main platform |
proactive-dispatcher.spec.ts |
8 unit tests for dispatcher (all guards + routing) |
quality-drop-trigger.spec.ts |
3 unit tests for quality trigger |
user-dossier-enhanced.spec.ts |
9 unit tests for dossier enhancements |
dossier-sync.spec.ts |
3 unit tests for sync service |
Modified Files (9)
| File | Change |
|---|---|
user-dossier.schema.ts |
4 new field groups (proactive prefs, skills, tier, sync) |
user-dossier.service.ts |
Dialectic enrichment, staleness re-hydration, dirty field tracking |
space-intelligence.service.ts |
Space brief implementation (was a stub returning '') |
context-assembly.service.ts |
Space brief + dialectic hints wired into LLM prompt |
vitto-engine.service.ts |
Task completion/failure → dispatcher with context-aware severity |
channel-processing.service.ts |
Auto-setup notification prefs for linked channel users |
proactive-monitor.service.ts |
Routes health alerts through dispatcher |
heartbeat.service.ts |
Routes heartbeat alerts through dispatcher |
proactive.module.ts |
Registers dispatcher + quality trigger |
users.module.ts |
Registers DossierSyncService |
backend-api-client.service.ts |
pushProfileEnrichment() for outbound sync |
Test Coverage
- 61 tests across 8 test suites, all passing
- Key test scenarios:
- Dispatcher respects all 5 safety guards
- Critical alerts bypass quiet hours
- Deduplication suppresses repeated alerts
- Quality trigger fires on >15% drop, ignores stable rates
- Dossier sync pushes dirty fields, skips clean dossiers
- Dialectic enrichment returns correct missing field, stops after 3 interactions
Commits (12)
| Commit | Type | Description |
|---|---|---|
ea7a57a |
feat | Dossier schema: proactivePreferences, skillPreferences, subscriptionTier, syncMetadata |
(T2) |
feat | getMissingCriticalFields, staleness re-hydration, markFieldDirty |
18fcc0c |
feat | getSpaceBriefForPrompt — space context for LLM prompts |
4bf76ee |
feat | ProactiveDispatcherService — centralized multi-channel dispatch |
(T5) |
feat | QualityDropTrigger — hourly regression detection |
22d12a7 |
feat | DossierSyncService — 15-min outbound enrichment sync |
6fe2066 |
feat | Wire space brief + dialectic enrichment into context assembly |
067681d |
feat | Register dispatcher + trigger, wire triggers through dispatch |
36c852c |
fix | Automatic dedup cache pruning |
342ec51 |
feat | Wire task completion/failure to ProactiveDispatcher |
a2223dd |
fix | Route significant task completions as 'warning' for WhatsApp |
d4ca73e |
feat | Auto-setup notification prefs for linked WhatsApp users |
What's NOT in This Session (Future Work)
| Item | Status | Why Not Now |
|---|---|---|
| Frontend settings UI for notification preferences | Nice-to-have | Auto-setup covers MVP; power users can use API directly |
PUT /api/users/:userId/profile-enrichment endpoint on backend-api |
Needed for outbound sync | DossierSyncService gracefully degrades (logs, doesn't crash) |
| WhatsApp Business template approvals | Twilio/Meta admin task | Sandbox mode works for testing; templates needed for production scale |
| Skills Closed Loop (confidence tiers, anti-pattern mining) | ~70% done | Separate v2 spec section, separate session |
| Golden Path Tests (28 behavioral contracts) | ~15% done | Separate v2 spec section, separate session |
| Cost Improvements (per-feature budgets) | ~40% done | Separate v2 spec section, separate session |
Key Lessons Learned
Trace the full flow before claiming "done." We built the dispatcher with passing tests but hadn't connected it to the engine — no notification would have reached WhatsApp.
Don't rebuild what already works. WhatsApp delivery infrastructure has been operational since Session 117. We needed governance (safety guards), not plumbing (Twilio integration).
Reuse proven UX patterns. The magic link account-linking flow was the perfect template for auto-creating notification preferences — same detect→create→confirm pattern.
Severity routing is the gatekeeper. The difference between
info(web only) andwarning(WhatsApp/Slack) determines whether the Jarvis vision works or doesn't.Mongoose defaults don't backfill. New schema fields with
@Prop({ default: ... })only apply to newly created documents. Existing documents need undefined-safe code.
Architecture Overview
┌─────────────────────────────┐
│ Trigger Sources │
│ ┌────────────────────────┐ │
│ │ VittoEngineService │ │ ← Task complete/fail
│ │ ProactiveMonitorService │ │ ← Health checks
│ │ HeartbeatService │ │ ← 30-min daemon
│ │ QualityDropTrigger │ │ ← Hourly quality check
│ └──────────┬─────────────┘ │
└─────────────┼────────────────┘
│
▼
┌─────────────────────────────┐
│ ProactiveDispatcherService │ ← NEW in S210.4
│ ┌────────────────────────┐ │
│ │ 1. Master toggle │ │
│ │ 2. Per-trigger opt-out │ │
│ │ 3. Quiet hours │ │
│ │ 4. Frequency cap │ │
│ │ 5. Deduplication │ │
│ └──────────┬─────────────┘ │
│ │ │
│ ┌──────────▼─────────────┐ │
│ │ Severity-based routing │ │
│ │ critical → all channels │ │
│ │ warning → preferred │ │
│ │ info → web only │ │
│ └──────────┬─────────────┘ │
└─────────────┼────────────────┘
│
┌─────────────┼────────────────┐
│ ▼ │
│ Channel Resolution │
│ UserDossier.proactivePrefs │
│ + UserNotificationPrefs │
│ (preferredChannel + channelId)│
└─────────────┼────────────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ WhatsApp │ │ Slack │ │ Web │
│ (Twilio) │ │ (API) │ │ (SSE │
│ │ │ │ │ Toast) │
└──────────┘ └──────────┘ └──────────┘
│ │
│ ┌─────────────────────┐ │
└────▶│ User's Phone │◀──────────┘
│ (or browser) │
└─────────────────────┘
Document created: 2026-03-12 | Session: S210.4 | Author: Claude Code