alvis ac1226c367 feat(integrations): migrate google-health from Fit REST to Google Health API v4
Google Fit REST API was closed to new sign-ups on 2024-05-01 and shuts down
end of 2026, surfacing as "Access blocked: this app's request is invalid"
when starting the OAuth flow.

- Swap the 10 fitness.* OAuth scopes for the 3 googlehealth.*.readonly
  scopes (activity_and_fitness, health_metrics_and_measurements, sleep).
- Replace fitness/v1 dataset:aggregate + sessions calls with
  health.googleapis.com/v4/users/me/dataTypes/{steps,total-calories,
  heart-rate,sleep}/dataPoints, filtered to today's window.
- Read the v4 DataPoint union defensively (the per-type schema is sparsely
  documented) and log the first raw sample at debug so we can refine field
  paths after the first real OAuth.
- Output Signal contract is unchanged — agents and downstream consumers
  see the same steps/activity/heart_rate/sleep signals.

Cloud Console still needs: enable Google Health API, add the 3 scopes to
the consent screen, add test user (all googlehealth scopes are Restricted).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 05:42:05 +00:00

oO

One tip. Right now. Feels like magic.

oO learns who you are from the apps you already use and surfaces one perfectly-timed suggestion — an advice or a todo — on a black page. No feed. No dashboard. One tip.


Why

Everyone has too many tasks, too many apps, too much noise. What people actually need is a single, well-chosen nudge at the right moment. oO is that nudge, powered by a recommendation engine that gets smarter the more of your life it sees.

Product principles

  1. One thing at a time. The UI is a black page with one tip. That's the product.
  2. We don't own your data, we understand it. Connect your apps; we read what we need, when we need it.
  3. Magic requires craft. Precision, timing, and restraint matter more than features.
  4. Private by default. Tokens are encrypted, models are per-user, deletion is one click.

Prototype scope (Phase 0)

Three pages. That's it.

Page What it does
Sign in Google / Apple OAuth. No passwords.
Connect A list of integrations. Tap "Todoist" → OAuth flow → token stored.
Tip Black page. One tip. Tap to dismiss / done / snooze.

Under the hood the "pick a tip" call already routes through a recommender service with a pluggable policy — so v0 is literally "random Todoist task" but every other version slots into the same contract.


Architecture at a glance

 ┌──────────┐   OAuth   ┌────────────┐
 │  Web /   │──────────▶│   auth     │
 │  Mobile  │           └─────┬──────┘
 │  client  │                 │ JWT
 │          │   REST/GraphQL  ▼
 │          │────────▶┌───────────────┐
 └──────────┘         │   gateway     │──┬──▶ profile
                      └───────┬───────┘  ├──▶ integrations ──▶ Todoist / Google / ...
                              │          └──▶ recommender ──▶ ml/serving (Python)
                              ▼
                      ┌───────────────┐
                      │    events     │ ◀── integrations emit normalized events
                      │  (Kafka/NATS) │ ──▶ ml/pipelines (features, training)
                      └───────────────┘

More detail in docs/architecture/ and decisions in docs/adr/.

Monorepo layout

See CLAUDE.md for the full tree and conventions.

apps/        web, ios, android
services/    gateway, auth, profile, integrations, recommender, events, notifier
packages/    shared-types, sdk-js, ui
ml/          pipelines, features, registry, experiments, serving
infra/       docker, k8s, terraform, ci
docs/        architecture, adr, api

AI stack

oO is AI-native. Domain-specialized agents pre-compute snippets describing the user's state from one angle each; an orchestrator LLM reasons over the assembled snippets and produces one tip (ADR-0013). The orchestrator iterates a registry, not a hardcoded list (ADR-0014) — adding an agent is a manifest change, nothing else.

Three-tier layout

Tier Service Purpose Where
Inference Ollama Local LLM + embedding; no data leaves the host localhost:11434
Routing LiteLLM Unified OpenAI-compatible API; model aliases; cloud fallback llm.alogins.net (Agap shared)
Testing OpenWebUI Prompt iteration, model comparison, manual evals ai.alogins.net (Agap shared)

Tip generation pipeline (ADR-0013, M2)

User signals          Pre-compute agents (every 15 min)
(tasks, calendar,  ──▶ ml/agents/{overdue-task, momentum,        ──▶  agent_outputs
 patterns, time)        time-of-day, recent-patterns,                 (per-agent TTL)
                        focus-area, ...}
                                                                            │
                              Eligibility filter: required consents +       │
                              active context + per-user prefs (ADR-0014) ◀──┘
                                                ▼
                                  Orchestrator prompt (`v4-orchestrator`)
                                  = global prefs + active context + snippets
                                                ▼
                                    LiteLLM ──▶ Ollama (local) / cloud fallback
                                                ▼
                                         Tip shown to user
                                                ▼
                              User reaction (done / snooze / dismiss + dwell)
                                                ▼
                              Logged to tip_feedback for observability
                              (no online ML reward loop — see ADR-0013)

Why LiteLLM as gateway: All LLM calls use a single LITELLM_URL env var. Swapping from qwen2.5 to llama3.2, or routing a fraction to Claude for A/B, is a config change in LiteLLM — zero code change in oO. The model name in tip_scores tells you exactly which model produced each tip.

Why Ollama first: Tips contain personal context. Local inference means no user data leaves the host for the inference path. Cloud models (Anthropic, OpenAI) are opt-in fallbacks for evaluation and simulation only, gated behind ANTHROPIC_API_KEY.

Models (planned; routes through LiteLLM)

Alias Model Task
tip-generator qwen2.5:1.5b (default) Generate typed tip candidates from user context; local-first via Ollama
embedder nomic-embed-text Task clustering, semantic similarity for dedup; local via Ollama
judge claude-haiku-4-5 (cloud, eval-only) Offline sim judge; rates tip quality for A/B (requires ANTHROPIC_API_KEY)

All model calls route through LiteLLM at llm.alogins.net (or LITELLM_URL env var) using model aliases. This decouples tip generation from model selection — swap the backend model in LiteLLM config without code changes. See ADR-0008.


Roadmap

Issues and open work are tracked in Gitea milestones. Pick an issue, check its milestone (= phase), read the service's README.md, ship.

Phase 0 — Walking skeleton (M0) ✓ shipped

Single user signs in with Google, connects Todoist, sees one random task on a black page. Deletion works. Auth, integrations, recommender stub, PWA, feedback loop, ToS/privacy, metrics baseline.

Phase 1 — Real signal + in-the-moment delivery (M1) ✓ shipped

Tips are picked, not drawn from a hat. Event bus, Todoist sync, task features, ε-greedy policy (v1 + v2), web push, NATS JetStream bridge, shadow-policy registry, offline sim framework, per-user profile features, admin + ML ops console (apps/admin).

Phase 2 — AI tips + multi-source signals (M2) ✓ shipped

Tips are AI-generated from user context. Multi-agent pipeline (ADR-0013): five pre-compute agents (overdue-task, momentum, time-of-day, recent-patterns, focus-area) emit prompt snippets; orchestrator LLM produces one tip. Unified Profile + agent registry + auto-inference framework (ADR-0014). LLM output validation + fallback. LiteLLM gateway, model benchmarking, prompt research, MLflow tracing.

Phase 3 — Native mobile (M3)

iOS (SwiftUI + APNs) and Android (Compose + FCM). notifier service gains APNs + FCM channels. Auth migrated from Auth.js to dedicated OIDC provider. Decide-and-deliver scheduler. See M3 milestone.

Phase 4 — MLOps at scale (M4)

Retraining pipeline, feature-to-prompt batch jobs, prompt optimization loop, LLM fine-tuning on reaction signals, modular-monolith import-boundary lint, online experiments framework, drift monitoring. See M4 milestone.

Phase 5 — Production hardening (M5)

Audit logging, key rotation, k3s → k8s, multi-region, public integration SDK, billing. See M5 milestone.


Contributing

This repo is split into independent modules; most tickets belong to exactly one. Pick an issue from Gitea, read the service's README.md, ship.

Conventions and per-service guidance live in CLAUDE.md.

License

All rights reserved — 2026. Contact the owner for licensing inquiries. (We'll switch to an OSS license for non-sensitive packages once the public SDK lands in Phase 5.)

Description
One tip. Right now. Feels like magic.
Readme 1.7 MiB
Languages
TypeScript 56.9%
Python 42.4%
CSS 0.5%
JavaScript 0.1%
Shell 0.1%