2026 03 13 S216 Vitto Jarvis Wiring Design
Documentation for Plans
S216: Vitto Jarvis Wiring — Close Every Open Loop
Summary
Vitto has 21+ implementation plans and massive infrastructure, but the connections between systems are loose. The same issues recur across sessions because features get designed but not wired end-to-end. This session closes every verified gap with surgical, small changes (~400 lines across 9 existing files + 1 new helper).
Goal: Transform Vitto from "impressive parts, loose connections" into a fully wired Jarvis-like super-agent where every subsystem feeds into every other subsystem.
Non-goal: No new features, no new architecture, no new services. Only wiring what already exists.
Gap Inventory (Code-Verified)
Each gap was verified by reading the exact source line. No assumptions.
Gap 1: Episodic Memory Gate Excludes Channel Messages
- File:
outcome-feedback.service.ts:84-87 - Code: Line 85:
if (task.type === 'channel-message') return;AND Line 87:if (!task.phases.find(p => p.phase === 'ANALYZE')) return; - Impact: Two guards block channel messages: (1) blanket type check, (2) ANALYZE phase requirement (most channel messages skip the 4-phase pipeline). Every WhatsApp/Slack/Telegram conversation is invisible to episodic memory.
- Fix: Replace blanket skip with a quality gate: only skip if task had 0 tool calls AND output < 100 chars. Move the ANALYZE phase check to only apply to non-channel tasks. Substantive channel conversations create lightweight episodes.
Gap 2: Skill Composition is Stubbed
- File:
skill-composition.service.ts:138 - Code:
const output = { executed: true, skillName: nodeName, ...input }; - Impact: Composite skills (multi-step workflows) never actually execute. The skill system looks complete from the outside but does nothing.
- Fix: Wire
executeComposite()to callToolRegistryService.executeTool()for each node, passing output of one step as input to the next.
Gap 3: Skill Extraction Never Notifies Training
- File:
skill-extraction.service.ts:71 - Code: Creates skill via
skillManager.createSkill()but never callstrainingRegistry.notifySkillExtracted(skill) - Impact: Training plugins (4 registered, all working) never learn about newly extracted skills. The skills closed loop is broken at the last mile.
- Fix: Add
this.trainingRegistry.notifySkillExtracted(skill)after successful creation. ~1 line.
Gap 4: Duplicate Outcome Feedback
- File:
vitto-engine.service.ts:440ANDmind.service.ts:129 - Code:
this.aiClient.postExecutionOutcome({...})(engine) +this.outcomeFeedback.sendOutcome(task, lessonsLearned)(mind) - Impact: Every task completion sends TWO outcome reports to the learning pipeline. Inflates metrics, wastes bandwidth, may create duplicate learned patterns.
- Fix: Remove the direct
aiClient.postExecutionOutcome()call fromvitto-engine.service.ts:440. Themind.recordExecution()path at line 129 is the canonical one.
Gap 5: Channel Session History Ignores Channel Type
- File:
channel-prompt-builder.service.ts:253ANDmemory-persistence.service.ts:155 - Code:
_channelparam unused in both files. Messages stored without channel tag, history loaded without channel filter. - Impact: WhatsApp, Slack, and Web all load the same unified session history. Vitto can't distinguish "what did we discuss on WhatsApp" from "what happened on Slack." Channel-specific context is lost.
- Fix: (1) Tag messages with channel field when persisting in
memory-persistence.service.ts. (2) Filter by channel when loading inchannel-prompt-builder.service.ts. Untagged legacy messages included as fallback.
Gap 6: Space Brief Thin for New Spaces
- File:
space-intelligence.service.ts:191 - Code:
getSpaceBriefForPrompt()returns empty string whenprofile?.briefis falsy - Impact: New spaces get zero context in Vitto's system prompt. Vitto doesn't know what the space is about until
updateAfterGeneration()runs (which requires a module generation). - Fix: Add a bootstrap brief that pulls from the space's name, description, and module list when no full brief exists. Falls back gracefully.
Gap 7: Daily Digest is Plain Text
- File:
daily-digest.service.ts:299(formatDigestWhatsApp()) - Code: Returns
lines.join('\n')— flat markdown-style text - Impact: The daily digest on WhatsApp is a wall of text instead of an interactive card with action buttons. Users can't tap to drill into alerts or tasks.
- Fix: Use
RichMessageComposerServiceto create aCardMessagewith "View Alerts", "View Tasks" quick reply buttons. Falls back to current text for non-WhatsApp channels.
Gap 8: Alert Acknowledgment Handler Missing
- File:
channel-processing.service.ts— no handler for "Acknowledge" button payloads - Impact: When Vitto sends an alert card with an "Acknowledge" button, tapping it does nothing. The inbound button payload has no routing to
ProactiveMonitorService.resolveAlert(). - Fix: Add a button payload handler in the channel message processing flow that routes
action:acknowledge_alert:{alertId}toproactiveMonitor.resolveAlert().
Gap 9: Voice Note Delivery Stubbed
- File:
channel-processing.service.ts:763 - Code:
this.logger.log('TTS voice note generated — needs public URL hosting to send'); - Impact: Voice notes are generated (OpenAI TTS) but never delivered. The generated audio buffer is discarded.
- Fix: Upload to Cloudflare R2 (or S3), get public URL, send via
channelGateway.sendWhatsAppMedia(). ~20 lines.
Gap 10: Whisper Voice-In (Not Started — DEFERRED)
- Status: No code exists. Requires speech-to-text integration for incoming voice messages.
- Decision: DEFER to S217. Depends on audio ingestion pipeline which doesn't exist yet.
Architecture
Principle: Minimal Wiring, Maximum Impact
Every fix targets an existing file. No new services except one small helper for voice note upload. The total diff is estimated at ~400 lines across 9 files.
Fix Map
| Gap | File(s) to Modify | Lines Changed | Dependencies |
|---|---|---|---|
| 1: Episodic memory gate | outcome-feedback.service.ts |
~12 | None |
| 2: Skill composition | skill-composition.service.ts |
~30 | ToolRegistryService |
| 3: Skill extraction notify | skill-extraction.service.ts |
~3 | TrainingPluginRegistryService |
| 4: Duplicate outcome | vitto-engine.service.ts |
-8 (delete) | None |
| 5: Channel session filter | channel-prompt-builder.service.ts, memory-persistence.service.ts |
~25 | Session schema |
| 6: Space brief bootstrap | space-intelligence.service.ts |
~25 | Space model |
| 7: Rich daily digest | daily-digest.service.ts |
~40 | RichMessageComposerService, ChannelGatewayService |
| 8: Alert ack handler | channel-processing.service.ts |
~30 | ProactiveMonitorService |
| 9: Voice note delivery | channel-processing.service.ts + new voice-note-upload.helper.ts |
~40 | R2/S3 SDK |
Data Flow After Wiring
User Message (WhatsApp/Slack/Web)
→ RequestClassifier (routes to fast/balanced/powerful)
→ ContextAssembly (now includes: space brief [Gap 6], channel-filtered history [Gap 5])
→ AgentLoop (tools + LLM iterations)
→ Task Completion
→ MindService.recordExecution() [single path, Gap 4 fixed]
→ OutcomeFeedback (now includes channel messages, Gap 1 fixed)
→ Creates episodic memory
→ TrainingPluginRegistry.notifyTaskCompleted()
→ SkillExtraction (if pattern detected)
→ TrainingPluginRegistry.notifySkillExtracted() [Gap 3 fixed]
→ SkillComposition (now actually executes, Gap 2 fixed)
→ Response
→ RichMessageComposer (picks card/quick_reply/text)
→ ChannelAdapter (WhatsApp/Slack/Web)
→ Voice note delivery (if short + TTS enabled, Gap 9 fixed)
→ Proactive Alerts
→ Daily Digest (now rich cards, Gap 7 fixed)
→ Alert buttons → Acknowledge handler (Gap 8 fixed)
What This Does NOT Cover
- Gap 10: Whisper voice-in — Deferred to S217. No audio ingestion pipeline exists.
- WhatsApp Flows — Phase 3 of rich messaging. Blocked on WhatsApp Cloud API credentials.
- Cost tracking ($0.00 bug) — Separate investigation needed for CostTrackerService data source.
- New LLM model routing — Covered by S210.5 plan, not this session.
Testing Strategy
Each gap fix gets a focused unit test:
| Gap | Test Approach |
|---|---|
| 1 | Mock task with channel-message type + tool calls → verify episode created |
| 2 | Mock tool registry → verify executeComposite calls tools in sequence |
| 3 | Mock training registry → verify notifySkillExtracted called after createSkill |
| 4 | Verify only ONE outcome call per task completion (no duplicate) |
| 5 | Mock sessions with channel field → verify filtering works |
| 6 | Mock space with no brief but has name/modules → verify bootstrap brief generated |
| 7 | Verify digest returns RichMessage with buttons for WhatsApp |
| 8 | Mock button payload → verify resolveAlert called |
| 9 | Mock R2 upload → verify audio sent via channelGateway |
No E2E tests — these are all internal wiring fixes testable with unit mocks.
Risk Assessment
| Risk | Mitigation |
|---|---|
| Episodic memory bloat from channel messages | Quality gate: only substantive conversations (tool calls OR long output) |
| Skill composition infinite loop | Max 10 nodes per composite, 30s timeout per node |
| Voice note R2 upload failure | Fire-and-forget with existing error swallowing pattern |
| Rich digest breaks non-WhatsApp channels | Fallback text always present in RichMessage |
| Alert ack race condition | Idempotent resolveAlert — multiple acks are safe |
Success Criteria
After S216:
- Channel conversations appear in episodic memory (verifiable via
/vitto/introspection) - Extracted skills trigger training plugins (verifiable via training logs)
- Only ONE outcome per task in learning pipeline (verifiable via ai-agents DB query)
- WhatsApp users see interactive digest cards with buttons
- Alert "Acknowledge" buttons work end-to-end
- Voice notes are delivered (when TTS is enabled)
- New spaces get a bootstrap brief in Vitto's context
- Channel history is filtered by channel type