# AI Chat Builder — Design Document

> **Date:** 2026-02-27
> **Status:** Approved
> **Relationship to existing wizard:** Coexists. Wizard stays as "Quick Build," this becomes "Build with AI" with a separate page and entry point.

---

## Overview

A conversational AI flow builder where the AI acts as a senior MSP engineer, conducting a multi-phase interview to collaboratively build troubleshooting or procedural flows. The user chats naturally; the AI demonstrates domain expertise, suggests diagnostic steps, challenges assumptions, and progressively constructs a TreeStructure that imports into the Tree Editor.

This is distinct from the existing wizard-based AI builder (scaffold → branch detail → assemble). The wizard is fast and automated; the chat builder is collaborative and guided.

---

## Key Decisions

| Decision | Choice | Rationale |
|----------|--------|-----------|
| Relationship to wizard | Keep both, separate entry points | Different use cases: quick generation vs. guided interview |
| SSE streaming | Not in MVP | Full-message responses with loading spinner. Add streaming later. |
| Database model | New `ai_chat_sessions` table | Different workflow/state shape than wizard's `ai_conversations` |
| Backend organization | Follow existing `core/ai_*.py` pattern | Consistent with codebase. No new `services/` directory. |
| Provider failover | Skip for MVP | `get_ai_provider()` selects configured provider. Failover later if needed. |

---

## Backend Data Model

### New Table: `ai_chat_sessions`

| Column | Type | Notes |
|--------|------|-------|
| `id` | UUID PK | `default=uuid4` |
| `user_id` | UUID FK → users | `ondelete=CASCADE` |
| `account_id` | UUID FK → accounts | `ondelete=CASCADE` |
| `status` | VARCHAR | `active`, `completed`, `abandoned` |
| `current_phase` | VARCHAR | `scoping`, `discovery`, `enrichment`, `review`, `generation` |
| `conversation_history` | JSONB | `[{role, content, timestamp}]` |
| `working_tree` | JSONB nullable | Progressive TreeStructure, updated as AI builds |
| `tree_metadata` | JSONB | `{name, description, category_id, tags}` extracted during conversation |
| `flow_type` | VARCHAR | `troubleshooting` or `procedural` |
| `provider_used` | VARCHAR nullable | Which AI provider served this session |
| `message_count` | INTEGER | Per-session exchange cap (prevents runaway token spend) |
| `total_input_tokens` | INTEGER | Running total for cost tracking |
| `total_output_tokens` | INTEGER | Running total |
| `generated_tree_id` | UUID FK → trees nullable | Set when user imports to editor |
| `expires_at` | DateTime(tz) | 24-hour TTL |
| `created_at` | DateTime(tz) | |
| `updated_at` | DateTime(tz) | |

### Quota Tracking

Reuses existing `ai_usage` table with new generation types:
- `chat_message` — each exchange, `counts_toward_quota=False`
- `chat_generate` — final tree generation, `counts_toward_quota=True`

**Required change:** Update `ai_quota_service.py` daily limit query to include chat builder types in the `generation_type.in_(...)` filter.

### Enforcement Layers

1. **Account monthly** — `ai_usage` with `counts_toward_quota=True` (existing, works across both builders)
2. **Account daily** — `ai_usage` by generation type (update filter to include chat types)
3. **Per-session** — `message_count` cap on session table (prevents runaway single-session spend)

---

## Backend API Endpoints

Router: `api/endpoints/ai_chat.py`, prefix `/ai/chat`, tag `ai-chat-builder`

| Method | Endpoint | Purpose |
|--------|----------|---------|
| `POST` | `/ai/chat/sessions` | Start session. Check quota, create session, return AI greeting. |
| `POST` | `/ai/chat/sessions/{id}/messages` | Send message, get AI response. Updates working_tree if AI includes tree update. |
| `GET` | `/ai/chat/sessions/{id}` | Get full session state. For resume after page reload. |
| `POST` | `/ai/chat/sessions/{id}/generate` | Final TreeStructure JSON generation. Validates, one retry on failure. |
| `POST` | `/ai/chat/sessions/{id}/import` | Create Tree record from generated tree. Returns tree ID. |
| `DELETE` | `/ai/chat/sessions/{id}` | Abandon session (soft — sets status to `abandoned`). |

**Rate limiting:** `@limiter.limit("10/minute")` on all mutating endpoints.

**Timeout handling for `/generate`:**
- AI provider timeout → 504 with clear "Generation timed out, please try again" message
- Failed attempts recorded with `counts_toward_quota=False`
- Session stays in `review` phase so user can retry
- Guard: if session already has a completed generation, return cached result (prevents duplicate costs from double-clicks)

**Ownership:** All endpoints verify `session.user_id == current_user.id`.

---

## Backend Service Layer

New file: `core/ai_chat_service.py`

### Functions

**`start_chat_session(flow_type, user_id, account_id, db) → (session, greeting_message)`**
- Creates session record
- Builds system prompt
- Sends initial prime to AI, returns greeting that demonstrates domain knowledge

**`send_message(session, user_message, db) → (ai_response, working_tree_update, phase)`**
- Appends user message to history
- Sends system prompt + full conversation history to AI
- Parses response: extracts conversational text, `[TREE_UPDATE]`, `[PHASE]`, `[METADATA]` markers
- Strips markers before storing the user-visible response
- Updates working_tree and phase if present
- Records usage via `record_ai_usage()`

**`generate_final_tree(session, db) → (tree_structure, metadata)`**
- Sends generation prompt with full conversation context
- Validates output (orphans, circular refs, missing terminals, valid next_node_id refs)
- One retry with correction prompt if validation fails
- Returns validated TreeStructure + metadata

### System Prompt Architecture

Four sections assembled programmatically:

1. **Role & Persona** — Senior MSP engineer, 15+ years, collegial and direct. Domain expertise: Windows Server, AD, Entra ID, M365, networking, virtualization, security, cloud.

2. **Schema Context** — TreeStructure node types with field definitions. Generated from Python dict to stay in sync with actual types.

3. **Interview Protocol** — Five-phase structure with behavioral rules:
   - One focused question at a time
   - Demonstrate domain knowledge immediately
   - Challenge assumptions constructively
   - Capture specific commands with exact syntax
   - Surface edge cases proactively
   - Explain WHY diagnostic order matters

4. **Response Format** — Structured output markers:
   - Conversational text (always present, this is what the user sees)
   - `[TREE_UPDATE]{...json...}[/TREE_UPDATE]` — only when tree structure changes
   - `[PHASE:phase_name]` — when transitioning phases
   - `[METADATA]{...json...}[/METADATA]` — when capturing tree name/description/tags

### Tree Update Rules (Critical for Prompt Quality)

The system prompt must be explicit about when to emit `[TREE_UPDATE]`:

- **Scoping phase:** Never. Still understanding the problem space.
- **Discovery phase:** Only when a concrete node is established — a decision with clear options, or an action with a specific command. Not during exploratory questions.
- **Enrichment phase:** When enriching existing nodes or adding edge case branches.
- **Review phase:** Only if user requests structural changes.
- **General rule:** "If you're asking a question, you're not updating the tree."

> **Known tuning area:** The tree update prompt rules will need iteration. First version will likely be too eager or too conservative. Test with Section 7 scenarios from the implementation guide and adjust.

---

## Frontend Architecture

### Page Layout

Split-panel inside existing AppLayout shell:

```
┌─────────────────────────────────────────────────────┐
│  Toolbar: [phase indicator]     [Import] [Abandon]  │
├──────────────────────────┬──────────────────────────┤
│     Chat Panel (60%)     │   Tree Preview (40%)     │
│                          │                          │
│  AI greeting message     │  Empty state until       │
│  User message            │  discovery phase         │
│  AI response             │                          │
│  ...                     │  TreePreviewPanel        │
│                          │  (reused component)      │
│  [Type a message...]     │                          │
└──────────────────────────┴──────────────────────────┘
```

Responsive: Below 1024px, stack vertically. Preview collapses to expandable panel.

### Route

`/ai/chat` → `AIChatBuilderPage` — under protected AppLayout children in `router.tsx`.

### Zustand Store: `store/aiChatStore.ts`

Plain store (no immer/temporal — conversation is append-only):

```typescript
interface AIChatState {
  sessionId: string | null
  status: 'idle' | 'active' | 'completed' | 'abandoned'
  currentPhase: InterviewPhase
  flowType: 'troubleshooting' | 'procedural' | null
  messages: ChatMessage[]
  isResponding: boolean
  workingTree: TreeStructure | null
  treeMetadata: { name?: string; description?: string; tags?: string[]; category_id?: string } | null
  generatedTree: TreeStructure | null
  isGenerating: boolean
  importedTreeId: string | null
  error: string | null

  startSession: (flowType) => Promise<void>
  sendMessage: (content: string) => Promise<void>
  generateTree: () => Promise<void>
  importToEditor: () => Promise<string>
  abandonSession: () => Promise<void>
  resumeSession: (sessionId: string) => Promise<void>
  reset: () => void
}
```

No localStorage persistence. Session state lives server-side. Page reload → `resumeSession()` rehydrates from API.

### New Components (`components/ai-chat/`)

| Component | Purpose |
|-----------|---------|
| `ChatPanel.tsx` | Scrollable message list + input. Auto-scroll on new messages. |
| `ChatMessage.tsx` | Message bubble. AI uses `MarkdownContent`, user messages plain. |
| `ChatInput.tsx` | Text input. Enter to send, Shift+Enter for newline. Disabled while AI responds. |
| `PhaseIndicator.tsx` | Horizontal breadcrumb: Scoping → Discovery → Enrichment → Review → Generate |
| `ChatToolbar.tsx` | Top bar with phase indicator + action buttons |
| `EmptyPreview.tsx` | Placeholder before tree data exists |

### Reused Components

- `TreePreviewPanel` — right panel tree visualization
- `MarkdownContent` — AI message rendering
- `PageLoader` / `Spinner` — loading states

### Entry Point

"Build with AI" button on `TreeLibraryPage.tsx` alongside existing Create menu. Lucide `Sparkles` icon. Routes to `/ai/chat`.

---

## Files

### New Files

| File | Purpose |
|------|---------|
| `backend/app/models/ai_chat_session.py` | SQLAlchemy model |
| `backend/app/schemas/ai_chat.py` | Pydantic schemas |
| `backend/app/core/ai_chat_service.py` | Conversation loop, system prompt, parsing |
| `backend/app/api/endpoints/ai_chat.py` | API endpoints |
| `backend/alembic/versions/XXX_add_ai_chat_sessions.py` | Migration |
| `frontend/src/types/ai-chat.ts` | TypeScript interfaces |
| `frontend/src/api/aiChat.ts` | API client module |
| `frontend/src/store/aiChatStore.ts` | Zustand store |
| `frontend/src/pages/AIChatBuilderPage.tsx` | Main page |
| `frontend/src/components/ai-chat/ChatPanel.tsx` | Chat message list |
| `frontend/src/components/ai-chat/ChatMessage.tsx` | Single message bubble |
| `frontend/src/components/ai-chat/ChatInput.tsx` | Text input + send |
| `frontend/src/components/ai-chat/PhaseIndicator.tsx` | Phase breadcrumb |
| `frontend/src/components/ai-chat/ChatToolbar.tsx` | Top bar |
| `frontend/src/components/ai-chat/EmptyPreview.tsx` | Preview placeholder |

### Modified Files

| File | Change |
|------|--------|
| `backend/app/api/router.py` | Include `ai_chat.router` |
| `backend/app/models/__init__.py` | Import `AIChatSession` |
| `backend/app/core/ai_quota_service.py` | Add chat builder types to daily limit query |
| `frontend/src/types/index.ts` | Export from `ai-chat.ts` |
| `frontend/src/api/index.ts` | Export `aiChat` module |
| `frontend/src/router.tsx` | Add `/ai/chat` route |
| `frontend/src/pages/TreeLibraryPage.tsx` | Add "Build with AI" button |

---

## Conflicts with Implementation Guide

| Guide Says | Resolution |
|-----------|------------|
| `services/ai/` directory structure | Follow existing `core/ai_*.py` pattern |
| `AIRouter` with automatic failover | Skip for MVP. Single configured provider. |
| SSE streaming | Skip for MVP. Full-message responses. |
| Separate `prompts/` and `validators/` modules | Prompt in service file. Validation reuses existing helpers. |
| `flow_builder_sessions` table name | `ai_chat_sessions` for naming consistency |
| Subscription tier table (Free: 2, Pro: 20, Team: unlimited) | Existing `plan_limits` handles this already |
| Caching for similar problem domains | Skip. Premature optimization. |
| Guide's Phase 4 polish items | Session resume included. Mobile is basic stacking. Full accessibility post-MVP. |

---

## Implementation Phases (from guide, adapted)

**Phase 1: Backend foundation** — Model, migration, service, endpoints, system prompt, validation. Milestone: can POST a message and get a domain-knowledgeable AI response.

**Phase 2: Frontend chat UI** — Page, store, components, routing, entry point. Milestone: full conversation flow with AI, no tree preview yet.

**Phase 3: Tree preview & import** — Wire up TreePreviewPanel with working_tree updates, generate action, import to editor. Milestone: end-to-end flow from conversation to tree in editor.

**Phase 4: Polish** — Error recovery, session resume on reload, prompt tuning with test scenarios, mobile layout.

---

## Test Scenarios (from guide Section 7)

Use after each phase to verify quality:
1. Azure AD Connect Sync Failures (identity/hybrid cloud)
2. Intermittent Site-to-Site VPN Drops (networking)
3. Exchange Online Mail Flow Issues (M365/email)
4. Slow Computer Troubleshooting (Tier 1 desktop support)

---

## Open Items / Known Tuning Areas

- System prompt `[TREE_UPDATE]` emission rules will need iteration with real conversations
- System prompt persona/tone will need tuning to feel like a colleague, not a chatbot
- Final generation prompt may need a more capable model than conversation phase
- Message count cap per session needs a specific number (guide suggests 25 for paid tiers, 10 for free)
