# oO — Project Instructions ## What this is **oO** is a recommendation system for personal tips. It collects signals across a user's life (tasks, habits, calendar, mood, context) to build a rich profile and deliver **one** perfectly-timed tip — an advice or a todo — that feels like magic. The magic is the product. Precision + timing + minimalism. The UI shows a single black page with one tip. The complexity lives behind it. ## Prime directives 1. **Modular by package, deployable by stage.** Contracts live at package boundaries from day one so extraction to a service is cheap. Deploy topology evolves with real pressure (team size, scaling hotspots, language boundaries), not with wishful architecture. Phase 0 = **modular monolith + Python ML sidecar**. See ADR-0003. 2. **Recommendation engine is the core.** Every other module feeds it or renders its output. Design schemas, event contracts, and APIs with that in mind. 3. **Python owns ML.** Training, features, online scoring are Python (FastAPI + PyTorch/scikit + MLflow/Feast). Application code is TypeScript (Node, Next.js) unless there's a reason. 4. **OAuth-first for identity and integrations.** Never ask users for passwords or raw API keys when a delegated-auth flow exists. Store provider tokens encrypted, refresh transparently. 5. **Privacy is a feature, not a phase.** Consent capture, token revocation, and account deletion exist from the first real user. Data minimization: store the token + derivatives we need, not the raw feed. 6. **Feel-of-magic over feature count.** When in doubt, ship fewer things, polished. The tip page is a watch face. ## Architecture (high level) The tree below is **logical module structure**. Directory layout is stable; how many processes you deploy is a stage decision (ADR-0003). ``` apps/ user-facing clients web/ Next.js PWA — the first shipped client mobile-ios/ Swift/SwiftUI (Phase 3) mobile-android/ Kotlin/Compose (Phase 3) services/ backend modules — each owns a contract; may share a deployable gateway/ BFF for clients; auth check; fan-out auth/ OAuth (Google, Apple, ...), sessions, JWT issuance profile/ user profile, preferences, consents integrations/ third-party connectors + token vault (Todoist first) recommender/ orchestration: candidates → policy → tip; feedback sink events/ event bus ingress + durable signal store notifier/ push/email/web delivery (web push from Phase 1) packages/ shared libraries (importable across services + apps) shared-types/ HTTP types via OpenAPI; event types via protobuf (ADR-0005) sdk-js/ client SDK used by web + mobile webviews ui/ shared React components + design tokens ml/ Python — separate deployable from day one serving/ online scorer (FastAPI), called by recommender features/ feature definitions + store adapter pipelines/ batch feature + training DAGs (Prefect/Airflow) registry/ MLflow model registry integration experiments/ assignment + A/B + bandit policies notebooks/ research only; never imported by production code infra/ docker-compose (Phase 0), k3s/k8s (later), terraform, CI docs/ architecture notes, ADRs, API specs ``` **Phase 0 deployables:** one Node process (`services/*` bundled via modular monolith) + one Python process (`ml/serving`, stubbed until M1) + Postgres + NATS. Services **extract to their own process** when a real reason appears: language boundary, scaling hotspot, team ownership, or SLA divergence. See ADR-0003. ## Contracts between modules - **HTTP** (OpenAPI, in `packages/shared-types/http/`) — synchronous request/response. In-process today; over the network once extracted. Signatures are identical. - **Events** (Protocol Buffers, in `packages/shared-types/events/`) — durable signals + feedback. Today: in-process event emitter. Tomorrow: NATS JetStream. Schema registry enforced in CI (ADR-0005). - Do not redefine types per module. Regenerate from `shared-types`. ## Conventions - Each module ships a `README.md` describing its contract, its `/health` story, and its extraction criteria (when it should become its own process). - One PR = one concern. Conventional-commit prefixes (`feat:`, `fix:`, `chore:`, `docs:`, `refactor:`). - ADRs go in `docs/adr/NNNN-title.md` for any decision that constrains future work. - No secrets in repo. Local dev via `.env.local` (gitignored), prod via the server's secret store (Vaultwarden now; k8s secrets later). - Compose profiles (`core`, `full`) so devs can run a subset without 16 GB of RAM. ## Definition of done (per feature) 1. Code + tests merged. 2. Module's `README.md` updated. 3. If it changes a contract → `shared-types` regenerated + consumers updated. 4. If it changes architecture → ADR added. 5. Deployable via `docker compose up` locally. 6. If it touches user data → a deletion path exists and is tested. ## Current phase **Phase 0 — Prototype.** See `README.md` for the phase roadmap and `docs/architecture/` for diagrams. Work is tracked as Gitea milestones + issues on `alvis/oO`. ## What NOT to do - Don't copy Todoist's data into our DB. Store the OAuth token + computed features/derivatives we need, fetch raw on demand. - Don't implement auth by hand. Phase 0 uses **Auth.js** behind an OIDC-shaped boundary (ADR-0004); swap to a dedicated OIDC provider only when mobile ships. - Don't hardwire a recommender. The "random todo" v0 must live behind the same interface the real ML model will implement (`POST /recommend` → `{tip}`). Swap internals, keep contract. - Don't replace a policy in one step. New policies deploy shadow-first; promoted only after offline + online agreement with the incumbent (ADR-0002). - Don't build an admin UI before the user-facing black page is polished. - Don't over-split processes. Extract a service when pressure demands it, not in anticipation (ADR-0003).