Skip to content

ja-der/miru

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Miru - AI-Powered Longitudinal Journaling

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.

Architecture

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
Loading

How it works

When you save an entry

  1. The write returns immediately so that nothing blocks the user
  2. A background job generates a 384d embedding and stores it in pgvector
  3. A fine-tuned DistilBERT classifier scores the entry across 10 emotion labels, mapped to valence + arousal
  4. NER extracts named entities (people, places, topics) and updates the temporal graph

When you chat with Miru

  1. Your message is embedded and used to query pgvector for the most relevant past entries
  2. Those entries are injected as context into the LLM prompt
  3. 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

  1. Three agents run over your full entry history: pattern detection, contradiction tracking, narrative arc analysis
  2. Insights are written to the database and surfaced in the companion chat when relevant

Stack

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

Setup

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 dev

Pull the local model if using Ollama:

ollama pull gemma3:4b       # i used gemma3:4b due to hardware constraints

Swap LLM_PROVIDER=openai or LLM_PROVIDER=anthropic in .env for cloud LLM inference.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors