In Japanese, miru (見る) means to observe or to see. Most journaling apps are "write-and-forget." I wanted Miru to be a friendly-companion to help you "write-and-reflect", using AI to surface patterns in your thoughts, moods, and behaviors that are often invisible to you.
graph TD
%% Shared Storage Nodes
VectorDB[("pgvector (Vector Store)")]
RelDB[("PostgreSQL (Relational)")]
%% Reflections & Intelligence Flow
subgraph ReflectionFlow ["1. Reflections"]
Editor["Journal Editor"] -->|Save Entry| API_E["API (FastAPI)"]
API_E -->|Background| Worker["Worker"]
Worker -->|BGE| Embed["Vectorization"]
Worker -->|DistilBERT| Class["Emotion Analysis"]
end
%% AI Companion & RAG Flow
subgraph ChatFlow ["2. AI Companion & RAG Flow"]
Companion["AI Chat"] -->|Message| API_C["API (FastAPI)"]
API_C -->|Contextualize| RAG["RAG Engine"]
RAG -->|Augment| LLM["LLM Service"]
LLM -->|Stream| Companion
end
%% Analytics & Visualization Flow
subgraph AnalyticsFlow ["3. Analytics & Visualization"]
Dash["Dashboard"] -->|Fetch Stats| API_A["API (FastAPI)"]
end
%% Shared Resource Connections
Embed -->|Index| VectorDB
Class -->|Persist| RelDB
RAG -->|Semantic Search| VectorDB
API_A -->|Query Trends| RelDB
RelDB -->|Aggregate| Dash
When you save an entry
- The write returns immediately so that nothing blocks the user
- A background job generates a 384d embedding and stores it in pgvector
- A fine-tuned DistilBERT classifier scores the entry across 10 emotion labels, mapped to valence + arousal
- NER extracts named entities (people, places, topics) and updates the temporal graph
When you chat with Miru
- Your message is embedded and used to query pgvector for the most relevant past entries
- Those entries are injected as context into the LLM prompt
- The response streams back via SSE, this way, the companion can reference something you wrote months ago without you prompting it
Nightly, while you're not using it
- Three agents run over your full entry history: pattern detection, contradiction tracking, narrative arc analysis
- Insights are written to the database and surfaced in the companion chat when relevant
| Next.js 15 + FastAPI | My preferred front-end framework + async Python backend |
| SQLModel | Combines SQLAlchemy + Pydantic v2 into one model definition to eliminate the duplicate ORM class pattern |
| PostgreSQL + pgvector | Let's me use one db instance to store embeddings. No need for seperate vector db |
| DistilBERT (fine-tuned) | For multi-label valence/arousal emotion scores |
| BAAI/bge-small-en-v1.5 | Embedding model is fast, powerful and small enough for my hardware |
| Ollama (gemma3:4b) | Local LLM for dev, can swap to cloud LLM for prod |
| APScheduler | For async cron jobs |
Prerequisites: Docker, Python 3.11+, Node 18+, Ollama/LLM API Key
# 1. start postgres + pgvector db
docker-compose up -d
# 2. flask backend
cd server
pip install -r requirements.txt
cp .env.example .env # set LLM_PROVIDER, DB URL, API keys
alembic upgrade head # run migrations
python -m app.main
# 3. next.js frontend
cd client
npm install
npm run devPull the local model if using Ollama:
ollama pull gemma3:4b # i used gemma3:4b due to hardware constraintsSwap LLM_PROVIDER=openai or LLM_PROVIDER=anthropic in .env for cloud LLM inference.