Files
oO/services/recommender/README.md
alvis b554970032 docs(observability): add services/api README; update ml/serving + recommender docs (#18)
- services/api/README.md: new — contract, middleware stack, background
  tasks, config table (LOG_LEVEL, SENTRY_DSN), health story, extraction
  criteria
- ml/serving/README.md: add Observability section (structlog JSON,
  traceparent → trace_id binding), add SENTRY_DSN + ENV to config table
- services/recommender/README.md: fix policy table — egreedy-v2 is
  active (#99), egreedy-v1 is shadow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 03:41:39 +00:00

2.0 KiB

recommender

The core of oO. Takes a user + context, returns one tip.

Contract

POST /api/recommend
  { }  (user inferred from session)
  → { tip: { id, content, source, kind, sourceId?, rationale?, createdAt } }

POST /api/tip/:id/feedback
  { action: "done"|"dismiss"|"snooze"|"helpful"|"not_helpful", dwellMs? }
  → { ok: true }

Pipeline

  1. SignalsSignalAggregator.fetchAll(userId) fans out to all registered SignalSource implementations in parallel. Currently: TodoistSignalSource. Add a source via aggregator.register(new MySource()).
  2. LLM candidatesPOST /generate on ml/serving returns TipCandidate[] from the tip-generator LiteLLM alias.
  3. Scoring — all candidates sent to ml/serving active policy (POST /score/egreedy). Falls back to random if ml/serving is unreachable.
  4. Shadow policies — active policy runs shadow policies in the same request for offline comparison (ADR-0002). Currently: egreedy-v2 shadows egreedy-v1.
  5. PersistencetipViews + tipScores rows written on every serve; tipFeedback row on reaction.
  6. Reward delivery — reaction triggers POST /reward/egreedy on ml/serving with inferred reward value.

Signal normalization

Signals carry features: Record<string, number | boolean> (bandit-ready) and metadata: Record<string, unknown> (source-specific raw fields). The bandit treats features as an opaque dict — sources own their feature names. See ADR-0009.

Policy registry

Policy Status Notes
random Fallback Used when ml/serving is unreachable
egreedy-v1 Shadow d=7, ADR-0007
egreedy-v2 Active d=12 + profile features, ADR-0012

Shadow → active promotion requires offline sim + online agreement (ADR-0002).

Extraction criteria

Extract to its own process at scaling hotspot: when POST /recommend p99 latency exceeds SLA or when recommendation CPU displaces API serving on shared host.