Roadmap Home RU

Mirror · technical architecture

How Mirror is built

A person keeps a journal and speaks out, gets reports on their psychological state, and the system ranks registered therapists by how effective they can be for that specific person. Below — how that works under the hood.

Repository: github.com/akipushdev/mirror-bot-v2 — private. For access, message me on Telegram at @GoldPirate and I'll open it up.

System at a glance

The whole product is a single docker-compose application — one command to deploy. The API is client-agnostic: iOS/Android/web clients plug into the same use-case layer later without duplicating logic.

Telegram client aiogram bottransport only FastAPI backenduse-case layer LangChain / LangGraphorchestration Gemini APILLM provider
PostgreSQL 16Alembic on deploy Qdrantvector store / RAG Langfusetracing · cost · eval

docker-compose stack · reverse proxy with auto-TLS on a cloud VPS

Python 3.12 FastAPI aiogram PostgreSQL 16 SQLAlchemy (async) Alembic LangChain LangGraph Qdrant Langfuse Pydantic Gemini (dialogs · voice · image · embeddings) Docker Compose

Three independent contours

Latency-sensitive dialog, heavy async analytics and quality control are separated — each contour scales and fails independently.

1 · Live dialog

Real-time, one turn at a time. LangGraph state machine picks the conversational move; RAG grounds persona replies.

2 · Background analytics

Async jobs: daily/weekly reports, psychological portrait updates, multi-agent therapist matching.

3 · Evaluation & prompt ops

Offline experiments with LLM-as-judge and guard metrics; live A/B analytics per prompt version. No prompt reaches production on "looks good".

Contour 1 — dialog engine (LangGraph)

The dialog is a graph of states, not a single prompt. Every client message runs through the graph; the crisis check is a hard edge — a prompt can be talked around, a graph edge cannot.

client message crisis checkhard edge generate candidatesbest-of-N score pick move
↳ crisis detected: therapy line stops · help line number
listen mirror deepen ask

LangGraph state machine · gestalt interventions (e.g. empty-chair practice) attach as separate subgraphs behind the router

Best-of-N, cost-aware

The graph generates several candidate replies, scores them and picks the move instead of one-shot generation. Currently disabled via config to keep per-turn cost low for clients — the graph path stays and re-enables per persona.

RAG grounding (Qdrant)

For the Yalom persona a vector lookup runs before the model call — his published books are chunked and indexed, replies are grounded in real case-work examples. The same pipeline extends to registered therapists as their session history accumulates.

Typed LLM output

Every LLM response that feeds business logic is parsed into a Pydantic schema — never used as raw text. LangChain handles prompt templates and structured-output parsing inside graph nodes.

Provider abstraction

All model access goes through an LLMPort interface with three implementations: Gemini (production), Ollama (local experiments), stub (fast tests). Swapping providers is a config change, not a refactor.

Contour 2 — background analytics

Long AI analysis never blocks the live dialog. Reports run as LangChain chains; therapist matching is a multi-agent pipeline where each agent has its own prompt and typed output.

session history portrait analysisanxiety · tone · trends therapist rankingagainst the portrait personal explanationwhy this specialist PostgreSQL → client

multi-agent matching pipeline · daily/weekly reports run as separate LangChain chains over the same history

In-app economy

Every LLM call is metered against a per-user balance ("yaloms") — cost control and a path to monetization from day one.

Voice & image input

Voice messages and handwritten notes are recognized via Gemini multimodal and enter the same journal pipeline as text.

Contour 3 — evaluation & prompt ops

The differentiator isn't persona artwork — it's how the therapist talks. That is the hardest thing to verify, so it gets its own pipeline.

15 client personasmulti-day scripted context simulated sessionsLLM plays the client, real HTTP API LLM-as-judgevs reference from a stronger model guard gateshallucinations · safety winner → production

one full run = 302 complete 10-turn conversations · judge trusted only after 80%+ agreement with manual grading · a version that breaks a guard threshold is rejected regardless of other numbers

Architecture principle

One rule keeps the codebase honest as it grows toward multiple clients: business logic never lives in a transport handler.

Use-case layer (app/core/use_cases/) — LLM calls, DB writes, crisis detection, report generation, portrait updates, matching. Anything a future web client would also need.

Handler layer (app/bot/) — Telegram transport only: parse the event, call a use case, render the response. No LLM calls, no direct DB writes.

The test for any new feature: "would a web client need this logic too?" If yes — it goes in a use case.

Next in roadmap

Privacy is critical for therapy data — the next steps move inference and sensitive data fully under the product's control.

Self-hosted LLM + LoRA adapters planned

Deploy a local model (Ollama/vLLM) and plug per-persona LoRA adapters — a distinct communication style for each therapist persona, and client data stops leaving the perimeter. Local provider and experiments already in place.

PII obfuscation planned

Personal data stripped/masked before any external API call while a cloud LLM remains the production provider.

Encryption at rest planned

Sensitive dialog content encrypted in the database.

DSPy optimization loop planned

Replace the manual "analyze losers → generate new prompt → re-run" cycle with DSPy, using the calibrated LLM-judge as the metric. Plus judge-scores on sampled production traffic pushed to Langfuse.