Skip to main content

Architecture

System overview

Chat-in-Bio is a single-tenant application. One instance = one creator's bio page + chatbot. Multi-tenancy is handled externally (e.g., Kubernetes orchestration with a separate control plane).

┌─────────────────────────────────────────────────┐
│ Frontend │
│ Lit web components + A2UI renderer │
│ │
│ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │
│ │ bio-header│ │ link-tree│ │ chat-container │ │
│ └──────────┘ └──────────┘ │ ┌───────────┐ │ │
│ │ │ a2ui-root │ │ │
│ │ └───────────┘ │ │
│ └────────────────┘ │
└──────────────────┬──────────────────┬────────────┘
│ REST │ WebSocket
▼ ▼
┌─────────────────────────────────────────────────┐
│ Litestar Backend │
│ │
│ /api/bio ──────► BioController │
│ /api/admin/* ──► AdminControllers (CRUD) │
│ /ws/chat ──────► ChatWebSocketHandler │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ PydanticAI │ │
│ │ Agent │ │
│ └──────┬───────┘ │
│ │ tool calls │
│ ┌─────────┼─────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ links │ │ events │ │ faq │ ... │
│ │ tool │ │ tool │ │ tool │ │
│ └───┬────┘ └───┬────┘ └───┬────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────┐ │
│ │ SQLite + SQLAlchemy │ │
│ └──────────────────────────────┘ │
│ ┌──────────────────────────────┐ │
│ │ SurfaceManager (A2UI) │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────────────────┘

Data flow: chat message

  1. Frontend sends {"message": "text"} over WebSocket
  2. ChatWebSocketHandler receives it, resolves the chat session
  3. The user message is persisted to SQLite
  4. PydanticAI agent runs with the user's message + conversation history
  5. The LLM decides which tools to call (intent routing)
  6. Each tool queries the database and builds A2UI components via SurfaceManager
  7. Tools return a text summary to the LLM so it can compose a natural-language response
  8. After the agent completes, SurfaceManager.flush() returns A2UI protocol messages
  9. The response (text + A2UI) is sent back over WebSocket as JSON
  10. The assistant message is persisted to SQLite
  11. Frontend renders the text as a chat bubble and A2UI components inline

Why single-tenant?

  • Simpler data model — no tenant isolation concerns in every query
  • Self-hostable — anyone can run their own instance
  • Security — no risk of cross-tenant data leaks
  • Performance — no noisy-neighbor problems
  • Multi-tenancy is a deployment concern, handled at the infrastructure layer

Technology choices

ChoiceRationale
LitestarModern Python ASGI with native WebSocket, dependency injection, and guards
PydanticAIFramework-agnostic agent with typed tool dependencies and multi-provider support
A2UIDeclarative UI protocol — no code execution in chat, safe by design
LitLightweight web components, Shadow DOM for style isolation, signal-based reactivity
SQLiteZero-config, single-file database — perfect for single-tenant self-hosting
uvFast Python package manager with lockfile support