S225.3 Vitto Chat Persistence Flow Analysis
Documentation for Sessions
S225.3: Vitto Chat Persistence/Flow Analysis
Session: 225.3
Focus: Fix Vitto stalling on page refresh — backend-driven persistence, socket-based real-time
Date: 2026-03-16
Executive Summary
Root cause (Certain): Vitto's responses are delivered only via SSE. When the page refreshes, the EventSource is destroyed. The backend continues processing and emits task.completed, but there is no consumer. The response is never persisted to MongoDB or pushed via Socket.IO. The user's message remains unanswered.
Your vision is correct: Responses (thinking, emotions, tool progress, final output) should be backend-owned. The backend should persist to MongoDB and emit via Socket.IO so reconnecting clients receive the response regardless of frontend state.
1. Critical Issues (High Confidence, Real Impact)
1.1 Session ID Never Passed to Backend — Certain
Location: libs/libs-ai/vitto-chat/src/lib/services/vitto-chat-engine.service.ts (lines 417–435)
The POST to /engine/message does not include sessionId. The backend has no way to know which chat session the message belongs to.
return this.http.post<{ success: boolean; taskId: string }>(
`${this.vittoApiUrl}/engine/message`,
{
message: content,
preferredModel: this.modelService.activeModel(),
spaceId: spaceId || undefined,
// sessionId is MISSING — backend cannot persist to the correct chat session
...
},
);
Impact: Even if the backend persisted on task.completed, it would not know where to store the response.
Fix: Add sessionId: this.persistence.activeSessionId() to the POST body. The backend controller and engine must accept and forward it.
1.2 Backend Does Not Persist to Chat Sessions — Certain
Location: APIs/backend-vitto-api/src/app/engine/vitto-engine.service.ts (lines 1010–1030)
On task.completed, the engine:
- Emits to
streamSubject(SSE) — frontend-only consumer - Persists to
agentSessions(vitto-api's own storage) — not chat_sessions in MongoDB - Does not call backend-ai-api to persist to chat sessions
// GAP 6: Emit task.completed so the frontend SSE subscriber can build the final message
streamSubject.next({ type: 'task.completed', ... });
// No call to BackendAiClientService to persist to chat_sessions
Impact: The response exists only in SSE and agent-sessions. If the frontend disconnects, it is lost for the chat UI.
Fix: When sessionId is provided, call BackendAiClientService to POST the assistant message to /sessions/${sessionId}/messages. The existing bridgeMessageToAiApi pattern can be reused or extended — it already POSTs to backend-ai-api and triggers SessionsService.appendMessages, which persists to MongoDB and emits via ChatGateway.emitNewMessages.
1.3 Reconnect Only Handles Module Generation — Certain
Location: vitto-chat-engine.service.ts (lines 968–992)
checkAndReconnectActiveGeneration(spaceId) only checks /module-generation/active/:spaceId. General conversational tasks are not tracked.
Impact: After a refresh during a normal chat (e.g. "tell me what you know about me"), there is no way to reconnect to the SSE stream or recover the response.
Fix options:
- Option A (simpler): Backend persists on
task.completed→ on reload,loadBackendSessionsfetches the session with the new message. No reconnect needed. - Option B (full real-time): Add
GET /engine/active-task?userId=&sessionId=returning{ taskId }if a task is in progress. Frontend resubscribes to SSE. Still requires backend persistence for the final message.
Recommendation: Option A is sufficient for the "never lose a response" guarantee. Option B adds live reconnection during generation.
1.4 Thinking/Emotions Are Frontend-Only — Certain
Location: vitto-chat-streaming.service.ts, vitto-chat-engine.service.ts
thinkingContext (e.g. "Understanding your requirements...", "Analyzing business domain...") is driven by SSE events. If the frontend disconnects, no client sees this state.
Impact: Minor for correctness — the final response is the critical piece. Thinking state is a UX enhancement.
Fix: Once backend persists and emits via Socket.IO, you can optionally push intermediate "thinking" updates as system/awareness messages. Lower priority than fixing the main flow.
2. Non-Critical Improvements (Pragmatic, Optional)
2.1 Extend bridgeMessageToAiApi or Add Dedicated Method
Current: BackendAiClientService.bridgeMessageToAiApi resolves session by by-space or uses channel-{userId}-{spaceKey}. It does not accept an explicit sessionId from the web chat.
Improvement: Add persistChatMessageToSession(sessionId, userId, messages) that POSTs directly to /sessions/${sessionId}/messages when sessionId is known. Reuse the same HTTP call and let backend-ai-api handle persistence + Socket.IO.
2.2 Tool Progress / Phase Updates via Socket.IO
Current: Tool progress (e.g. tool.starting, module-gen.phase-progress) is only in SSE. After reconnect, the user sees the final message but not the intermediate steps.
Improvement: Optionally persist key phase transitions (e.g. phase-start, phase-complete) as task messages in the session. This would require more backend changes and may not be worth it for typical short conversations.
2.3 Active Task Tracking for Reconnect
Current: No endpoint returns "active conversational task for user X / session Y."
Improvement: Store sessionId → taskId in the engine when sessionId is provided. Expose GET /engine/active-task?userId=&sessionId=. Frontend can poll on init and resubscribe to SSE if a task is in progress.
3. Nitpicks (Style, Naming, Minor)
checkAndReconnectActiveGenerationis a long name; considerreconnectModuleGenStreamfor clarity.- Console.log statements in
sendMessageViaVitto(e.g.[VittoChat] POST success...) could be routed throughLoggerServicefor consistency.
4. Implementation Plan (Recommended Order)
Phase 1: Backend Persistence (Fixes 1.1 + 1.2)
- Frontend: Add
sessionIdto POST/engine/messagebody. - Backend vitto.controller: Accept
sessionId?: stringin the body, pass tostartConversationalMessage. - Backend vitto-engine: In
runConversationalLoop, whensessionIdis provided andtask.completed:- Call
BackendAiClientServiceto POST assistant message to/sessions/${sessionId}/messages. - Use the same format as
bridgeMessageToAiApi(id, role, content, timestamp, metadata).
- Call
- Backend ai-api: No change —
POST /sessions/:id/messagesalready persists and emits viaChatGateway.emitNewMessages.
Result: On page refresh, loadBackendSessions will fetch the session with the new message. injectRealtimeMessages will also deliver it if the user has Socket.IO connected.
Phase 2: Frontend Reconnect (Optional, Fix 1.3)
- Backend: Add
GET /engine/active-task?userId=&sessionId=returning{ active: boolean, taskId?: string }. - Frontend: In
checkAndReconnectActiveGeneration(or a newcheckAndReconnectActiveChat), also call this endpoint for the active session. If active, resubscribe to the SSE stream.
5. Confidence Calibration
| Finding | Confidence | Basis |
|---|---|---|
| SessionId not passed | Certain | Code inspection — POST body has no sessionId |
| Backend doesn't persist to chat | Certain | vitto-engine only emits SSE + agentSessions |
| Reconnect only module-gen | Certain | checkAndReconnectActiveGeneration uses module-generation endpoint |
| bridgeMessageToAiApi exists | Certain | BackendAiClientService lines 674–738 |
| POST /sessions/:id/messages persists + emits | Certain | SessionsService.appendMessages → ChatGateway.emitNewMessages |
6. Gaps / Assumptions
- Assumption:
SessionsService.appendMessagesin backend-ai-api creates the session if it does not exist when appending messages. Verify insessions.service.tsandsessions.controller.ts. - Gap: The backend-ai-api
/sessions/:id/messagesendpoint may require the session to exist. If the frontend creates a session locally (IndexedDB) but has not yet synced it, the session might not exist on the backend. Mitigation: Frontend already callssyncService.appendMessagesfor user messages, which creates/updates the session. By the time the engine responds, the session should exist. - Gap:
bridgeMessageToAiApiusesby-spacefor session resolution when spaceId is present. For web chat we need explicit sessionId. A dedicated method or an optionalsessionIdoverride in the bridge would be cleaner.
7. References
docs/english/Plans/vitto-more-reliable-intelligence.plan.md— Task 1.6 documents the same gap (generation summary not persisted).APIs/backend-vitto-api/src/app/connectivity/backend-ai-client.service.ts—bridgeMessageToAiApi(lines 674–738).APIs/backend-ai-api/src/app/sessions/chat.gateway.ts—emitNewMessages,emitAwarenessMessage.libs/libs-ai/chat-sync/src/lib/services/chat-backend-sync.service.ts—appendMessagesflow.