A privacy-first personal journaling and memory system with AI-powered reflection capabilities
Features • Architecture • Quick Start • Deployment • API Documentation
- Overview
- Features
- Architecture
- Technology Stack
- Project Structure
- Quick Start
- Configuration
- API Documentation
- Database Schema
- Deployment
- Security
- Development
- Testing
- Troubleshooting
- Contributing
- License
LifeMemory AI is a production-grade personal journaling system that combines traditional journaling with AI-powered memory retrieval and pattern analysis. Built with privacy-first principles, it uses advanced RAG (Retrieval-Augmented Generation) techniques to help users reflect on their experiences, identify patterns, and gain insights from their journal entries.
- 🔒 Privacy-First: Strict user isolation, no cross-user data access, no training on user data
- 🧠 Evidence-Grounded: All AI responses are based solely on retrieved journal entries
- ⏰ Temporal Intelligence: Time, recency, and emotional context matter in retrieval
- 🔄 Agent-Based Reasoning: Multi-node LangGraph workflow for sophisticated query processing
- 🚀 Production-Ready: FastAPI, Docker, comprehensive error handling, MLOps integration
- 📝 Daily Journaling: One entry per day with mood tracking
- 🔍 Semantic Search: Find entries by meaning, not just keywords
- 🤖 AI Memory Assistant: Ask questions about your journal entries
- 📊 Pattern Detection: Identify trends in emotions, productivity, and habits
- 📅 Calendar View: Visual calendar showing days with entries
- 🎨 Mood Tracking: Track emotional states with emoji-based moods
- 🔐 Secure Authentication: Supabase Auth with JWT verification
- Intent Classification: Automatically classifies queries (reflection, pattern, recall, etc.)
- Hybrid Retrieval: Combines vector similarity, temporal weighting, and mood filtering
- Temporal Pattern Analysis: Analyzes patterns across time periods
- Evidence-Based Answers: All responses grounded in retrieved journal entries
- Multi-LLM Fallback: Automatic failover between OpenAI, Gemini, and Groq
- Vector Embeddings: pgvector for efficient similarity search
- Row Level Security: Database-level user isolation
- Async Processing: Non-blocking embedding generation
- Structured Logging: Request tracing and observability
- Health Checks: Production-ready monitoring
- CORS Support: Secure cross-origin requests
┌─────────────────────────────────────────────────────────────────┐
│ User Interface │
│ (Next.js Frontend - Vercel) │
└────────────────────────────┬────────────────────────────────────┘
│ HTTPS + JWT
▼
┌─────────────────────────────────────────────────────────────────┐
│ FastAPI Backend (Render) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ API Layer │ │ Auth Layer │ │ LangGraph Pipeline │ │
│ │ │ │ │ │ │ │
│ │ • /journal │ │ • JWT Verify │ │ 1. Intent Classifier │ │
│ │ • /ask │ │ • User ID │ │ 2. Memory Retriever │ │
│ │ • /insights │ │ • RLS Check │ │ 3. Pattern Analyzer │ │
│ │ • /health │ │ │ │ 4. Answer Synthesizer│ │
│ └──────┬───────┘ └──────┬───────┘ └──────────┬───────────┘ │
│ │ │ │ │
│ └─────────────────┴─────────────────────┘ │
│ │ │
│ ┌───────────────────┴───────────────────┐ │
│ │ │ │
│ ┌──────▼──────┐ ┌──────────▼──────┐ │
│ │ Retrieval │ │ Embeddings │ │
│ │ System │ │ Service │ │
│ │ │ │ │ │
│ │ • Hybrid │ │ • OpenAI │ │
│ │ • Temporal │ │ • Cohere │ │
│ │ • Mood │ │ • Async Gen │ │
│ └──────┬──────┘ └──────────┬──────┘ │
└─────────┼───────────────────────────────────────┼───────────────┘
│ │
│ PostgreSQL + pgvector │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Supabase PostgreSQL Database │
│ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ journals │ │ journal_embeddings │ │
│ │ │ │ │ │
│ │ • id │ │ • journal_id (FK) │ │
│ │ • user_id │ │ • embedding (vector) │ │
│ │ • entry_date │ │ • metadata (jsonb) │ │
│ │ • content │ │ • entry_date │ │
│ │ • mood │ │ │ │
│ │ • created_at │ │ │ │
│ │ • updated_at │ │ │ │
│ └──────────────────┘ └──────────────────────┘ │
│ │
│ Row Level Security (RLS) enforced on all tables │
└──────────────────────────────────────────────────────────────┘
User Input → Frontend → POST /journal/save
↓
Backend validates JWT → Extract user_id
↓
UPSERT to journals table (ON CONFLICT DO UPDATE)
↓
Background Task: Generate Embedding
↓
Delete old embeddings for (user_id, entry_date)
↓
Generate embedding (OpenAI/Cohere)
↓
Store in journal_embeddings table
↓
Return success to user
User Question → POST /ask
↓
JWT Verification → Extract user_id
↓
┌─────────────────────────────────────────┐
│ LangGraph Agent Pipeline │
├─────────────────────────────────────────┤
│ │
│ 1. Intent Classifier │
│ → Classify: reflection/pattern/ │
│ recall/temporal_comparison/advice│
│ │
│ 2. Memory Retriever │
│ → Extract temporal filters │
│ → Extract mood filters │
│ → Hybrid retrieval (vector + │
│ temporal + mood weighting) │
│ │
│ 3. Evidence Safety Check │
│ → Validate minimum evidence │
│ → Check relevance scores │
│ │
│ 4. Temporal Pattern Analyzer │
│ → Analyze time-of-day patterns │
│ → Analyze day-of-week patterns │
│ → Analyze mood distribution │
│ │
│ 5. Reflection Synthesizer │
│ → Build context from entries │
│ → Generate evidence-based answer │
│ → Return answer + evidence │
│ │
└─────────────────────────────────────────┘
↓
Return JSON: {answer, evidence, intent, ...}
backend/
├── api/ # FastAPI endpoints
│ ├── journal.py # Journal CRUD operations
│ ├── ask.py # Memory query endpoint
│ ├── insights.py # Statistics and insights
│ └── health.py # Health check
│
├── auth/ # Authentication
│ └── supabase.py # JWT verification, OIDC discovery
│
├── config/ # Configuration
│ └── settings.py # Centralized settings (pydantic)
│
├── database/ # Database layer
│ ├── connection.py # Connection pooling (asyncpg)
│ └── schema.sql # Database schema
│
├── embeddings/ # Embedding generation
│ └── embedder.py # OpenAI/Cohere embedding service
│
├── graph/ # LangGraph agent pipeline
│ └── memory_graph.py # Multi-node state machine
│
├── llm/ # LLM routing
│ └── router.py # Multi-provider fallback router
│
├── retrieval/ # Hybrid retrieval
│ └── hybrid_retriever.py # Vector + temporal + mood retrieval
│
├── middleware/ # Request middleware
│ └── logging.py # Request ID, structured logging
│
└── utils/ # Utilities
└── safety.py # Production safety checks
- Framework: FastAPI 0.109+ (async Python)
- Database: Supabase PostgreSQL with pgvector extension
- Authentication: Supabase Auth (JWT with OIDC discovery)
- LLM Orchestration: LangChain + LangGraph
- LLM Providers: OpenAI (primary), Gemini (fallback), Groq (disabled)
- Embeddings: Cohere
embed-v3(default, 1024 dim) or OpenAItext-embedding-3-large - Vector Search: pgvector with cosine similarity
- Async: asyncpg for database, httpx for HTTP
- Configuration: pydantic-settings
- Logging: Structured JSON logging with request IDs
- Framework: Next.js 15 (React)
- UI: Tailwind CSS, shadcn/ui components
- Authentication: Supabase Auth client
- State Management: React hooks
- Deployment: Vercel
- Backend Hosting: Render (Docker)
- Frontend Hosting: Vercel
- Database: Supabase PostgreSQL
- Containerization: Docker, Docker Compose
- Tracking: MLflow
- Drift Detection: Evidently AI
- Evaluation: Ragas
ai-journal/
├── backend/ # FastAPI backend
│ ├── api/ # API endpoints
│ ├── auth/ # Authentication
│ ├── config/ # Configuration
│ ├── database/ # Database layer
│ ├── embeddings/ # Embedding service
│ ├── graph/ # LangGraph pipeline
│ ├── llm/ # LLM router
│ ├── retrieval/ # Hybrid retrieval
│ ├── middleware/ # Request middleware
│ ├── utils/ # Utilities
│ ├── main.py # FastAPI app entry point
│ ├── Dockerfile # Docker configuration
│ ├── requirements.prod.txt # Production dependencies
│ └── requirements.ml.txt # ML/evaluation dependencies
│
├── frontend/ # Next.js frontend
│ ├── app/ # Next.js app directory
│ ├── components/ # React components
│ ├── lib/ # Utilities and API client
│ ├── package.json # Node dependencies
│ └── vercel.json # Vercel configuration
│
├── .env.example # Environment variable template
├── README.md # This file
├── LICENSE # License file
└── DEMO_ENTRIES.md # Demo entries for testing
- Python 3.11+ (backend)
- Node.js 18+ (frontend)
- Supabase account
- OpenAI API key (or Cohere API key)
- Docker (optional, for containerized deployment)
git clone https://github.com/Pranaykarvi/LifeMemory-AI.git
cd ai-journalcd backend
pip install -r requirements.prod.txtCopy .env.example to .env and fill in your credentials:
cp .env.example .envRequired variables:
# Supabase
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
SUPABASE_JWT_SECRET=your-jwt-secret
DATABASE_URL=postgresql://postgres:password@host:port/dbname
# Embeddings (choose one)
USE_COHERE=true
COHERE_API_KEY=your-cohere-key
# OR
OPENAI_API_KEY=your-openai-key
# LLM (at least one required)
OPENAI_API_KEY=your-openai-key
# Optional fallbacks
GEMINI_API_KEY=your-gemini-key
# Application
ENV=development
LOG_LEVEL=INFO
ALLOWED_ORIGINS=http://localhost:3000- Go to your Supabase project dashboard
- Open the SQL Editor
- Run the schema from
backend/database/schema.sql:
-- Enable pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;
-- Create journals table
CREATE TABLE IF NOT EXISTS journals (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
entry_date DATE NOT NULL,
content TEXT NOT NULL,
mood TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
UNIQUE (user_id, entry_date)
);
-- Create journal_embeddings table
CREATE TABLE IF NOT EXISTS journal_embeddings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
journal_id UUID NOT NULL REFERENCES journals(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
entry_date DATE NOT NULL,
embedding vector(1024),
metadata JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
UNIQUE(journal_id),
UNIQUE(user_id, entry_date)
);
-- Enable Row Level Security
ALTER TABLE journals ENABLE ROW LEVEL SECURITY;
ALTER TABLE journal_embeddings ENABLE ROW LEVEL SECURITY;
-- RLS Policies
CREATE POLICY user_owns_journals ON journals
FOR ALL USING (auth.uid() = user_id);
CREATE POLICY user_owns_embeddings ON journal_embeddings
FOR ALL USING (auth.uid() = user_id);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_journals_user_id ON journals(user_id);
CREATE INDEX IF NOT EXISTS idx_journals_entry_date ON journals(entry_date DESC);
CREATE INDEX IF NOT EXISTS idx_journals_user_entry_date ON journals(user_id, entry_date DESC);
CREATE INDEX IF NOT EXISTS idx_journal_embeddings_user_id ON journal_embeddings(user_id);
CREATE INDEX IF NOT EXISTS idx_journal_embeddings_entry_date ON journal_embeddings(entry_date);python main.pyThe backend will start on http://localhost:8000
cd frontend
npm installCreate .env.local:
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
NEXT_PUBLIC_API_URL=http://localhost:8000npm run devThe frontend will start on http://localhost:3000
- Open
http://localhost:3000in your browser - Sign up for a new account
- Create your first journal entry
- Wait a few seconds for embedding generation
- Ask a question in the memory chat (e.g., "What patterns do you notice in my emotions?")
| Variable | Required | Description | Default |
|---|---|---|---|
SUPABASE_URL |
Yes | Supabase project URL | - |
SUPABASE_ANON_KEY |
Yes | Supabase anonymous key | - |
SUPABASE_SERVICE_ROLE_KEY |
Yes | Supabase service role key | - |
SUPABASE_JWT_SECRET |
Yes | JWT secret for token verification | - |
DATABASE_URL |
Yes | PostgreSQL connection string | - |
USE_COHERE |
No | Use Cohere for embeddings | true |
COHERE_API_KEY |
Conditional | Cohere API key (if USE_COHERE=true) |
- |
OPENAI_API_KEY |
Conditional | OpenAI API key (for embeddings or LLM) | - |
GEMINI_API_KEY |
No | Gemini API key (fallback LLM) | - |
ENV |
No | Environment (development/production) | production |
LOG_LEVEL |
No | Logging level | INFO |
ALLOWED_ORIGINS |
No | CORS allowed origins (comma-separated) | http://localhost:3000 |
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Yes | Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Yes | Supabase anonymous key |
NEXT_PUBLIC_API_URL |
Yes | Backend API URL |
The system supports multiple LLM providers with automatic fallback:
- OpenAI (Primary):
gpt-4o-mini- Fast, reliable, cost-effective - Gemini (Fallback):
gemini-1.5-flash-002- Google's model - Groq (Disabled): Requires incompatible
langchain-coreversion
The router automatically selects the first available provider. If all fail, it returns a safe fallback message.
- Default: Cohere
embed-v3(1024 dimensions, cost-effective) - Fallback: OpenAI
text-embedding-3-large(3072 dimensions, if Cohere unavailable)
- Development:
http://localhost:8000 - Production: Your Render backend URL
All endpoints (except /health) require authentication via JWT token:
Authorization: Bearer <your-jwt-token>
GET /healthResponse:
{
"status": "healthy",
"timestamp": "2024-01-01T00:00:00Z"
}POST /journal/save
Content-Type: application/json
Authorization: Bearer <token>
{
"content": "Today was a great day...",
"mood": "happy",
"entry_date": "2024-01-01"
}Response:
{
"id": "uuid",
"user_id": "uuid",
"entry_date": "2024-01-01",
"content": "Today was a great day...",
"mood": "happy",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}Note: This is an UPSERT operation. If an entry exists for the same user_id and entry_date, it will be updated.
GET /journal/get?entry_date=2024-01-01
Authorization: Bearer <token>Response:
{
"id": "uuid",
"user_id": "uuid",
"entry_date": "2024-01-01",
"content": "Today was a great day...",
"mood": "happy",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}GET /journal/list?page=1&page_size=20
Authorization: Bearer <token>Response:
{
"entries": [
{
"id": "uuid",
"entry_date": "2024-01-01",
"content": "Today was a great day...",
"mood": "happy",
"created_at": "2024-01-01T00:00:00Z"
}
],
"total": 10,
"page": 1,
"page_size": 20
}GET /journal/days-with-entries?month=2024-01
Authorization: Bearer <token>Response:
{
"dates": ["2024-01-01", "2024-01-05", "2024-01-10"]
}POST /ask
Content-Type: application/json
Authorization: Bearer <token>
{
"query": "What patterns do you notice in my emotions?"
}Response:
{
"answer": "Based on your journal entries, I notice...",
"evidence": [
{
"id": "uuid",
"date": "2024-01-01",
"content": "Today was a great day...",
"mood": "happy",
"score": 0.85
}
],
"intent": "pattern",
"retrieved_count": 5,
"llm_provider": "openai"
}GET /insights/summary
Authorization: Bearer <token>Response:
{
"total_entries": 50,
"total_words": 5000,
"mood_distribution": {
"happy": 20,
"sad": 10,
"anxious": 5
},
"most_active_day": "Monday",
"average_entry_length": 100
}| Column | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PRIMARY KEY | Journal entry ID |
user_id |
UUID | NOT NULL | User ID (from Supabase Auth) |
entry_date |
DATE | NOT NULL | Entry date (YYYY-MM-DD) |
content |
TEXT | NOT NULL | Journal entry content |
mood |
TEXT | NULL | Mood label (e.g., "happy", "sad") |
created_at |
TIMESTAMPTZ | DEFAULT now() | Creation timestamp |
updated_at |
TIMESTAMPTZ | DEFAULT now() | Last update timestamp |
Constraints:
UNIQUE (user_id, entry_date)- One entry per user per day
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PRIMARY KEY | Embedding ID |
journal_id |
UUID | NOT NULL, FK | Reference to journals table |
user_id |
UUID | NOT NULL | User ID (for RLS) |
entry_date |
DATE | NOT NULL | Entry date (for filtering) |
embedding |
vector(1024) | NULL | Vector embedding (pgvector) |
metadata |
JSONB | DEFAULT '{}' | Additional metadata |
created_at |
TIMESTAMPTZ | DEFAULT now() | Creation timestamp |
Constraints:
UNIQUE(journal_id)- One embedding per journalUNIQUE(user_id, entry_date)- One embedding per user per day
Metadata Structure:
{
"mood": "happy",
"entry_date": "2024-01-01",
"week": 1,
"month": 1,
"year": 2024
}Both tables have RLS enabled with policies:
-- Journals
CREATE POLICY user_owns_journals ON journals
FOR ALL USING (auth.uid() = user_id);
-- Embeddings
CREATE POLICY user_owns_embeddings ON journal_embeddings
FOR ALL USING (auth.uid() = user_id);This ensures users can only access their own data, even if application-level filtering fails.
-
Create a new Web Service on Render
-
Connect your GitHub repository
-
Configure the service:
- Root Directory:
backend - Environment:
Docker - Dockerfile Path:
backend/Dockerfile - Instance Type: Free tier or higher
- Root Directory:
-
Set Environment Variables:
- All variables from
.env(see Configuration section) ALLOWED_ORIGINS: Your Vercel frontend URL (e.g.,https://your-app.vercel.app)
- All variables from
-
Deploy: Render will automatically build and deploy
-
Import your GitHub repository to Vercel
-
Configure the project:
- Root Directory:
frontend - Framework Preset: Next.js
- Build Command:
npm run build - Output Directory:
.next
- Root Directory:
-
Set Environment Variables:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYNEXT_PUBLIC_API_URL: Your Render backend URL
-
Deploy: Vercel will automatically deploy on push
-
Update Vercel Environment Variables:
- Set
NEXT_PUBLIC_API_URLto your Render backend URL
- Set
-
Update Render Environment Variables:
- Set
ALLOWED_ORIGINSto your Vercel frontend URL
- Set
-
Test the Connection:
- Open your Vercel frontend
- Check browser console for CORS errors
- Test authentication and API calls
# Build and run with Docker Compose
docker-compose up --build
# Or build and run manually
cd backend
docker build -t lifememory-backend .
docker run -p 8000:8000 --env-file .env lifememory-backend- JWT Verification: Every request is authenticated via Supabase JWT
- OIDC Discovery: Dynamic JWKS URI discovery for token verification
- User Isolation: All queries are scoped to
user_idfrom JWT - Row Level Security: Database-level enforcement of user isolation
- No Cross-User Access: Impossible by design (RLS + explicit filtering)
- No Data Training: User data is never used for training
- Privacy-Preserving Logs: No journal content in logs, user IDs truncated
- Secure Storage: All data encrypted at rest (Supabase)
- CORS: Configured for specific origins only (no wildcards in production)
- Rate Limiting: Can be added via middleware (not included by default)
- Input Validation: Pydantic models validate all inputs
- SQL Injection Protection: Parameterized queries only
- Never commit
.envfiles - Use strong, unique API keys
- Rotate keys regularly
- Monitor for suspicious activity
- Keep dependencies updated
# Backend
cd backend
ENV=development python main.py
# Frontend
cd frontend
npm run dev- Type Hints: All functions have type hints
- Docstrings: All functions and classes documented
- Error Handling: Comprehensive error handling throughout
- Async/Await: Proper async patterns for I/O operations
- Logging: Structured logging with request IDs
- API Endpoints: Add to
backend/api/ - Database Changes: Update
backend/database/schema.sqland create migration - Frontend Components: Add to
frontend/components/ - LLM Prompts: Update
backend/graph/memory_graph.py
# Backend tests (if available)
cd backend
pytest
# Frontend tests (if available)
cd frontend
npm test-
Create Test Entries: Use entries from
DEMO_ENTRIES.md -
Test Queries: Try different intent types:
- Reflection: "Why was I feeling burned out?"
- Pattern: "What patterns do you notice in my emotions?"
- Recall: "What happened last week?"
- Temporal Comparison: "How was January different from December?"
- Advice: "What should I do about my work stress?"
-
Test Edge Cases:
- Empty journal entries
- Very long entries
- Special characters
- Multiple entries per day (should update, not create)
- Test authentication flow
- Test journal creation and retrieval
- Test memory queries with various intents
- Test error handling (invalid tokens, missing data, etc.)
- Check environment variables: Ensure all required variables are set
- Check database connection: Verify
DATABASE_URLis correct - Check API keys: Ensure at least one LLM provider key is set
- Check
ALLOWED_ORIGINS: Ensure frontend URL is included - Check backend logs: Look for CORS-related errors
- Verify headers: Ensure
Authorizationheader is sent
- Check JWT token: Verify token is valid and not expired
- Check Supabase credentials: Verify
SUPABASE_URLandSUPABASE_ANON_KEY - Check JWT secret: Verify
SUPABASE_JWT_SECRETmatches Supabase
- Check API keys: Verify
COHERE_API_KEYorOPENAI_API_KEYis set - Check database: Ensure
journal_embeddingstable exists - Check logs: Look for embedding generation errors
- Wait for embeddings: Embeddings are generated asynchronously
- Check entry count: Need at least 2-3 entries for pattern queries
- Check relevance scores: Low scores may indicate poor matches
Enable debug logging:
# Backend
LOG_LEVEL=DEBUG python main.py
# Check logs for detailed informationContributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes: Follow code style and add tests
- Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request: Describe your changes clearly
- Python: Follow PEP 8, use type hints, add docstrings
- TypeScript: Follow ESLint rules, use TypeScript types
- Commits: Use clear, descriptive commit messages
This project is proprietary software. All rights reserved.
- FastAPI for the excellent async framework
- Supabase for the database and auth infrastructure
- LangChain/LangGraph for the agent orchestration
- OpenAI/Cohere for embeddings and LLM capabilities
- Next.js for the frontend framework
For issues, questions, or contributions:
- GitHub Issues: Create an issue
- Documentation: See this README and inline code documentation
Built with ❤️ for personal reflection and growth