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:

  1. Emits to streamSubject (SSE) — frontend-only consumer
  2. Persists to agentSessions (vitto-api's own storage) — not chat_sessions in MongoDB
  3. 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:

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)


4. Implementation Plan (Recommended Order)

Phase 1: Backend Persistence (Fixes 1.1 + 1.2)

  1. Frontend: Add sessionId to POST /engine/message body.
  2. Backend vitto.controller: Accept sessionId?: string in the body, pass to startConversationalMessage.
  3. Backend vitto-engine: In runConversationalLoop, when sessionId is provided and task.completed:
    • Call BackendAiClientService to POST assistant message to /sessions/${sessionId}/messages.
    • Use the same format as bridgeMessageToAiApi (id, role, content, timestamp, metadata).
  4. Backend ai-api: No change — POST /sessions/:id/messages already persists and emits via ChatGateway.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)

  1. Backend: Add GET /engine/active-task?userId=&sessionId= returning { active: boolean, taskId?: string }.
  2. Frontend: In checkAndReconnectActiveGeneration (or a new checkAndReconnectActiveChat), 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


7. References