Skip to content

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/ and memory/episodes/ (the hierarchical entity-memory consolidator), memory/codebase/ (code-aware memory), and memory/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)

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 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:

  1. Reads the PDF using the Read tool
  2. Identifies document type (lab results, DEXA, VO2 max, MRI, etc.)
  3. Extracts ALL data points with dates, values, units, reference ranges
  4. 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

  1. Section headers for each marker category
  2. Markdown tables with Date, Value, Unit, Reference, Status columns
  3. Trend notes under each table summarizing patterns
  4. Observations section at the end for cross-cutting insights
  5. Bold/emphasis for values outside reference ranges

Workflow

Adding New Health Data

  1. 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/
    

  2. Run /update-trends in Claude:

    > /update-trends
    

  3. 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
    +-----------------------------------------+
    

  4. Data appears in:

  5. memory/extracted/{category}/*.json - structured extraction
  6. trends/*.md - human-readable tables
  7. memory/views/health-snapshot.json - after next view rebuild

Backfilling Historical Data

For older documents not yet in the system:

  1. Gather all PDFs into appropriate memory/sources/ directories
  2. Run /update-trends - it processes all unprocessed files
  3. Review trend files for any extraction issues
  4. 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.

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):

  1. 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.
  2. 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.
  3. Hand the filled prompt to an LLM, which reads the current view file and rewrites it with fresh data.
  4. Stamp the output with built_at, the refresh_contract, and a prompt_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's prompt field names the template, dependencies names the inputs, and consumers names who reads the result. scripts/memory/build_view_direct.py is a lighter, non-agentic variant; scripts/memory/view_loader.py is 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, and memory/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.