Norman Chat
Architecture

Architecture

Request Routing

Norman Chat acts as a smart proxy, routing chat requests to the appropriate backend service based on the selected agent mode:

User → Norman Chat /api/chat

         ├─ agentMode: "v0.1" → Norman Engine /api/complete (stream)

         └─ agentMode: "v1"  → Norman Agent /api/chat (stream)

Streaming Flow

  1. Client opens SSE connection to /api/chat
  2. Chat route forwards request to upstream service with stream: true
  3. Upstream responds with SSE events
  4. Chat route re-emits events to the client
  5. On completion, chat is persisted to MongoDB asynchronously

Non-Streaming Fallback

If the upstream returns JSON instead of SSE:

  1. Full response extracted from JSON body
  2. Emitted as a single token event for UI consistency
  3. done event sent with metadata

Data Model

Chat Document

interface ChatDocument {
  chatId: string;
  userId: string;
  title: string;          // Derived from first user message
  agentMode: 'v0.1' | 'v1';
  messages: {
    role: 'user' | 'assistant' | 'system';
    content: string;
    timestamp: string;
    toolsUsed?: string[];
  }[];
  createdAt: string;
  updatedAt: string;
}

Persistence Strategy

  • Upsert pattern: First message creates the chat document; subsequent messages append
  • Title derivation: First user message truncated to 60 chars becomes the chat title
  • Background persistence: MongoDB writes happen after the response stream completes (non-blocking)

Authentication

NextAuth.js configured with ShellApps as a custom OAuth provider:

Browser → NextAuth → ShellApps OAuth → Authorization Code → Token Exchange → JWT Session

All API routes verify the session before processing.


© 2026 Shell Technology. All rights reserved.