- 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>
2.0 KiB
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
- 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 |
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.