integrations/README — replace stale Connector interface and fictional libsodium vault with the actual SignalSource pattern, SQLite token table, and real OAuth routes. recommender/README — document the SignalAggregator pipeline, current policy registry, and actual /recommend + /feedback contract shapes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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
- Signals —
SignalAggregator.fetchAll(userId)fans out to all registeredSignalSourceimplementations in parallel. Currently:TodoistSignalSource. Add a source viaaggregator.register(new MySource()). - LLM candidates —
POST /generateonml/servingreturnsTipCandidate[]from thetip-generatorLiteLLM alias. - Scoring — all candidates sent to
ml/servingactive policy (POST /score/egreedy). Falls back to random ifml/servingis unreachable. - Shadow policies — active policy runs shadow policies in the same request for offline comparison (ADR-0002). Currently:
egreedy-v2shadowsegreedy-v1. - Persistence —
tipViews+tipScoresrows written on every serve;tipFeedbackrow on reaction. - Reward delivery — reaction triggers
POST /reward/egreedyonml/servingwith 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 |
Shadow | Fallback when ml/serving unreachable |
egreedy-v1 |
Active | d=7, ADR-0007 |
egreedy-v2 |
Shadow | 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.