docs: Helios design spec
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
148
docs/superpowers/specs/2026-08-18-helios-design.md
Normal file
148
docs/superpowers/specs/2026-08-18-helios-design.md
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# Helios — design spec
|
||||||
|
|
||||||
|
2026-08-18 · status: approved pending Marcus's review
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Helios is a self-hosted, MIT-licensed web app: a FOSS alternative to Illume
|
||||||
|
Labs / Superpower. One Docker container per user (or household). The pitch:
|
||||||
|
**your health data lives on your server, and the AI runs on your key.**
|
||||||
|
|
||||||
|
v1 features:
|
||||||
|
|
||||||
|
- Connect wearables with the user's own credentials: Oura, Whoop, Fitbit
|
||||||
|
(official APIs), Garmin (unofficial library, clearly labeled).
|
||||||
|
- Upload lab PDFs → LLM extraction → user reviews/confirms → normalized
|
||||||
|
biomarkers with units, reference ranges, and flags.
|
||||||
|
- Log meals by photo or text → LLM macro estimate → editable card.
|
||||||
|
- Grounded AI chat: tool-calls into the local DB, so only relevant data
|
||||||
|
slices are sent to the LLM — never a full dump.
|
||||||
|
- Daily brief generated at a user-chosen hour from the last 24 h + 30-day
|
||||||
|
trends + flagged labs.
|
||||||
|
- Dashboards: Today (brief + cards), Sleep, Training, Labs, Nutrition,
|
||||||
|
Settings.
|
||||||
|
|
||||||
|
Non-goals for v1: multi-user SaaS, mobile app (planned fast-follow for
|
||||||
|
HealthKit/Health Connect sync), Apple Health ingestion (needs the companion
|
||||||
|
app), medical advice claims of any kind.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
API-first monolith. The web UI is a pure client of a typed REST API — that
|
||||||
|
API is the contract a future mobile app reuses unchanged.
|
||||||
|
|
||||||
|
```
|
||||||
|
Web UI (Vite + React SPA)
|
||||||
|
Today/brief · Chat · Sleep · Train · Labs · Nutrition · Settings
|
||||||
|
│ typed REST (Zod-validated)
|
||||||
|
Server (Bun + Hono, one process)
|
||||||
|
vendor pollers (in-process cron, per-token, with backfill)
|
||||||
|
lab PDF → LLM extraction → review queue
|
||||||
|
meal photo/text → LLM macro estimate
|
||||||
|
chat orchestrator (typed tools over the DB)
|
||||||
|
daily brief generator (cron)
|
||||||
|
│
|
||||||
|
SQLite (single file, WAL, Drizzle ORM) + uploads dir on one /data volume
|
||||||
|
LLM: any OpenAI-compatible endpoint — OpenRouter documented default,
|
||||||
|
Ollama/LM Studio for zero-third-party operation
|
||||||
|
```
|
||||||
|
|
||||||
|
Stack rationale: TypeScript end to end (shared types between API, UI, and
|
||||||
|
LLM tool schemas); SQLite over Postgres because a per-user instance needs
|
||||||
|
one-file backup and zero ops (Open WebUI ships the same way); SPA over
|
||||||
|
Next.js because an API-first self-hosted app gains nothing from SSR and the
|
||||||
|
SPA keeps the mobile-later contract honest.
|
||||||
|
|
||||||
|
`health-api` (marcus/health-api) is **reference only**: its lab
|
||||||
|
normalization (`normalize.ts`, `units.ts`) and schema shapes inform clean
|
||||||
|
re-implementations. No code copied with Marcus-specific assumptions, no
|
||||||
|
personal data, MIT throughout.
|
||||||
|
|
||||||
|
## Data model (Drizzle/SQLite)
|
||||||
|
|
||||||
|
- `settings` — LLM base URL + key, vendor tokens, brief hour. Secret
|
||||||
|
columns encrypted (see Security).
|
||||||
|
- `daily_metrics` — per-day, per-source rollups: steps, resting HR, HRV,
|
||||||
|
active calories, readiness/recovery scores.
|
||||||
|
- `sleep_sessions` — start/end, stage durations, scores; raw vendor JSON
|
||||||
|
retained in a `raw` column.
|
||||||
|
- `workouts` — type, duration, load metrics; raw JSON retained.
|
||||||
|
- `lab_draws` + `biomarkers` — draw metadata; marker slug, value,
|
||||||
|
normalized unit, reference range, flag.
|
||||||
|
- `meals` — timestamp, photo path, macro estimate, LLM confidence,
|
||||||
|
user-edited flag.
|
||||||
|
- `chat_threads` + `chat_messages`.
|
||||||
|
- `briefs` — generated markdown + input snapshot.
|
||||||
|
- `sync_state` — per-connector cursor/watermark for incremental polls.
|
||||||
|
|
||||||
|
## Connectors
|
||||||
|
|
||||||
|
| Vendor | Path | User setup |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Oura | official API | paste personal access token |
|
||||||
|
| Whoop | official API | register own free dev app, paste client id/secret (documented 5-min flow) |
|
||||||
|
| Fitbit | official API | same own-app flow as Whoop |
|
||||||
|
| Garmin | unofficial `garmin-connect` lib | username/password; labeled "may break without notice" |
|
||||||
|
|
||||||
|
Pollers run on an in-process cron; first connect triggers historical
|
||||||
|
backfill; `sync_state` makes polls incremental. Every connector normalizes
|
||||||
|
into the tables above and keeps raw vendor JSON.
|
||||||
|
|
||||||
|
Labs: PDF upload → LLM extraction prompt → **review/confirm screen** —
|
||||||
|
nothing writes to `biomarkers` until the user approves the parsed values.
|
||||||
|
Trust in medical numbers requires the human check.
|
||||||
|
|
||||||
|
Nutrition: photo or free-text → LLM estimates items + macros (Open Food
|
||||||
|
Facts as grounding where possible) → editable card; edits stored, model
|
||||||
|
estimate retained for calibration.
|
||||||
|
|
||||||
|
## AI layer
|
||||||
|
|
||||||
|
- Provider abstraction: base URL + key + model id; anything
|
||||||
|
OpenAI-compatible works. OpenRouter is the documented default; Ollama is
|
||||||
|
the fully-local option.
|
||||||
|
- Chat tools (typed, Zod-schema'd): `query_sleep`, `query_metrics`,
|
||||||
|
`query_workouts`, `query_labs`, `query_meals`, `log_meal`. Tools return
|
||||||
|
bounded slices (date-ranged, capped row counts).
|
||||||
|
- Daily brief: cron at the user's hour; prompt assembles yesterday's data,
|
||||||
|
30-day trends, and out-of-range biomarkers; output stored in `briefs` and
|
||||||
|
rendered on Today.
|
||||||
|
- Honest privacy wording everywhere (README + Settings): chat context is
|
||||||
|
sent to the LLM endpoint the user chose. Only Ollama-class local
|
||||||
|
endpoints mean zero third parties.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- Auth: single password (argon2id), rate-limited login, httpOnly session
|
||||||
|
cookie. Passkeys are a later upgrade.
|
||||||
|
- Secrets at rest: AES-256-GCM column encryption; key generated at first
|
||||||
|
boot into the /data volume (`secret.key`).
|
||||||
|
- The SQLite file itself is not app-encrypted in v1 (bun:sqlite lacks
|
||||||
|
SQLCipher); docs prescribe disk encryption + encrypted backups. Column
|
||||||
|
crypto covers the credential-theft blast radius.
|
||||||
|
- No telemetry, no update phone-home. Outbound traffic only: chosen LLM
|
||||||
|
endpoint + connected vendor APIs.
|
||||||
|
- CSP on the SPA; uploads served with safe content-type handling.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- Vitest: unit normalization (units/ranges/marker slugs), connector
|
||||||
|
transforms against recorded vendor fixtures, tool-schema round-trips.
|
||||||
|
- API integration tests over an in-memory SQLite.
|
||||||
|
- CI: Gitea Actions — typecheck + tests on push.
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
- One Dockerfile (bun base), `docker compose up`, single `/data` volume.
|
||||||
|
- Caddy reverse-proxy recipe in docs.
|
||||||
|
- Repo: git.rehbock.xyz/marcus/helios (public). GitHub mirror optional
|
||||||
|
later for discoverability.
|
||||||
|
|
||||||
|
## Milestones
|
||||||
|
|
||||||
|
1. Scaffold: repo, CI, Docker, auth, settings (LLM + encryption plumbing).
|
||||||
|
2. First connector (Oura) end to end: poll → normalize → Sleep/Today UI.
|
||||||
|
3. Chat + tools over whatever data exists.
|
||||||
|
4. Labs pipeline (upload → extract → review → dashboard).
|
||||||
|
5. Meals + daily brief.
|
||||||
|
6. Whoop/Fitbit/Garmin connectors; docs site; v0.1 announcement.
|
||||||
Reference in New Issue
Block a user