Memory and Trends System
VITA extracts structured health data from PDFs (lab results, DEXA scans, VO2 max tests, imaging) and maintains longitudinal trend files for tracking changes over time. This enables pattern recognition across years of health data without re-reading source documents.
Scope note: This page documents the health-document trend pipeline and the precomputed view layer it feeds. Those are one slice of
memory/. The same directory also hosts sibling subsystems βmemory/entities/andmemory/episodes/(the hierarchical entity-memory consolidator),memory/codebase/(code-aware memory), andmemory/prompts/(the LLM prompt templates that build the views). The entity/episode consolidator is summarized at the end of this page; the codebase memory is out of scope here.
System Architecture
+---------------------------+
| Drop PDF in |
| memory/sources/{cat}/ |
+---------------------------+
|
v
+---------------------------+ +---------------------------+
| /update-trends |---->| Parallel Subagents |
| (compare vs manifest) | | (one per unprocessed PDF)|
+---------------------------+ +---------------------------+
|
+-------------------------+
| |
v v
+---------------------------+ +---------------------------+
| memory/extracted/ | | trends/*.md |
| {cat}/*.json | | (Markdown tables) |
| (Structured data) | | (Human-readable) |
+---------------------------+ +---------------------------+
| |
v v
+---------------------------+ +---------------------------+
| manifest.json | | memory/views/ |
| (Track processed files) | | health-snapshot.json |
+---------------------------+ +---------------------------+
|
v
+---------------------------+
| Daily brief / Briefing |
| /now, /checkin, /coach |
+---------------------------+
The
memory/views/-> brief/command flow is mediated by the view-rebuild engine: trend updates mark dependent views stale, the builder regenerates them from prompts, and consumers read the cached JSON.
Directory Structure
memory/sources/
Raw source documents organized by category:
memory/sources/
medical/ # Lab results, blood panels
2024-10-23.pdf
2025-03-29 Metabolic Panel.pdf
2025-06-15 Hormone Panel.pdf
imaging/ # MRI, CT, X-ray
2022-12-15 MRI WWO CONTRAST.docx
2024-03-04 MRI WWO CONTRAST.docx
performance/ # DEXA, VO2 max, RMR
2025-12-22 DEXA.pdf
2025-12-22 VO2 Max.pdf
Naming Convention: YYYY-MM-DD Description.pdf
memory/extracted/
Structured JSON extractions from source documents:
memory/extracted/
medical/
labs_2024-10-23.json
labs_2025-03-29_Metabolic_Panel.json
labs_2025-06-15_Hormone_Panel.json
imaging/
imaging_2022-12-15_MRI.json
imaging_2024-03-04_MRI_Followup.json
performance/
performance_2025-12-22_DEXA.json
performance_2025-12-22_VO2_Max.json
memory/_meta/
Metadata and tracking:
| File | Purpose |
|---|---|
manifest.json |
Maps sources to extracted files, tracks what has been processed |
staleness.json |
Tracks which views are marked dirty and need rebuilding |
view-registry.json |
Defines every view: path, prompt template, dependencies, staleness_days, priority, and consumers (plus a planned list of future views) |
build-log.jsonl |
Append-only log of view rebuilds |
view-access.jsonl |
Append-only log of view reads (access tracking) |
trends/
Longitudinal data tables in Markdown:
| File | Content |
|---|---|
hormones.md |
Hormone panels and endocrine markers |
metabolic.md |
Glucose, A1C, lipids, liver/kidney function, vitamins |
blood-counts.md |
CBC, white cell differential, red cell indices |
body-composition.md |
DEXA results - body fat, lean mass, bone density |
fitness.md |
VO2 max, RMR, heart rate zones, thresholds |
memory/views/
Precomputed, LLM-synthesized summaries β one JSON answer per recurring question, so consumers read a cached view instead of re-scanning raw data. The full set is declared in view-registry.json:
| View | Consumes (dependencies) | Staleness | Priority |
|---|---|---|---|
health-snapshot.json |
extracted/performance/*, extracted/medical/*, garmin/* |
1 day | high |
training-status.json |
garmin/*, activities/*, goals/active/* |
1 day | medium |
current-status.json |
garmin, objectives, commitments, training-status + goal-progress views | 0.5 day (12h) | high |
goal-progress.json |
goals/active/*, occurrence logs |
1 day | medium |
week-summary.json |
garmin/*, checkins/*, activities/* |
1 day | medium |
identity-snapshot.json |
profile/*, identity observations |
7 days | low |
ceo-scorecard.json |
ceo-mode dailies, transformation goal, heartbeat, commitments | 7 days | high |
Two views are declared in the registry's planned list but not yet built: hormone-history (longitudinal hormone trends, 30-day staleness) and body-composition-trend (7-day staleness).
How views get built is covered in The View-Rebuild Engine below.
Document Types Supported
Lab Results (medical/)
Blood panels from labs like Quest, LabCorp, Vibrant America:
- Metabolic panels (BMP, CMP) - glucose, electrolytes, kidney/liver function
- Lipid panels - cholesterol, LDL, HDL, triglycerides
- Hormone panels - endocrine markers, thyroid function
- CBC - complete blood count, white cell differential
- Specialty - omega-3 index, vitamin D, iron studies, inflammation markers
DEXA Scans (performance/)
Body composition analysis:
- Total body fat percentage
- Lean tissue mass (total and regional)
- Bone mineral density and Z-scores
- Visceral fat measurement
- RSMI (Relative Skeletal Muscle Index)
- Android/Gynoid fat ratio
- Historical comparison to previous scans
VO2 Max Tests (performance/)
Cardiorespiratory fitness testing:
- Peak VO2 (ml/kg/min)
- Percentile ranking for age/sex
- Aerobic threshold (AeT) and anaerobic threshold (AT)
- Heart rate zones with caloric burn rates
- Recovery heart rate
- Fitness classification (Poor to Superior)
RMR Tests (performance/)
Resting metabolic rate:
- Baseline caloric burn (kcal/day)
- Metabolic comparison to norm
- Caloric zones (weight loss, maintenance)
MRI/Imaging (imaging/)
Radiology reports:
- Document type classification
- Key findings extraction
- Size measurements where applicable
- Comparison to prior imaging
- Raw report text preserved
The /update-trends Command
The /update-trends command processes new PDFs and updates trend files.
Step 1: Find Unprocessed Files
Compares memory/sources/ against memory/_meta/manifest.json:
from scripts.utils import safe_read_json
import os
manifest = safe_read_json('memory/_meta/manifest.json', {"sources": {}})
# Build set of already-processed files
processed_set = set()
for category in manifest.get("sources", {}).values():
for entry in category:
processed_set.add(entry["source"])
# Find new files
new_files = []
for root, dirs, files in os.walk('memory/sources'):
for f in files:
if f.endswith(('.pdf', '.PDF', '.docx')):
rel_path = os.path.join(root, f)
if rel_path not in processed_set:
new_files.append(rel_path)
Step 2: Parallel Subagent Extraction
For each unprocessed file, a parallel subagent:
- Reads the PDF using the Read tool
- Identifies document type (lab results, DEXA, VO2 max, MRI, etc.)
- Extracts ALL data points with dates, values, units, reference ranges
- Writes structured JSON to
memory/extracted/{category}/
Why Subagents? PDF extraction is context-heavy. Running each in a separate agent keeps the main conversation context clean and allows parallel processing.
Step 3: Update Trend Files
For each extracted panel, the appropriate trend file is updated:
| Panel Type | Target Trend File |
|---|---|
| Metabolic Panel, BMP, CMP | trends/metabolic.md |
| CBC, Complete Blood Count | trends/blood-counts.md |
| Hormone panels | trends/hormones.md |
| DEXA, Body Composition | trends/body-composition.md |
| VO2 Max, Metabolic Cart | trends/fitness.md |
| MRI, Imaging | Notes in observations (may need manual review) |
Step 4: Update Manifest
After successful extraction, the manifest is updated:
from scripts.utils import safe_read_json, atomic_write
manifest = safe_read_json('memory/_meta/manifest.json', {"sources": {}, "version": 1})
category = "medical" # or "imaging", "performance"
if category not in manifest["sources"]:
manifest["sources"][category] = []
manifest["sources"][category].append({
"source": "memory/sources/medical/2025-06-15 Labs.pdf",
"extracted": "memory/extracted/medical/labs_2025-06-15_Labs.json"
})
atomic_write('memory/_meta/manifest.json', manifest)
Extraction Format
JSON Schema
Extracted files follow this structure:
{
"source_file": "medical/2024-10-23.pdf",
"extracted_at": "2025-12-29T17:30:00Z",
"document_type": "comprehensive_lab_panel",
"document_date": "2024-10-23",
"lab_name": "Example Labs",
"fasting": true,
"panels": [
{
"panel_name": "Lipid Panel",
"target_trend": "trends/metabolic.md",
"results": [
{
"marker": "Total Cholesterol",
"value": 185,
"unit": "mg/dL",
"reference_range": "<=199",
"status": "normal"
},
{
"marker": "LDL",
"value": 95,
"unit": "mg/dL",
"reference_range": "<=99",
"status": "normal"
},
{
"marker": "HDL",
"value": 82,
"unit": "mg/dL",
"reference_range": ">=56",
"status": "normal"
}
]
}
],
"observations": [
"All lipid markers within normal range",
"HDL excellent at 82 mg/dL"
],
"raw_text": "Full text transcription of the document..."
}
DEXA Extraction Example
Note: All values below are illustrative placeholders, not real readings.
{
"source_file": "performance/2025-12-22 DEXA.pdf",
"extracted_at": "2025-12-29T17:35:00Z",
"document_type": "dexa",
"document_date": "2025-12-22",
"facility": {
"name": "Example Fitness Lab",
"address": "123 Main St, Anytown, ST"
},
"patient": {
"height_inches": 70.0,
"age": "NN.N"
},
"panels": [
{
"panel_name": "Total Body Composition",
"target_trend": "trends/body-composition.md",
"results": [
{"marker": "Total Body Fat %", "value": 16.0, "unit": "%"},
{"marker": "Total Mass", "value": 185.0, "unit": "lbs"},
{"marker": "Lean Tissue", "value": 152.3, "unit": "lbs"},
{"marker": "Bone Mass", "value": 7.2, "unit": "lbs"}
]
},
{
"panel_name": "Bone Density",
"target_trend": "trends/body-composition.md",
"results": [
{"marker": "Total BMD", "value": 1.350, "unit": "g/cm2"},
{"marker": "Total BMD Z-Score", "value": 1.0, "unit": "z-score"}
]
}
],
"historical_comparison": {
"previous_scans": [
{
"date": "2024-01-15",
"total_body_fat_pct": 19.0,
"lean_tissue_lbs": 145.0
}
],
"changes_from_previous": {
"total_body_fat_pct": -3.0,
"lean_tissue_lbs": 7.3
}
},
"observations": [
"Body fat decreased 3.0% since last scan",
"Gained 7.3 lbs lean tissue",
"Bone density above average for age"
]
}
Status Values
| Status | Meaning |
|---|---|
normal |
Within reference range |
high |
Above reference range |
low |
Below reference range |
optimal |
In ideal range (subset of normal) |
borderline |
At edge of reference range |
Trend File Format
Trend files use Markdown tables with chronological data:
Example: trends/hormones.md
# Hormone Trends
Tracking hormone panel markers over time.
## Example Hormone Marker
| Date | Total (ng/dL) | Free (ng/dL) | Reference | Status |
|------|---------------|--------------|-----------|--------|
| Sep 13, 2021 | NNN | N.N | ref range | Low |
| Oct 23, 2024 | NNN | NN.N | ref range | Normal |
| Apr 4, 2025 | NNN | NN.N | ref range | Normal |
**Trend:** Marker improving significantly. Recovered to healthy mid-range.
## Example Secondary Marker
| Date | Value (pg/mL) | Reference | Status |
|------|---------------|-----------|--------|
| Sep 13, 2021 | NN.N | ref range | Low |
| Oct 23, 2024 | NN.N | ref range | Low |
| Apr 4, 2025 | NN.N | ref range | **High** |
**Note:** Marker elevated as of latest draw. Currently tracking.
---
## Observations
1. **Primary marker dramatically improved** - significant increase over tracking period
2. **Secondary marker now elevated** - Was low, now high. May need adjustment
3. **Thyroid stable** - All markers consistently normal
Key Elements
- Section headers for each marker category
- Markdown tables with Date, Value, Unit, Reference, Status columns
- Trend notes under each table summarizing patterns
- Observations section at the end for cross-cutting insights
- Bold/emphasis for values outside reference ranges
Workflow
Adding New Health Data
-
Drop PDF in the appropriate category:
# Lab results cp ~/Downloads/2025-06-15_Quest_Labs.pdf memory/sources/medical/ # DEXA scan cp ~/Downloads/2025-06-15_DEXA.pdf memory/sources/performance/ # MRI report cp ~/Downloads/2025-06-15_MRI.pdf memory/sources/imaging/ -
Run /update-trends in Claude:
> /update-trends -
Review output:
+-- Update Trends Complete ---------------- Processed 2 files: 1. 2025-06-15 Quest Labs.pdf -> trends/hormones.md (3 markers) -> trends/metabolic.md (12 markers) 2. 2025-06-15 DEXA.pdf -> trends/body-composition.md Files remaining: 0 +-----------------------------------------+ -
Data appears in:
memory/extracted/{category}/*.json- structured extractiontrends/*.md- human-readable tablesmemory/views/health-snapshot.json- after next view rebuild
Backfilling Historical Data
For older documents not yet in the system:
- Gather all PDFs into appropriate
memory/sources/directories - Run
/update-trends- it processes all unprocessed files - Review trend files for any extraction issues
- Manually correct any misextracted data if needed
Integration with Other Systems
Health Snapshot View
The health-snapshot view (memory/views/health-snapshot.json) consumes extracted data:
{
"dependencies": [
"memory/extracted/performance/*.json",
"memory/extracted/medical/*.json",
"data/garmin/*.json"
]
}
When trend data updates, the health snapshot is marked stale and rebuilt to reflect: - Latest body composition from DEXA - Hormone status from labs - Combined with daily Garmin metrics
Daily Brief
The morning brief surfaces relevant health data: - Flags if labs are overdue (e.g. months since last draw) - Notes lab values outside reference range - References body composition changes
Note: The live morning-brief path is the Living Briefing (API on port 33801), continuously updated through the day. The older VDB newsletter path still exists but is largely dormant; for exact trigger timing and which path is active, see Garmin Integration, which is the authoritative source for the brief schedule.
Commands That Read Trends
| Command | Uses Trends For |
|---|---|
/now |
Health snapshot summary |
/checkin |
Context for daily check-in questions |
/coach |
Goal coaching with health context |
/insights |
Pattern analysis across data sources |
The View-Rebuild Engine
Views are not hand-written β each is regenerated by an LLM from a per-view prompt template. The builder is scripts/memory/build_view.py.
# Rebuild a single view (force ignores staleness)
python scripts/memory/build_view.py health-snapshot --force
# Rebuild every stale view
python scripts/memory/build_view.py --all
How a build runs (build_view.py):
- Resolve the prompt template for the view id. The builder loads
memory/prompts/build-<view_id>.md(e.g.build-health-snapshot.md); it raises if no prompt exists. - Gather the view's source data (the dependency globs from
view-registry.json) and substitute it into the prompt's{placeholder}slots; unfilled placeholders are warned about. - Hand the filled prompt to an LLM, which reads the current view file and rewrites it with fresh data.
- Stamp the output with
built_at, therefresh_contract, and aprompt_hash(first 16 hex of the SHA-256 of the prompt template) so a prompt change can invalidate a view.
Tip: A reimplementer can treat each view as
prompt template + dependency list -> JSON. The registry'spromptfield names the template,dependenciesnames the inputs, andconsumersnames who reads the result.scripts/memory/build_view_direct.pyis a lighter, non-agentic variant;scripts/memory/view_loader.pyis the read-side helper consumers use to load a view (and record access).
Every view file carries a refresh_contract, for example:
{
"refresh_contract": {
"max_age_days": 1,
"owner": "scripts/memory/build_view.py",
"rebuild_trigger": "/sleep nightly | reactive (garmin sync)",
"input_sources": ["data/garmin/", "memory/extracted/performance/", "data/cgm/"]
}
}
Freshness and Staleness (the lying-instrument antibody)
Two complementary mechanisms keep views from silently going stale or rendering empty data as real:
| Mechanism | File | What it does |
|---|---|---|
| Event-driven staleness | scripts/memory/staleness.py |
When a source-data category changes, marks the dependent views dirty. Uses an explicit CATEGORY_VIEW_MAP (e.g. garmin -> health-snapshot, training-status, week-summary, current-status; cgm -> health-snapshot, current-status) plus a registry dependency scan, writing to memory/_meta/staleness.json. |
| Passive freshness | scripts/memory/freshness.py |
Age check: compares each view's built_at to its refresh_contract.max_age_days (default 7 if absent) and renders a flag β ok, STALE, or UNKNOWN (when built_at can't be parsed). |
| Reactive rebuild | scripts/memory/reactive.py |
Debounced, dependency-aware inline rebuilds so a fresh source triggers a rebuild without waiting for the nightly cycle. |
# Print the freshness report (optionally only stale views)
python -m scripts.memory.freshness --stale-only
This trio is part of the system's "lying instrument" defenses (see Sleep Guardian and the executive loop): an UNKNOWN view never renders as 0, and the freshness flag is checked before a stale value is surfaced. A fresh timestamp on a view file is not proof the engine underneath is alive β the freshness guard makes that explicit. The freshness report feeds /sleep and the e-ink display's freshness guard.
Manifest Structure
The manifest tracks all processed files. The stats block below is illustrative (counts grow as documents are added) β it is not a live reading:
{
"version": 1,
"description": "Maps source files to their extracted JSON representations",
"generated_at": "2026-01-02T13:00:00Z",
"sources": {
"medical": [
{
"source": "memory/sources/medical/2024-10-23.pdf",
"extracted": "memory/extracted/medical/labs_2024-10-23.json"
}
],
"imaging": [
{
"source": "memory/sources/imaging/2024-03-04 MRI WWO CONTRAST.docx",
"extracted": "memory/extracted/imaging/imaging_2024-03-04_MRI_Followup.json"
}
],
"performance": [
{
"source": "memory/sources/performance/2025-12-22 DEXA.pdf",
"extracted": "memory/extracted/performance/performance_2025-12-22_DEXA.json"
}
]
},
"stats": {
"total_sources": 19,
"total_extracted": 22,
"categories": {
"medical": {"sources": 11, "extracted": 14},
"imaging": {"sources": 2, "extracted": 2},
"performance": {"sources": 6, "extracted": 6}
}
}
}
Error Handling
| Scenario | Handling |
|---|---|
| Subagent fails to extract | Error logged, file skipped, not marked processed |
| Unreadable PDF | Subagent notes error in JSON, continues with raw text if available |
| Unknown document type | Sets document_type: "other", flags for manual review |
| Missing trend file | Creates from template with initial data |
| Duplicate processing | Manifest check prevents re-extraction |
File Locations Summary
| Location | Purpose |
|---|---|
memory/sources/{category}/ |
Raw PDFs to process |
memory/extracted/{category}/ |
Structured JSON extractions |
memory/_meta/manifest.json |
Processing status tracking |
memory/_meta/staleness.json |
View dirty-marking |
memory/_meta/view-registry.json |
View definitions (path, prompt, deps, consumers) |
memory/_meta/build-log.jsonl / view-access.jsonl |
View rebuild / read logs |
memory/views/ |
Precomputed view summaries |
memory/prompts/build-*.md |
Per-view LLM build prompts |
memory/entities/ , memory/episodes/ |
Entity-memory consolidator (see below) |
trends/*.md |
Longitudinal data tables |
scripts/memory/build_view.py |
View-rebuild engine |
scripts/memory/freshness.py / staleness.py / reactive.py |
Freshness/staleness/reactive-rebuild |
scripts/memory/consolidator.py |
Entity/episode consolidator |
.claude/commands/update-trends.md |
Command implementation |
Entity Memory and Episodes (sibling subsystem)
Alongside the trend pipeline, memory/ maintains a markdown "memory filesystem" of entities and daily digests, built by scripts/memory/consolidator.py:
memory/entities/{people,projects,topics}/*.mdβ one consolidated markdown file per entity.memory/episodes/β daily episode digests.
It runs as part of the nightly /sleep cycle: it reads the day's data sources, extracts entity mentions, updates the entity files, and writes digests. Entity files are themselves indexed by hybrid Search (as source_type=entity_file), so they become retrievable long-term memory.
# Subcommands: seed (build from scratch), consolidate (daily update),
# index (rebuild the entity index), all (seed + consolidate; default)
python -m memory.consolidator all
Data Handling and Privacy
Warning: Source PDFs, extracted JSON, trend tables, and view files all contain personal health information. They are committed to the repository, not kept local-only.
The repository is private, and .gitignore was deliberately rewritten (2026-01-27) to commit nearly everything for full snapshotting β the stated rationale is "we commit EVERYTHING for full snapshotting; git history IS our safety net for autonomous overnight work." Only secrets and credentials are gitignored (secrets/, *.pem, *.key, *_token.json, .env*, and similar).
Practical consequences for a reimplementer:
memory/sources/,memory/extracted/,trends/*.md, andmemory/views/are version-controlled and live in git history. Treat the repo's access controls β not gitignore β as the privacy boundary.- Source filenames themselves can be revealing (a filename can disclose what was tested). If you adapt this system, decide consciously whether to commit raw health documents or keep them out of git; the choice here is "commit, but keep the repo private."
- For genuinely sensitive material that should never sit in plaintext, vita uses a separate encrypted vault rather than
memory/.
Where to Go Next
- Search β hybrid index that makes extracted data and entity files retrievable as long-term memory.
- Garmin Integration β daily wearable metrics that views combine with lab/DEXA data; authoritative source for brief timing.
- Living Briefing β the live morning-brief path that consumes these views.
- Sleep Guardian β another consumer of the "lying instrument" / freshness discipline.
- Document Filing β the broader inbound-document routing this pipeline is one destination of.