# 08 — Technical Architecture (Phase 7) How the redesign is built on the **existing Clean Architecture** — extending seams, not rewriting. Everything here preserves the dependency rule `Api → Infrastructure → Application → Domain` and the security posture from [01](01-architecture-review.md). > **Update — multi-provider platform.** This architecture now assumes the > [multi-provider design](multi-provider/README.md): the Gmail-centric `Email`/`Sender` > model generalises to **account-scoped, provider-normalised** entities behind > `IEmailProvider`; the app becomes **small-team multi-user** (Admin/Member) with > **OAuth-as-login**, settings, and feature flags; and **AI gating is feature-flag-driven** > (`ai.enabled` system flag → user pref). See that folder for the provider abstraction, > DB schema, security model, and admin/settings design; this doc remains the search/AI/ > caching/indexing reference they build on. ## Overall ``` React SPA (v2 shell) │ REST (+ SSE for streaming AI/search) Api ────────────────────────────────────────────── Application: IInboxAi · ISearchService · SearchIntentParser · DTOs Infrastructure: ├ Search: SearchService (structured + lexical rank + hybrid) ├ AI: IAiProvider / IEmbeddingProvider / model router / VRAM guard ├ Jobs: EmbeddingBackfill · AiEnrichment · IndexMaintenance (IHostedService + Channel queue) ├ Gmail/Sync/Cleanup/Analytics (existing) └ Persistence: EF Core + Npgsql (+ pgvector) Domain: entities/enums (+ Embedding, Entities, ThreadSummary, SavedSearch) External (optional): Ollama (local, own container) · cloud AI (opt-in only) ``` ## Search architecture The 4-layer engine from [05](05-search-redesign.md): - **Structured** (SQL WHERE) + **Lexical** (`websearch_to_tsquery` + `ts_rank_cd`, weighted multi-field `tsvector`, `pg_trgm` fuzzy fallback) — **always on**. - **Semantic** (pgvector, HNSW) + **AI-assisted** (intent parse, RAG) — **optional**. - **Hybrid fusion (RRF)** merges lexical+semantic; boosts recency/sender/unread. - **Keyset pagination** replaces offset; score-ordered top-N for ranked queries. - `SearchIntentParser` (evolves `GmailQueryParser`): operators → rules-NL → optional LLM. ## AI architecture - Provider interfaces + `IInboxAi` facade + **model router** (task→model via config) + **prompt templates** (versioned files) + **VRAM guard** (serialises heavy vision jobs behind unloading the 7B; embeddings stay hot). See [06](06-ai-strategy.md). - **Streaming** via SSE for summaries/replies/RAG answers (perceived speed). - **Bounded**: per-call timeout, `CancellationToken`, Polly fallback to Null path. ## Plugin / modularity architecture - **Analyzer pipeline:** email enrichment is a set of `IEmailAnalyzer` plugins (classifier, entity-extractor, summariser, phishing, dedup). Each declares required capabilities (`Chat`/`Embeddings`/none) and is **skipped gracefully** if unavailable. New AI features = new analyzers; no core changes. - **Search providers** implement a common `ISearchLayer` so semantic/AI layers plug in. - **AI providers** already pluggable (`IAiProvider`); adding a provider = one class + config. - This is the "modular AI" requirement realised structurally. ## Caching - **Embeddings & summaries**: persisted in Postgres (compute once, invalidate on new msg). - **Search suggestions / recent**: cached per-user (memory + DB); debounced live search. - **Query results**: short-TTL cache for identical repeated queries; ETag on read endpoints. - **Model warmth**: Ollama `keep_alive` keeps the 7B hot between calls. ## Indexing | Index | Column | Purpose | |-------|--------|---------| | GIN | `SearchVector` (weighted A/B/C/D) | Full-text | | GIN `pg_trgm` | sender addr/name, subject | Fuzzy / substring (fixes non-sargable `.Contains`) | | HNSW | `Email.Embedding` (pgvector) | Semantic k-NN | | btree | `(UserId, SentAtUtc)`, `(UserId, ThreadId)` | Keyset pagination, threading | - Built incrementally on sync; **EmbeddingBackfillWorker** batch-fills history (VRAM-aware, low priority). ## Background jobs - Keep the existing `IHostedService` worker pattern; add a **`Channel` in-process queue** with a bounded concurrency worker for AI enrichment (summaries/entities/embeddings on new mail). Idempotent, resumable, backpressured. (Upgrade path: durable queue if multi-node.) - Jobs: `GmailSyncWorker` (exists), `DigestWorker` (exists), `EmbeddingBackfillWorker`, `AiEnrichmentWorker`, `IndexMaintenanceWorker`. ## Database schema improvements - **Widen** `Email.SearchVector` (subject/sender/filename/labels, weighted). - **Add**: `Email.Embedding vector(768)`; tables `EmailEntities`, `ThreadSummary`, `SavedSearch`, `SearchHistory`, `AiJob` (status/retry), `SenderImportance` (materialised). - **Fix debt**: resolve the `Email↔EmailLabel` global-query-filter warning (optional nav or matching filters); make FTS **language-aware** (detect → per-language config) instead of hardcoded English. - **Scale**: consider per-`UserId` list partitioning of `Emails` if single users exceed ~1M rows. ## Scalability - Single-user/self-host is the primary shape → vertical scaling + good indexes is enough. - Connection pooling (Npgsql), keyset pagination, top-N-by-score, batched embeddings. - **Ollama is the throughput bottleneck** → serialise/queue AI, cache aggressively, prefer the small/embedding models for high-volume paths. - Multi-tenant future: read replicas, per-user partitioning, durable job queue. ## Deployment - Extend the Compose stack (from the CI/CD we built): add an **optional `ollama` service** (profile `ai`) and a **pgvector-enabled Postgres image**. AI-off deployments omit the profile entirely. Dev→staging auto-deploy already proven; prod is tag-gated. ## Offline support - Data is **already local** (Postgres on the user's machine) — the product is local-first by nature. SPA: service-worker cache for shell + last-viewed mail (read offline); queue mutations (label/cleanup) to replay on reconnect. AI is inherently offline (Ollama local). ## Security model - Retain: OAuth **read-only** scope, encrypted refresh tokens (Data Protection), **IDOR global query filters**, SSRF egress guard, non-root containers, confirmed+previewed destructive actions. - **New AI concerns:** - **Prompt injection** — email content is untrusted input to the LLM. Treat model output as **advisory only**; never let it trigger actions directly; sanitise/format; the LLM can *suggest* but a human/rule confirms (aligns with "AI never acts"). - **Attachment/vision** — sandbox parsing; size/type limits; never execute. - **Local-only by default** — cloud provider is explicit opt-in with per-feature consent and egress logging; the SSRF guard already constrains outbound calls. - Secrets stay in `deploy/.env` / Actions secrets (never in git) — as established.