Health tracking
Body stats, bloodwork panels, biomarker trends, Garmin Connect daily summaries, immunization registry, and medical history. Per-user SQLite on local disk at Config.module_db_path(user, "health") (default {db_path.parent}/modules/{user}/health.db); user-facing uploads stay in the workspace on the mount. All measurements stored metric (kg, cm, °C, mmHg, bpm); the display layer converts to the user's preferred units.
Health is an on-by-default module with per-user opt-out via disabled_modules in user settings.
Features
Body stats — Time series for weight, blood pressure, resting HR, body fat %, body temp, respiratory rate, SpO2. Manual logging via skill CLI or auto-populated from Garmin sync. Unit-aware input (accepts lb, °F, etc. and converts at log time). BMI derived from latest weight + profile height.
Bloodwork — Panel ingestion from three sources: drag-and-drop OCR upload (PDF/image → LLM extraction with review-and-confirm), CSV bulk import, or manual entry via skill CLI. 60+ canonical biomarkers with sex-specific reference ranges, alias normalization, and auto-flagging (H/L/C). Blood-pressure and resting-HR biomarker rows fan out to the stats time series.
Biomarker trends — Per-marker trend charts with out-of-range zones shaded. LLM-generated educational explainer cards for flagged markers (never diagnoses or prescriptions — hard guardrails in the prompt). Explainers cached per-user per (name, direction).
Garmin Connect — OAuth connection via the general Settings → Connected services page. Garmin is a cross-module connected service, shared with the location track importer — connect once and both features use the same token. Sync runs on demand (the "Sync health data" button on that Garmin card, the garmin-sync skill CLI, or a user-configured CRON job), not on an automatic schedule. It pulls sleep (duration, score, stages), stress, body battery, steps, active calories, SpO2, HRV, VO2 max, and respiration. A multi-day backfill is available but not auto-triggered on connect.
Immunizations — Registry of administered vaccines with date, product, manufacturer, lot, site, route, and facility. Bundled canonical vaccine reference list with recommended schedules. Coverage tracker shows due-soon and overdue immunizations. Bulk import from MyChart/clipboard paste with dry-run preview. Static educational explainers per vaccine.
Medical history — Encounters (doctor visits, procedures, screenings, hospitalizations) and diagnoses (active, resolved, chronic) with ICD-10 codes. history-summary command generates a new-doctor packet.
A condition is linked to every appointment it was seen at, not just one: something a GP found, a specialist was referred for and a follow-up reviewed carries all three, and appears under each of them. Link and unlink from either side — an encounter's page lists its conditions, a condition lists its appointments — and unlinking keeps both records; removing the last link does not delete the condition. Links recorded before this existed are carried over on first upgrade.
Setup
Health requires no additional configuration — it's enabled by default for all users. To disable for a specific user, add health to their disabled_modules list in user settings or via the web UI Preferences page.
Install optional dependencies for full functionality:
uv sync --extra all
# or specifically for OCR:
uv sync --extra transcribe # pytesseract + Pillow
OCR upload also requires pdftotext (from poppler-utils) for PDF text extraction.
Database
Per-user SQLite on local disk (see above). Tables:
| Table | Purpose |
|---|---|
stats | Body stat time series (metric, value, unit, date, source) |
panels | Bloodwork panels (drawn_at, lab, type, draft/confirmed, content_hash) |
biomarkers | Individual biomarker results linked to panels |
biomarker_explainers | Cached LLM explainer text per (name, direction) |
biomarker_refs | Bundled canonical biomarker reference ranges and aliases |
immunizations | Vaccine administration records |
immunization_refs | Bundled canonical vaccine reference list and schedules |
encounters | Medical encounters (visits, procedures, screenings) |
diagnoses | Diagnoses with status (active, resolved, chronic) |
diagnosis_encounters | Which appointments a condition was seen at (many-to-many) |
health_settings | Key/value store for profile (DOB, height, sex) and unit display preferences |
documents | Stored paperwork — scans, discharge summaries, vaccination cards |
document_links | Which records a document evidences (encounter / diagnosis / immunization) |
Document bytes live at {workspace}/health/uploads/documents/{id}/{filename} — beside the panel sources, under the one path root the file-serving routes guard. A document is stored once and can be linked to several records, so a visit summary covering a diagnosis and a vaccine given at the same visit is one file, not three copies. Size is capped by [health] max_document_bytes (default 25 MiB; 0 = unlimited). A document that ends up attached to nothing is deleted 24 hours after the last thing that referenced it — long enough that detaching a file on the way to re-attaching it elsewhere is safe, short enough that an import you abandoned doesn't linger.
Garmin Connect OAuth tokens are not stored here — they live in the framework-level encrypted secrets table under service="garmin" (Fernet via ISTOTA_SECRET_KEY).
Web pages
| Path | Content |
|---|---|
/health/stats | Netdata-style sparkline grid for all body stats |
/health/bloodwork | Dates-as-rows × markers-as-columns spreadsheet with category bands |
/health/bloodwork/panel?id=… | Panel detail with inline-edit table and source preview |
/health/bloodwork/upload | Drag-and-drop OCR review-and-confirm |
/health/bloodwork/marker?name=… | Trend chart, related markers, clinical description, explainer card |
/health/immunizations | Registry table, coverage status, import controls |
/health/history/encounter?id=… | Encounter detail — linked diagnoses, panels, and its documents |
/health/history/diagnoses | Conditions list; a paperclip badge opens that condition's documents |
/health/immunizations/detail?id=… | Per-record edit plus "Proof of immunization" |
/health/settings | DOB/height/sex, display preferences (Garmin connect/sync lives on Settings → Connected services) |
Skill CLI
The health skill exposes istota-skill health <subcommand>. Key subcommands:
log,stats,latest— body stat CRUD and queriespanels,panel,add-panel,add-biomarker— bloodwork managementtrend,summary— biomarker analysisupload,import-csv,export-csv— bulk data operationssettings,set— profile and display preferencesencounters,add-encounter,update-encounter,delete-encounter— medical visitsdiagnoses,diagnosis,add-diagnosis,resolve-diagnosis,update-diagnosis,delete-diagnosis— conditionslink-encounter,unlink-encounter— attach a condition to an appointment it was seen at, or detach it (both records are kept)immunizations,add-immunization,update-immunization,delete-immunization— vaccine recordsvaccine-refs,coverage,explain-immunization— reference data and coverageimport-immunizations— bulk import from clipboard/pastegarmin-status,garmin-sync,garmin-disconnect— Garmin integrationdocuments,document,attach-document,detach-document— file paperwork against a record
Attaching takes a TYPE:ID token: istota-skill health attach-document --path ~/inbox/card.jpg --to immunization:5. Types are encounter, diagnosis, immunization. The agent attaches files the user supplied; deleting a document stays a web-UI action behind a confirmation, since it removes the file from every record it is attached to.
Immunization coverage status
coverage resolves each vaccine in the registry to one of seven states, from its schedule kind and the doses on record:
| Status | Meaning |
|---|---|
up_to_date | Covered, with nothing due inside the 30-day window |
due_soon | Next dose falls inside the 30-day window (annual, every_10y) |
overdue | Next dose has passed (annual, every_10y) |
series_incomplete | The series was started and has doses left |
never_recorded | No dose of this vaccine has been recorded |
expired | A travel_pre_trip vaccine whose interval has run out |
risk_based | Recommended on risk factors rather than a schedule, with no dose recorded — never auto-flagged as missing |
series_incomplete means what it says: you started the series and have doses left. A series vaccine with zero recorded doses is never_recorded. It used to read part-done from the start, so an empty immunizations page opened on ten vaccines that looked half-finished, and the health summary the model reads listed all ten as needing action.
A schedule kind the code does not recognise falls back to never_recorded with no dose and up_to_date with one.
All mutating operations are deferred under sandbox (written to task_<id>_health_ops.json, replayed post-task by the scheduler).
Privacy
Health data is the most sensitive data in the system. The health DB is the single source of truth.
- Quantitative health data (measurements, biomarker values, lab dates, symptoms) must never be written to USER.md, the knowledge graph, dated memories, or KV.
- Biomarker values are excluded from briefings and log channels.
- Source files for uploaded labs are served only through the auth-gated
/panels/{id}/sourceroute; attached documents likewise through/documents/{id}/file. Neither is ever a public share link. Documents are always served as a download withnosniff, because one may have arrived from outside (an email attachment the agent filed). - Stable identity-level medical facts (allergies, named chronic conditions) belong in the knowledge graph; detailed records stay in the health DB.