Files
oO/docs/adr/0002-recommender-contract.md

1.1 KiB

ADR-0002: Recommender as the stable contract, policy as a plugin

Status

Accepted — 2026-04-13

Context

v0 picks a random Todoist task. v1+ will use a contextual bandit, then learned rankers, then collaborative signals. If the HTTP contract and the candidate-generation path are coupled to today's "random", every change is a migration.

Decision

recommender exposes POST /recommend as the one stable contract. Internally it has three seams:

  1. Candidate sources — async functions that yield TipCandidates from integrations, advice libraries, etc.
  2. Context assembler — pulls features (today: inline; later: feature store).
  3. PolicyPolicy.pick(candidates, context) → tip. Registered by name; selected per-request by the experiments framework (Phase 4) or a static config (now).

Swapping a policy never changes the contract or the client.

Consequences

  • v0 policy is RandomPolicy, trivially 50 lines.
  • v1 moves scoring to ml/serving behind the same Policy interface (RemotePolicy wrapper).
  • A/B is introduced without touching clients.