diff --git a/TASKS.md b/TASKS.md new file mode 100644 index 00000000..6b2f6d3c --- /dev/null +++ b/TASKS.md @@ -0,0 +1,218 @@ +# Standalone Setup — Remaining Tasks + +Branch: `local-llm-prd`. Setup doc: `docs/docs/installation/standalone-local-setup.md`. + +**Goal**: one script (`scripts/setup-local-dev.sh`) that takes a fresh clone to a working Reflector with no cloud accounts, no API keys, no manual env editing. Live/WebRTC mode only (no file upload, no Daily.co, no Whereby). + +**Already done**: Step 1 (Ollama/LLM) in `scripts/setup-local-llm.sh`, Step 3 (storage — skip S3). + +**Not our scope**: Step 4 (transcription/diarization) — another developer handles that. + +--- + +## Task 1: Research env defaults for standalone + +### Goal + +Determine the exact `server/.env` and `www/.env.local` contents for standalone mode. Both files must be generatable by the setup script with zero user input. + +### server/.env — what we know + +Source of truth for all backend settings: `server/reflector/settings.py` (pydantic BaseSettings, reads from `.env`). + +**Vars that MUST be set (no usable default for docker):** + +| Variable | Standalone value | Why | +|----------|-----------------|-----| +| `DATABASE_URL` | `postgresql+asyncpg://reflector:reflector@postgres:5432/reflector` | Default is `localhost`, containers need `postgres` hostname | +| `REDIS_HOST` | `redis` | Default is `localhost`, containers need `redis` hostname | +| `CELERY_BROKER_URL` | `redis://redis:6379/1` | Default uses `localhost` | +| `CELERY_RESULT_BACKEND` | `redis://redis:6379/1` | Default uses `localhost` | +| `HATCHET_CLIENT_TOKEN` | *generated at runtime* | Must be extracted from hatchet container after it starts (see below) | + +**Vars that MUST be overridden from .env.example defaults:** + +| Variable | Standalone value | .env.example has | Why | +|----------|-----------------|------------------|-----| +| `AUTH_BACKEND` | `none` | `jwt` | No Authentik in standalone | +| `TRANSCRIPT_STORAGE_BACKEND` | *(unset/empty)* | `aws` | Skip S3, audio stays local | +| `DIARIZATION_ENABLED` | `false` | `true` (settings.py default) | No diarization backend in standalone | +| `TRANSLATION_BACKEND` | `passthrough` | `modal` (.env.example) | No Modal in standalone. Default in settings.py is already `passthrough`. | + +**Vars set by LLM setup (step 1, already handled):** + +| Variable | Mac value | Linux GPU value | Linux CPU value | +|----------|-----------|-----------------|-----------------| +| `LLM_URL` | `http://host.docker.internal:11434/v1` | `http://ollama:11434/v1` | `http://ollama-cpu:11434/v1` | +| `LLM_MODEL` | `qwen2.5:14b` | same | same | +| `LLM_API_KEY` | `not-needed` | same | same | + +**Vars with safe defaults in settings.py (no override needed):** + +- `LLM_CONTEXT_WINDOW` = 16000 +- `SECRET_KEY` = `changeme-f02f86fd8b3e4fd892c6043e5a298e21` (fine for local dev) +- `BASE_URL` = `http://localhost:1250` +- `UI_BASE_URL` = `http://localhost:3000` +- `CORS_ORIGIN` = `*` +- `DATA_DIR` = `./data` +- `TRANSCRIPT_BACKEND` = `whisper` (default in settings.py — step 4 developer may change this) +- `HATCHET_CLIENT_TLS_STRATEGY` = `none` +- `PUBLIC_MODE` = `false` + +**OPEN QUESTION — Hatchet token chicken-and-egg:** + +The `HATCHET_CLIENT_TOKEN` must be generated after the hatchet container starts and creates its DB schema. The current manual process (from `server/README.md`): +```bash +TENANT_ID=$(docker compose exec -T postgres psql -U reflector -d hatchet -t -c \ + "SELECT id FROM \"Tenant\" WHERE slug = 'default';" | tr -d ' \n') && \ +TOKEN=$(docker compose exec -T hatchet /hatchet-admin token create \ + --config /config --tenant-id "$TENANT_ID" 2>/dev/null | tr -d '\n') && \ +echo "HATCHET_CLIENT_TOKEN=$TOKEN" +``` + +The setup script needs to: +1. Start postgres + hatchet first +2. Wait for hatchet to be healthy +3. Generate the token +4. Write it to `server/.env` +5. Then start server + workers (which need the token) + +**OPEN QUESTION — HATCHET_CLIENT_HOST_PORT and HATCHET_CLIENT_SERVER_URL:** + +These are NOT in `settings.py` — they're Hatchet SDK env vars read directly by the SDK. The JWT token embeds `localhost` URLs, but workers inside Docker need `hatchet:7077`. From CLAUDE.md: +``` +HATCHET_CLIENT_HOST_PORT=hatchet:7077 +HATCHET_CLIENT_SERVER_URL=http://hatchet:8888 +HATCHET_CLIENT_TLS_STRATEGY=none +``` +These may need to go in `server/.env` too. Verify by checking how hatchet-worker containers connect — they share the same `env_file: ./server/.env` as the server. + +### www/.env.local — what we know + +The `web` service in `docker-compose.yml` reads `env_file: ./www/.env.local`. + +Template: `www/.env.example`. For standalone: + +| Variable | Standalone value | Notes | +|----------|-----------------|-------| +| `SITE_URL` | `http://localhost:3000` | | +| `NEXTAUTH_URL` | `http://localhost:3000` | Required by NextAuth | +| `NEXTAUTH_SECRET` | `standalone-dev-secret-not-for-production` | Any string works for dev | +| `API_URL` | `http://localhost:1250` | Browser-side API calls | +| `SERVER_API_URL` | `http://server:1250` | Server-side (SSR) API calls within Docker network | +| `WEBSOCKET_URL` | `ws://localhost:1250` | Browser-side WebSocket | + +**Not needed for standalone (no auth):** +- `AUTHENTIK_*` vars — only needed when `AUTH_BACKEND=jwt` +- `FEATURE_REQUIRE_LOGIN` — should be `false` or unset +- `ZULIP_*` — no Zulip integration +- `SENTRY_DSN` — no Sentry + +**OPEN QUESTION**: Does the frontend crash if `AUTHENTIK_*` vars are missing? Or does it gracefully skip auth UI when backend reports `AUTH_BACKEND=none`? Check `www/` auth code. + +### Deliverable + +A concrete list of env vars for each file, with exact values. Resolve all open questions above. + +--- + +## Task 2: Build unified setup script + docker integration + +### Goal + +Create `scripts/setup-local-dev.sh` that does everything: LLM setup (absorb existing `setup-local-llm.sh`), env file generation, docker services, migrations, health check. + +### Depends on + +Task 1 (env defaults must be decided first). + +### Script structure (from standalone-local-setup.md) + +``` +setup-local-dev.sh +├── Step 1: LLM/Ollama setup (existing logic from setup-local-llm.sh) +├── Step 2: Generate server/.env and www/.env.local +├── Step 3: (skip — no S3 needed) +├── Step 4: (skip — handled by other developer) +├── Step 5: docker compose up (postgres, redis, hatchet, server, workers, web) +├── Step 6: Wait for services + run migrations +└── Step 7: Health check + print success URLs +``` + +### Key implementation details + +**Idempotency**: Script must be safe to re-run. Each step should check if already done: +- LLM: check if Ollama running + model pulled +- Env files: check if files exist, don't overwrite (or merge carefully) +- Docker: `docker compose up -d` is already idempotent +- Migrations: `alembic upgrade head` is already idempotent +- Health check: always run + +**Hatchet token flow** (the tricky part): +1. Generate env files WITHOUT `HATCHET_CLIENT_TOKEN` +2. Start postgres + redis + hatchet +3. Wait for hatchet health (`curl -f http://localhost:8889/api/live`) +4. Generate token via `hatchet-admin` CLI (see Task 1 for command) +5. Append/update `HATCHET_CLIENT_TOKEN=...` in `server/.env` +6. Start server + hatchet-worker-cpu + hatchet-worker-llm + web + +**Docker compose invocation**: +- Mac: `docker compose -f docker-compose.yml -f docker-compose.standalone.yml up -d ` +- Linux with GPU: add `--profile ollama-gpu` +- Linux without GPU: add `--profile ollama-cpu` +- Services for standalone: `postgres redis hatchet server hatchet-worker-cpu hatchet-worker-llm web` +- Note: `worker` (Celery) and `beat` may not be needed for standalone live mode — verify if live pipeline uses Celery or only Hatchet + +**Migrations**: `docker compose exec server uv run alembic upgrade head` — must wait for server container to be ready first. + +**Health checks**: +- `curl -sf http://localhost:1250/health` (server `/health` endpoint returns `{"status": "healthy"}`) +- `curl -sf http://localhost:3000` (frontend) +- LLM reachability from container: `docker compose exec server curl -sf http://host.docker.internal:11434/v1/models` (Mac) or equivalent + +### Files to create/modify + +| File | Action | +|------|--------| +| `scripts/setup-local-dev.sh` | Create — unified setup script | +| `scripts/setup-local-llm.sh` | Keep or remove after folding into unified script | +| `docs/docs/installation/standalone-local-setup.md` | Update status section when done | +| `server/.env.example` | May need standalone section/comments | + +### Docker compose considerations + +Current `docker-compose.yml` services: server, worker, beat, hatchet-worker-cpu, hatchet-worker-llm, redis, web, postgres, hatchet. + +Current `docker-compose.standalone.yml` services: ollama (GPU profile), ollama-cpu (CPU profile). + +**OPEN QUESTION**: Does the live pipeline (WebRTC recording) use Celery tasks or Hatchet workflows? If only Hatchet, we can skip `worker` and `beat` services. Check `server/reflector/pipelines/main_live_pipeline.py` — it currently uses Celery chains/chords for post-processing. So `worker` IS needed for live mode. + +Update: Looking at `main_live_pipeline.py`, the live pipeline dispatches Celery tasks via `chain()` and `chord()` (lines ~780-810). So both `worker` (Celery) and hatchet workers are needed. `beat` is for cron jobs (cleanup, polling) — probably not critical for standalone demo but harmless to include. + +### Final service list for standalone + +``` +postgres redis hatchet server worker hatchet-worker-cpu hatchet-worker-llm web +``` + +Plus on Linux: `ollama` or `ollama-cpu` via profile. + +--- + +## Reference: key file locations + +| File | Purpose | +|------|---------| +| `server/reflector/settings.py` | All backend env vars with defaults | +| `server/.env.example` | Current env template (production-oriented) | +| `www/.env.example` | Frontend env template | +| `docker-compose.yml` | Main services definition | +| `docker-compose.standalone.yml` | Ollama services for standalone | +| `scripts/setup-local-llm.sh` | Existing LLM setup script | +| `docs/docs/installation/standalone-local-setup.md` | Setup documentation | +| `server/README.md:56-84` | Hatchet token generation commands | +| `server/reflector/hatchet/client.py` | Hatchet client (requires HATCHET_CLIENT_TOKEN) | +| `server/reflector/storage/__init__.py` | Storage factory (skipped when TRANSCRIPT_STORAGE_BACKEND unset) | +| `server/reflector/pipelines/main_live_pipeline.py` | Live pipeline (uses Celery chains for post-processing) | +| `server/reflector/app.py:72-74` | Health endpoint (`GET /health` returns `{"status": "healthy"}`) | +| `server/docker/init-hatchet-db.sql` | Creates `hatchet` DB on postgres init | diff --git a/docs/docs/installation/standalone-local-setup.md b/docs/docs/installation/standalone-local-setup.md index 7ab5ed1f..41e5c455 100644 --- a/docs/docs/installation/standalone-local-setup.md +++ b/docs/docs/installation/standalone-local-setup.md @@ -101,4 +101,5 @@ These require external accounts and infrastructure that can't be scripted: - Step 1 (Ollama/LLM) — implemented and tested - Step 3 (transcript storage) — resolved: skip for live-only mode, no code changes needed -- Steps 2, 4, 5, 6, 7 — need a separate research and implementation pass each +- Step 4 (transcription/diarization) — in progress by another developer +- Steps 2, 5, 6, 7 — next up: env defaults research, then unified script (see `TASKS.md`)