# Phase 1 — Deep Dive Implementation Plan

**Companion to:** Universal-Chatbot-Widget-Documentation.md
**Purpose:** Break each Phase 1 milestone into concrete, buildable steps in the order they should actually be implemented.

---

## How to use this document

Each milestone below is broken into numbered sub-steps. Build them **in order** within a milestone — later steps assume earlier ones exist. Milestones themselves should also mostly be built in order (M1 → M2 → M3 → M4), because M2's orchestrator is the thing M3 and M4 plug into.

---

## Full endpoint reference (all of Phase 1)

| # | Method | Endpoint | Introduced in | Purpose |
|---|---|---|---|---|
| 1 | POST | `/api/v1/tenant/register` | M1 | Site owner registers a website, receives a `siteId` |
| 2 | POST | `/api/v1/session/init` | M1 | Widget creates/resumes a visitor session; validates `siteId` + origin |
| 3 | POST | `/api/v1/message` | M1 (echo stub) → M2 (real orchestrator logic) | Single entry point for all chat messages; internally routes to rule engine, AI engine, or human relay based on session mode |
| 4 | POST | `/api/v1/tenant/{siteId}/context` | M3 | Site owner submits website content for RAG ingestion (manual paste/upload) |
| 5 | POST | `/api/v1/handoff/request` | M4 | Visitor requests a human agent; sets session mode to `human`, fires a ring event |
| 6 | GET | `/api/v1/conversation/{id}` | M2 | Fetch full message history for a conversation |
| 7 | GET | `/api/v1/admin/active-sessions?siteId=` | M2 | Snapshot of currently active sessions for a tenant (initial load for the admin dashboard) |
| 8 | WS | `/ws/session/{id}` | M2 (presence only) → M4 (also carries live chat) | Drives real-time presence (connect = active, disconnect = inactive); becomes the live message channel once a session is in human mode |
| 9 | WS | `/ws/agent` | M2 (presence updates) → M4 (also carries ring events) | Agent dashboard's live feed — active-session updates and human-handoff notifications |

**Total: 7 REST endpoints + 2 WebSocket endpoints.**

---

## Milestone 1: Widget shell + tenant registration

**Goal at the end of this milestone:** a working widget that can be embedded on a dummy test page, open/close, and exchange a hardcoded/echo message with the backend.

| Step | Task | Notes |
|---|---|---|
| 1.1 | Backend: `Tenant` model + `POST /api/v1/tenant/register` | Generates and returns `siteId`; store `domain`, `name`, `createdAt` |
| 1.2 | Backend: CORS middleware setup | Widget will be embedded on arbitrary domains — CORS must allow the registered tenant domains specifically, not `*` |
| 1.3 | Widget: build tooling | Use `esbuild` (fast, simple) to bundle vanilla JS into a single IIFE file — this is what site owners will `<script src>` |
| 1.4 | Widget: Shadow DOM container | On script load, create a `<div>` with `attachShadow({mode: "open"})` so host site CSS can't leak in or out |
| 1.5 | Widget: chat bubble UI | Floating button → click → chat panel opens (open/close state only, no messages yet) |
| 1.6 | Widget: read config from script tag | `data-site-id` attribute read via `document.currentScript.dataset.siteId` |
| 1.7 | Backend: `POST /api/v1/session/init` | Validates `siteId` + request origin against tenant's registered domain; creates a `Session` record; returns `sessionId` |
| 1.8 | Widget: call `/session/init` on load, store `sessionId` in `localStorage` | Reuse `sessionId` on page reload instead of creating a new one each time |
| 1.9 | Widget: message input + send (echo test only) | Wire up send button → `POST /api/v1/message` → display raw echo response, just to confirm the pipe works end-to-end |
| 1.10 | Test harness | A plain local HTML file with the widget script embedded — this is your manual test bed for every milestone going forward |

**Milestone 1 exit check:** widget loads on the test HTML page, opens/closes, and a typed message round-trips to the backend and back.

---

## Milestone 2: Orchestrator + rule engine + presence

**Goal at the end of this milestone:** messages route correctly based on session mode, rule-based replies work, and active sessions are visible in real time.

| Step | Task | Notes |
|---|---|---|
| 2.1 | Backend: `Message` model + save on every exchange | Every user/bot message persisted against `conversationId` |
| 2.2 | Backend: `Conversation` model, created alongside session | One conversation per session for Phase 1 (no multi-thread yet) |
| 2.3 | Backend: rule engine JSON schema | Design the shape first, e.g. `{ "trigger": ["hi","hello"], "reply": "Hello! How can I help?" }` — keep it flat for Phase 1, no complex branching trees yet |
| 2.4 | Backend: rule engine lookup function | Simple keyword/exact match against the JSON list for the tenant; returns match or `None` |
| 2.5 | Backend: `route_message()` orchestrator function | Core logic: check `session.mode` → rule lookup → auto-escalate to `ai` mode if no match (AI engine can be a stub returning a placeholder until M3) |
| 2.6 | Backend: wire `/api/v1/message` to call `route_message()` | This becomes the single real entry point widget talks to, replacing the M1 echo stub |
| 2.7 | Backend: `isLoggedIn`/`userId`/`userName`/`userEmail` fields on session | Widget passes `user` object (if provided by host site) at `/session/init` time |
| 2.8 | Backend: IP capture for anonymous sessions | Extract from `X-Forwarded-For` header (fallback to direct client IP if not behind a proxy) |
| 2.9 | Backend: `WS /ws/session/{id}` endpoint | Accepts connection, adds entry to in-memory `active_sessions` dict on connect, removes on disconnect |
| 2.10 | Widget: open WebSocket connection alongside REST calls | Runs in parallel to `/message` REST calls — WS here is for presence only in Phase 1, not chat transport yet |
| 2.11 | Backend: `WS /ws/agent` endpoint | Agent dashboard connects here; backend pushes presence add/remove events as they happen |
| 2.12 | Backend: `GET /api/v1/admin/active-sessions?siteId=` | Fallback/initial-load endpoint — returns current snapshot of `active_sessions` filtered by `siteId`, so the dashboard doesn't start empty before any WS events fire |
| 2.13 | Minimal agent dashboard page | Even a bare HTML/React page listing active sessions live is enough for Phase 1 testing — full UI polish is Phase 2 |

**Milestone 2 exit check:** typing a scripted keyword gets an instant rule-based reply; typing anything else gets a placeholder AI response; opening the widget on the test page shows up live on the agent dashboard page, and closing the tab removes it.

---

## Milestone 3: AI engine + RAG

**Goal at the end of this milestone:** unmatched messages get a real LLM answer, grounded in the tenant's website content where relevant.

| Step | Task | Notes |
|---|---|---|
| 3.1 | Backend: `POST /api/v1/tenant/{siteId}/context` (manual ingestion) | Site owner pastes/uploads text content for Phase 1 (no crawler yet — deferred to Phase 2) |
| 3.2 | Backend: chunking function | Split content into ~300-500 token chunks with slight overlap; store `sourceUrl` (or a label) per chunk |
| 3.3 | Backend: embedding generation | Call the embedding API (OpenAI `text-embedding-3-small` or equivalent) per chunk |
| 3.4 | Vector store setup | Pick one (Qdrant is a reasonable free/self-hostable default) and create a per-tenant-filterable collection |
| 3.5 | Backend: store chunk + embedding + `siteId` in vector store | Must be filterable by `siteId` at query time — this is a second place multi-tenancy isolation matters |
| 3.6 | Backend: retrieval function | On an AI-mode message, embed the query, do a top-k similarity search scoped to `siteId`, return matched chunks |
| 3.7 | Backend: prompt construction | System prompt + retrieved chunks as context + conversation history (last few turns) + current user message |
| 3.8 | Backend: LLM call wrapper | A single `call_llm(prompt)` function behind an interface, so the provider (OpenAI/Claude) can be swapped without touching orchestrator code |
| 3.9 | Backend: replace M2's AI stub with the real call | `route_message()` now calls retrieval + LLM wrapper instead of returning a placeholder |
| 3.10 | Backend: per-tenant usage guard | Track message count per tenant per day/month; hard-stop or degrade gracefully past the limit — protects against runaway LLM cost |
| 3.11 | Backend: LLM failure fallback | If the API call errors/times out, return a safe fallback message rather than letting the request hang or crash |

**Milestone 3 exit check:** asking a question that's covered in the ingested content returns an answer that reflects that content specifically, not a generic LLM response.

---

## Milestone 4: Human handoff

**Goal at the end of this milestone:** a visitor can request a human, an agent gets notified live, and the two can exchange live messages.

| Step | Task | Notes |
|---|---|---|
| 4.1 | Widget: "Talk to a human" button in chat UI | Visible at all times or only after AI mode — decide based on product preference; simplest is always-visible |
| 4.2 | Backend: `POST /api/v1/handoff/request` | Sets `session.mode = "human"`; creates a `RingEvent` (`type: human_requested`) |
| 4.3 | Backend: push ring event over `/ws/agent` | Agent dashboard receives it live, same channel already built in M2 |
| 4.4 | Agent dashboard: notification + accept action | Agent clicks to "join" — this associates `agentId` with the session |
| 4.5 | Backend: repurpose `/ws/session/{id}` for live chat | Once in human mode, this same socket (already open for presence since M2) now also carries real message payloads both directions |
| 4.6 | Widget: switch message rendering to use WS messages once in human mode | Instead of waiting for `/message` REST responses, listen on the socket for agent replies |
| 4.7 | Backend: persist human-mode messages too | Same `Message` model as rule/AI mode — conversation history should be continuous regardless of mode |
| 4.8 | Backend: session close / mode reset | Agent or visitor ends the human session → `mode` can reset to `rule`/`ai` if the visitor keeps chatting |
| 4.9 | (Optional Phase 1 fallback) Slack/Teams webhook bridge | If a full agent dashboard is too much for the Phase 1 timeline, an alternative is: `human_requested` posts into a Slack/Teams channel via webhook, and agent replies happen outside the product initially. Pick ONE of 4.3–4.8 (in-app) or this fallback — not both, to avoid building two paths |

**Milestone 4 exit check:** clicking "Talk to a human" on the test page results in a live notification on the agent dashboard, and messages typed on either side appear on the other in real time.

---

## Suggested build order across milestones (with dependency reasoning)

```
M1 (widget shell)  →  M2 (orchestrator + rules + presence)  →  M3 (AI + RAG)  →  M4 (handoff)
```

M2 must come before M3 and M4 because `route_message()` — the routing core — is what both the AI engine and the handoff flow plug into. Building M3 or M4 before M2 exists means building against a moving target.

## What to explicitly skip in Phase 1 (don't get pulled into these mid-build)

- Auto-crawling website content (manual paste only — section 3.1 of the main doc)
- Multi-agent queueing / ticket assignment logic
- Redis-based presence (in-memory is enough at this scale — section 3.3 of the main doc)
- Any UI polish on the agent dashboard beyond "it works and shows the data"
