Files
Inboxintel/docs/discovery/08-technical-architecture.md
cesnimda 4ce2df0a2b
CI / backend (push) Successful in 52s
CI / frontend (push) Successful in 14s
Deploy Staging / deploy (push) Successful in 18s
CI / backend (pull_request) Successful in 52s
CI / frontend (pull_request) Successful in 15s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 55s
Security / secrets (pull_request) Successful in 4s
Security / dependencies (pull_request) Successful in 54s
docs: discovery blueprint + multi-provider design (#8)
2026-07-01 19:59:53 +02:00

6.9 KiB

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.

Update — multi-provider platform. This architecture now assumes the multi-provider design: 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:

  • 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.
  • 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<T> 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.