Belief-centric memory AI. Holdmind extracts what you know, how you feel, and what you believe from every conversation — building a typed belief graph that makes the AI smarter over time.
Unauthenticated visitors see the public landing page at /. Authenticated users go straight to /chat.
Stack: FastAPI · PostgreSQL · Qdrant · Next.js 15 · TypeScript
browser ──→ Next.js (Vercel) ──→ /api/* rewrite ──→ FastAPI (DigitalOcean)
│
PostgreSQL (Aiven)
Qdrant (vector store)
SQLite (memory graphs, per-user)
Authentication uses a two-token system: a 7-day JWT in localStorage for API calls, and a 30-day opaque refresh token in an httpOnly cookie for silent re-authentication.
| Tool | Version | Notes |
|---|---|---|
| Python | 3.13+ | |
| uv | latest | Backend package manager |
| Node.js | 20+ | |
| pnpm | 9+ | Frontend package manager |
| PostgreSQL | 14+ | Aiven free tier works |
| Qdrant | latest | Docker or Qdrant Cloud |
cd backend
cp .env.example .env # fill in real values (see table below)
uv sync --extra devApply database migrations:
DATABASE_URL="postgresql://..." uv run alembic upgrade headStart the API server:
uv run uvicorn main:app --reload --port 8000cd frontend
pnpm install
pnpm devThe Next.js dev server proxies /api/* → http://localhost:8000 automatically. Do not set NEXT_PUBLIC_API_URL in local dev — leaving it unset ensures all API calls go through the proxy, which is required for the httpOnly refresh token cookie to work.
Open http://localhost:3000 — unauthenticated visitors see the landing page, authenticated users go straight to /chat.
cd frontend
pnpm test:run| Variable | Required | Description |
|---|---|---|
JWT_SECRET_KEY |
✅ | Secret for signing JWTs. Use a random 32+ char string. |
ENCRYPTION_KEY |
✅ | Base64-encoded 32-byte key for encrypting OpenRouter API keys at rest. Generate: python -c "import secrets,base64; print(base64.b64encode(secrets.token_bytes(32)).decode())" |
DATABASE_URL |
✅ | PostgreSQL connection string. Must use postgresql:// prefix (not postgres://). Example: postgresql://user:pass@host:5432/dbname?sslmode=require |
QDRANT_URL |
✅ | Qdrant instance URL. Example: http://localhost:6333 |
QDRANT_API_KEY |
✅ | Qdrant API key (empty string "" for local Qdrant with no auth) |
MEMORY_DB_DIR |
✅ | Directory for per-user SQLite memory graphs. Example: /data/memories (created on first run) |
CORS_ORIGINS |
optional | JSON list of allowed origins. Default: ["http://localhost:3000"] |
No .env.local is needed for local development. In production, set:
| Variable | Description |
|---|---|
NEXT_PUBLIC_API_URL |
Backend URL. Only set this in production when the API lives on a different domain. Leave unset otherwise. |
Migrations are managed with Alembic. The migration history is in backend/alembic/versions/.
# Apply all pending migrations
cd backend && DATABASE_URL="postgresql://..." uv run alembic upgrade head
# Check current revision
cd backend && DATABASE_URL="postgresql://..." uv run alembic current
# Roll back one revision
cd backend && DATABASE_URL="postgresql://..." uv run alembic downgrade -1cd backend
uv run pytest tests/ -vTests use an in-memory SQLite database. No external services required.
holdmind/
├── backend/
│ ├── alembic/ # DB migrations
│ ├── auth/ # JWT decode, dependencies, cookie helpers
│ ├── models/ # SQLAlchemy ORM models
│ ├── routes/ # FastAPI routers (auth, chat, conversations, memories, settings, token)
│ ├── schemas/ # Pydantic request/response schemas
│ ├── services/ # Business logic (auth, chat, memory, token)
│ ├── tests/ # pytest test suite
│ ├── config.py # Pydantic settings (reads from .env)
│ ├── main.py # FastAPI app, middleware, router registration
│ └── .env.example # Template for required env vars
└── frontend/
├── app/
│ ├── (marketing)/ # Public landing page (no auth shell)
│ │ └── page.tsx # Landing page — checks hm_auth cookie, redirects to /chat if set
│ ├── (auth)/ # Login and signup pages
│ └── (app)/ # Authenticated app shell (chat, memories, settings)
├── components/
│ ├── landing/ # HeroGraph and PreviewGraph canvas animations
│ ├── chat/ # Chat UI components
│ ├── memories/ # Belief graph and memory list
│ └── sidebar/ # Navigation sidebar
├── lib/
│ ├── api.ts # apiFetch wrapper (JWT auth, 401 refresh interceptor)
│ └── auth-context.tsx # AuthProvider, login/logout
└── middleware.ts # Route protection (redirects unauthenticated users)
| Endpoint | Limit | Key |
|---|---|---|
POST /api/auth/signup |
5/min | IP |
POST /api/auth/signin |
5/min | IP |
POST /api/chat |
20/min | User ID |
| All other endpoints | 60/min | User ID |
GET /health |
200/min | IP |
