A multi-agent social media automation system that learns your voice, tracks what is trending in your domain, and composes publication-ready posts that sound authentically like you.
by Adya Prasad
Cupid orchestrates four specialized AI agents through a shared state pipeline:
- ** Agent** — Builds and maintains a living model of your voice, tone, vocabulary, and domain expertise using RAG over your writing samples.
- Research & Ideation Agent — Given your persona and a topic signal, autonomously researches and returns structured post angle ideas with supporting evidence.
- Trend Intelligence Agent — Monitors Reddit, HackerNews, and RSS feeds in your domain. Scores trending topics by velocity and filters them against your persona.
- Composer Agent — Takes all upstream context and produces platform-specific, publication-ready posts with a persona fidelity check before output.
Analytics, scheduling, brand safety, and notifications are handled by deterministic algorithm-based services — not additional LLM agents.
User Intent
│
▼
Orchestrator (LangGraph StateGraph)
│
├── Personalization Agent → retrieves user identity context
├── Research Agent → finds angles, sources, ideas
└── Composer Agent → assembles platform-specific post
│
▼
Structured Output → FastAPI → Next.js Frontend
All agent state is typed via MemoryState. No agent holds internal state between runs. All persistence is in PostgreSQL and ChromaDB.
| Layer | Technology |
|---|---|
| API | FastAPI, Python 3.11 |
| Agent Orchestration | LangGraph |
| Database | PostgreSQL 16 (via SQLAlchemy + Alembic) |
| Vector Store | ChromaDB |
| Task Queue | Celery + Redis |
| LLM Runtime | Ollama (local) |
| Embeddings | nomic-embed-text via Ollama |
| Frontend | Next.js 14 (App Router), Tailwind CSS, shadcn/ui |
| State Management | Zustand, TanStack Query |
Before running Cupid locally, ensure the following are installed:
- Python 3.11 or higher
- Node.js 18 or higher and npm
- Docker Desktop (running)
- Ollama — ollama.com
- Git
git clone https://github.com/your-username/cupid.git
cd cupidFrom the project root, start PostgreSQL, Redis, and ChromaDB via Docker:
docker compose up -dVerify all containers are healthy:
docker compose psYou should see cupid_postgres, cupid_redis, and cupid_chroma all in a running state.
Cupid uses local LLMs via Ollama. Pull the required models:
ollama pull llama3.2
ollama pull nomic-embed-textNavigate to the backend directory:
cd backendCreate and activate a virtual environment:
python -m venv .venv
.venv\Scripts\Activate.ps1If PowerShell blocks the activation script, run this once and retry:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserInstall dependencies:
pip install --upgrade pip
pip install -r requirements.txtConfigure environment variables. Copy the example file and fill in your values:
Copy-Item .env.example .envOpen .env in your editor and set at minimum:
SECRET_KEY=your-secret-key-here
database_url=postgresql+asyncpg://cupid:cupid@localhost:5432/cupid_db
REDIS_URL=redis://localhost:6379/0Run database migrations:
alembic upgrade headStart the API server:
python -m uvicorn app.main:app --reload --port 8000Verify the backend is live:
http://localhost:8000/health → {"status": "ok", "env": "development"}
http://localhost:8000/api/docs → Swagger UI (interactive API docs)
Open a second PowerShell window:
cd cupid\frontend
npm install
npm run devThe frontend will be available at:
http://localhost:3000
To run the complete local stack you need four things active simultaneously:
| Process | Command | Window |
|---|---|---|
| Infrastructure | docker compose up -d |
Background |
| Ollama | ollama serve |
Terminal 1 |
| Backend API | python -m uvicorn app.main:app --reload --port 8000 |
Terminal 2 |
| Celery Worker | celery -A app.celery_app worker --loglevel=info |
Terminal 3 |
| Frontend | npm run dev |
Terminal 4 |
cupid/
├── backend/
│ ├── app/
│ │ ├── main.py # FastAPI app factory
│ │ ├── config.py # Pydantic settings
│ │ ├── agents/ # LangGraph agent nodes
│ │ ├── services/ # Non-agent algorithm services
│ │ │ ├── analytics.py
│ │ │ ├── scheduler.py
│ │ │ ├── brand_safety.py
│ │ │ └── notifications.py
│ │ ├── routers/ # FastAPI route handlers
│ │ ├── models/ # SQLAlchemy ORM models
│ │ └── core/ # DB, Redis, ChromaDB clients
│ ├── alembic/ # Migration files
│ ├── tests/
│ ├── requirements.txt
│ ├── pyproject.toml
│ └── .env.example
├── frontend/
│ ├── app/ # Next.js App Router
│ ├── components/
│ └── lib/
├── docs/ # Architecture docs, ADRs
├── scripts/ # Dev utilities, seed scripts
├── docker-compose.yml
└── README.md
| Variable | Required | Description |
|---|---|---|
SECRET_KEY |
Yes | JWT signing key. Use a long random string in production. |
database_url |
Yes | PostgreSQL async connection string. |
REDIS_URL |
Yes | Redis connection string. |
CHROMA_HOST |
Yes | ChromaDB host (default: localhost). |
CHROMA_PORT |
Yes | ChromaDB port (default: 8001). |
OLLAMA_BASE_URL |
Yes | Ollama server URL (default: http://localhost:11434). |
OLLAMA_LLM_MODEL |
Yes | LLM model name pulled via Ollama. |
OLLAMA_EMBED_MODEL |
Yes | Embedding model name pulled via Ollama. |
TAVILY_API_KEY |
No | Optional. Enriches Research Agent search. Free tier available. |
REDDIT_CLIENT_ID |
No | Optional. Enables Reddit trend source. |
RESEND_API_KEY |
No | Optional. Enables email notifications. |
Cupid enforces consistent style and catches mistakes automatically at three layers: as you save (EditorConfig), before you commit (pre-commit hooks), and before you merge (GitHub Actions CI).
| Layer | Backend (Python) | Frontend (TypeScript/React) |
|---|---|---|
| Linter | Ruff (E,F,I,N,W,UP) |
ESLint (Next config) |
| Formatter | Ruff format | Prettier (+ Tailwind class sorting) |
| Types | — | TypeScript (tsc --noEmit) |
| Editor baseline | .editorconfig |
.editorconfig |
| Pre-commit | pre-commit framework |
pre-commit framework |
| CI gate | GitHub Actions | GitHub Actions |
# 1. Frontend: install Prettier + ESLint deps
cd frontend
npm install
# 2. Pre-commit hooks (from the repo root, in your Python env)
cd ..
pip install pre-commit
pre-commit install # wires the hooks into .git so they run on every commit| Command | What it does | Expect |
|---|---|---|
npm run format |
Prettier rewrites all files to the canonical style (and sorts Tailwind classes). | Files get reformatted in place. The first run touches many files — commit that as one "format" commit. |
npm run format:check |
Checks formatting without writing. Used by CI. | Exit 0 if clean; lists files + non-zero exit if not. |
npm run lint |
ESLint finds bugs/anti-patterns. | Prints warnings/errors; non-zero exit on error. |
npm run lint:fix |
ESLint auto-fixes what it safely can. | Fixable issues disappear; the rest are reported. |
npm run typecheck |
tsc --noEmit — full TypeScript type check, no output files. |
Exit 0 if types are sound; lists type errors otherwise. |
| Command | What it does | Expect |
|---|---|---|
ruff check . |
Lints all Python (imports, unused vars, naming, pyupgrade…). | Lists violations; non-zero exit if any. |
ruff check . --fix |
Lints and auto-fixes the safe ones. | Fixable issues vanish; rest reported. |
ruff format . |
Formats all Python (Black-compatible). | Files reformatted in place. |
ruff format --check . |
Format check only (no writes). Used by CI. | Exit 0 if clean, non-zero otherwise. |
After pre-commit install, every commit runs the hooks on your staged files only:
- Ruff lint + format on changed
backend/**Python - Hygiene: trailing whitespace, final newline, merge-conflict markers, YAML/JSON validity, large-file guard
Frontend formatting is not in pre-commit (Node formatters with local plugins
don't resolve reliably inside pre-commit's isolated env on Windows). It's enforced
by your editor's "format on save" and by CI (npm run format:check).
If a hook reformats a file, the commit is aborted and the fixes are left
staged-but-unstaged — review them, git add, and commit again. To run every
hook against the whole repo manually:
pre-commit run --all-filesTo bypass hooks in an emergency (avoid this): git commit --no-verify.
.github/workflows/ci.yml runs on every pull request and push to main:
- Backend job —
ruff check+ruff format --check - Frontend job —
prettier --check→eslint→tsc --noEmit→next build
A failing check blocks the merge. Run the same commands locally before pushing to get a green check on the first try.
First-time note: running
npm run formatandruff format .once across the existing codebase will produce a large but mechanical diff. Land it as a single dedicated "apply formatter" commit so future diffs stay clean and reviewable.
cd backend
.venv\Scripts\Activate.ps1
pytestRun with coverage:
pytest --cov=app --cov-report=term-missingWhen the backend is running in development mode, full interactive API documentation is available at:
- Swagger UI:
http://localhost:8000/api/docs - ReDoc:
http://localhost:8000/api/redoc
MIT License. See LICENSE for full terms.