# 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

**Reprioritization note:** the build order has changed from the original plan. AI/rule-engine chat (originally Milestone 3) is now **deferred**. The priority is: get a live visitor-tracking + human-agent-handoff system working end to end first (a usable product on its own), then layer AI/rule chat on top of it afterward. Milestone numbers below reflect the new order.

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), because M2's presence/session layer is what M3's handoff plugs into. M4 (rule engine + AI + RAG) comes after M3 is working, not before.

---

## 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) → M4 (real orchestrator logic, deferred) | 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` | M4 (deferred) | Site owner submits website content for RAG ingestion (manual paste/upload) |
| 5 | POST | `/api/v1/handoff/request` | M3 | Visitor requests a human agent directly; sets session mode to `human`, fires a ring event |
| 6 | POST | `/api/v1/handoff/accept` | M3 | The (single) agent accepts the request; stops the ring |
| 7 | POST | `/api/v1/handoff/end` | M3 | Either party ends the live chat |
| 8 | GET | `/api/v1/conversation/{id}` | M2 | Fetch full message history for a conversation |
| 9 | GET | `/api/v1/admin/active-sessions?siteId=` | M2 | Snapshot of currently active sessions for a tenant, with available visitor details — initial load for the admin dashboard |
| 10 | WS | `/ws/session/{id}` | M2 (presence only) → M3 (also carries live chat once accepted) | Drives real-time presence (connect = active, disconnect = inactive); becomes the live message channel once a session is accepted by the agent |
| 11 | WS | `/ws/agent` | M2 (presence updates) → M3 (also carries ring events) | Agent dashboard's live feed — active-session updates and human-handoff notifications |

**Total: 9 REST endpoints + 2 WebSocket endpoints** for the current simplified build push (M1–M3, single agent, no auth). Multi-agent (`claim` instead of `accept`) and agent auth (`register`/`login`) get added as a later layer — M4 (deferred) adds 2 more REST endpoints for AI/RAG when picked up.

---

## 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: Session tracking + real-time presence + admin dashboard

**Current immediate scope (simplified):** agent login/auth is deferred for now — the dashboard is open-access and a single agent is assumed. Full multi-agent auth (steps 2.6 in the original plan) gets added as a later layer once this simplified version works end to end.

**Goal at the end of this milestone:** every visitor session is tracked, visible live on an admin dashboard with available details (IP, login identity if present, connect time). No rule engine or AI yet — messages sent in this milestone are still just echoed (from M1), since the point of M2 is presence and visibility, not chat intelligence.

| Step | Task | Notes |
|---|---|---|
| 2.1 | Backend: `Message` model + save on every exchange | Every user/bot/agent 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: `isLoggedIn`/`userId`/`userName`/`userEmail` fields on session | Widget passes `user` object (if provided by host site) at `/session/init` time — **done (M2.3)** |
| 2.4 | Backend: IP capture for anonymous sessions | Extract from `X-Forwarded-For` header (fallback to direct client IP if not behind a proxy) — **done (M2.3)** |
| 2.5 | *(Deferred)* IP → country lookup | Nice-to-have detail on the active-user list; add once the core presence flow is working, not required for the current build push |
| 2.6 | *(Deferred)* `Agent` model + register/login + password hashing + tokens | Full multi-agent auth — build this after the single-agent flow (M2–M3 as currently scoped) is working |
| 2.7 | Backend: `WS /ws/agent` endpoint | Open access for now (no auth check yet); backend pushes presence add/remove events as they happen — **next up (M2.4)** |
| 2.8 | Backend: `GET /api/v1/admin/active-sessions?siteId=` | Snapshot of `active_sessions` (IP, login identity if present, connected-since time) filtered by `siteId` — **next up (M2.4)** |
| 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 this milestone |
| 2.11 | Minimal agent dashboard page | No login screen for now — a live list showing each active visitor's available details |

**Milestone 2 exit check:** opening the widget on the test page shows up live on the dashboard with correct available details, and closing the tab removes it in real time.

---

## Milestone 3: Human handoff with multiple agents

**Current immediate scope (simplified):** single agent only — no claim-race logic needed since there's nobody to race against yet. Multi-agent (simultaneous ring to several agents, first-come claim) is deferred as a later layer once this works.

**Goal at the end of this milestone:** a visitor can request a human, the (single) agent gets notified with a repeating visual+sound ring until they accept, then agent and visitor exchange live messages, and the visitor can end the chat.

| Step | Task | Notes |
|---|---|---|
| 3.1 | Widget: "Talk to our agent" button in chat UI | Always visible for now — no AI/rule step in front of it in this phase |
| 3.2 | Backend: `POST /api/v1/handoff/request` | Sets `session.mode = "human"`; creates a `RingEvent` (`type: human_requested`) |
| 3.3 | Backend: push ring event over `/ws/agent` | Same channel built in M2.4 |
| 3.4 | Agent dashboard: repeating visual + sound alert | Card blinks/pulses and a sound plays on an interval, tied to the still-unaccepted `RingEvent` — stops the moment it's accepted |
| 3.5 | Backend: `POST /api/v1/handoff/accept` | Marks the `RingEvent` accepted, sets `session.mode` to reflect an active human chat, stops the ring |
| 3.6 | Backend: repurpose `/ws/session/{id}` for live chat | Once accepted, this same socket (already open for presence since M2) now carries real message payloads both directions |
| 3.7 | 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 |
| 3.8 | Backend: persist human-mode messages | Same `Message` model from M2 — conversation history should be continuous |
| 3.9 | Minimal agent-side chat UI | Accepted conversation opens a simple message thread for the agent to reply in |
| 3.10 | Widget: "End chat" button, visible to the user once in human mode | Calls `POST /api/v1/handoff/end` |
| 3.11 | Backend: `POST /api/v1/handoff/end` | Closes the conversation, resets `session.mode` |

**Milestone 3 exit check:** clicking "Talk to our agent" rings the agent dashboard (visual + sound) until accepted; accepting connects agent and visitor live; messages exchange in real time; the user can end the chat.

**Deferred for later (once single-agent flow works):** agent login/auth, multiple simultaneous agents with first-come claim, idle-time tracking during chat, IP-to-country resolution.

---

## Milestone 4 (deferred): Rule engine + AI + RAG

**Status: not part of the current build push.** This layer gets added once Milestones 1–3 are working end to end as a standalone product. Kept here for reference so the plan isn't lost, not as a near-term to-do.

| Step | Task | Notes |
|---|---|---|
| 4.1 | Backend: `POST /api/v1/tenant/{siteId}/context` (manual ingestion) | Site owner pastes/uploads text content (no crawler yet — deferred to Phase 2) |
| 4.2 | Backend: chunking function | Split content into ~300-500 token chunks with slight overlap; store `sourceUrl` (or a label) per chunk |
| 4.3 | Backend: embedding generation | Call the embedding API (OpenAI `text-embedding-3-small` or equivalent) per chunk |
| 4.4 | Vector store setup | Pick one (Qdrant is a reasonable free/self-hostable default) and create a per-tenant-filterable collection |
| 4.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 |
| 4.6 | Backend: retrieval function | On an AI-mode message, embed the query, do a top-k similarity search scoped to `siteId`, return matched chunks |
| 4.7 | Backend: rule engine (JSON-driven, for common queries before falling to AI) | Design as a flat `{ "trigger": [...], "reply": "..." }` list; keep simple, no branching trees |
| 4.8 | Backend: `route_message()` orchestrator function | Check rule matches first, fall back to AI/RAG, escalate to human if requested — this is where M1–M3's separate flows unify into one entry point |
| 4.9 | Backend: prompt construction | System prompt + retrieved chunks as context + conversation history (last few turns) + current user message |
| 4.10 | 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 |
| 4.11 | 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 |
| 4.12 | 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 4 exit check (deferred):** asking a question that's covered in the ingested content returns an answer that reflects that content specifically, not a generic LLM response.

---

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

```
M1 (widget shell)  →  M2 (presence + admin dashboard)  →  M3 (human handoff, multi-agent)  →  M4 (rule engine + AI + RAG, deferred)
```

M2 must come before M3 because the presence/WebSocket infrastructure M2 builds (`/ws/session/{id}`, `/ws/agent`) is exactly what M3 repurposes for live handoff chat and ring events. M4 comes last — it's not on the current build path, but when picked up, it plugs into the same `/api/v1/message` entry point M1 stubbed out.

## What to explicitly skip in the current build push (don't get pulled into these)

- Rule engine, AI/LLM chat, RAG — all of Milestone 4, deferred entirely for now
- Agent login/auth system — single agent, open-access dashboard for the current build push
- Multi-agent claim-race logic and auto-assignment — single agent only for now
- IP-to-country resolution and idle-time tracking — nice-to-have detail layers, add once the core flow works
- Auto-crawling website content (this belongs to the deferred M4 anyway)
- 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"