# recommender The core of oO. Takes a user + a context, returns **one** tip. ## Contract ``` POST /recommend { user_id, context?: { time, timezone, client, ... } } → { tip: { id, kind: "todo"|"advice", title, body, source, deep_link, meta } } POST /feedback { user_id, tip_id, reaction: "done"|"snooze"|"dismiss", at } ``` ## Internals (stable seams) - **Candidate sources** — pluggable async generators. v0: Todoist tasks via `integrations`. Later: advice library, calendar nudges, health prompts. - **Feature assembler** — fills the `context` blob (inline in Phase 0; calls feature store from M1). Never inlined into policy code. - **Policy registry** — `Policy.pick(candidates, context) → tip`. Named entries: - `random` — v0 (Phase 0). - `bandit.linucb.pooled` — v1 (Phase 1). **Global-then-personalize**: pooled features shared across users; per-user residual once data allows. - `remote` — delegates to `ml/serving` FastAPI scorer (Phase 1+). - **Shadow hook** — every request optionally runs N shadow policies in parallel and logs their picks + estimated rewards. Promotion from shadow → A/B → launch is a separate, deliberate step (ADR-0002). - **TipInstance persistence** — every decision writes `context_snapshot` (features seen at decision time). This is what makes offline replay honest. ## Phase 0 goal `RandomPolicy` only. The service, contract, registry, shadow hook, and tip-instance persistence all exist; no ML yet.