Skip to main content

Context Assembly

How user-provided data becomes AI prompts. This is the core intelligence layer — every AI feature is only as good as the context it receives.

The Big Picture

Every AI call passes the same shape: { system, messages, maxTokens, onStream? }. The caller assembles the prompt; the router just delivers it. In-app AI context is filtered by Manage → Context → Data sources. Those per-profile settings live in importedData.contextSourceSettings and are read through context-source-registry.js / lab-context.js. Agent Access is intentionally different: the gateway push is controlled by Settings → Agent Access and builds its encrypted summary with ignoreContextToggles: true.

Data Sources

Everything the AI can know about the user comes from these sources:
Lab context consumes the projected getActiveData() view, so user-selected category organization can affect section grouping and display labels. Marker resolution still follows storageDotKey; changing a visual category does not duplicate a value, change its history, or alter the canonical references used by prompts and scoring.

buildLabContext() — The Central Serializer

buildLabContext() in lab-context.js is the single function that converts profile data into a plain-text block. For in-app callers it respects Context source toggles: Insight Context Cards, Supplements & Medications, Blood marker results, specialty lab groups, Genome tiers, Light & Sun, and Wearable recovery context can each be omitted. Agent Access calls the same serializer with skipGroupFilter: true and ignoreContextToggles: true after its own external-sharing permission is enabled. The Focus Card uses the lighter buildFocusContext() instead.

Output Structure

Sections are ordered by priority — the AI sees “what are you trying to solve?” first, then “what do the numbers say?”, then medical/lifestyle context. This exploits primacy bias in LLMs. The example below assumes the relevant Context sources are enabled. Each section is wrapped in [section:name]...[/section:name] tags for machine-parsable extraction (used by getbased-mcp, Hermes Agent, OpenClaw). Flagged results use [critical]...[/critical]. Lab sections include an updated:date attribute.

Genome context tiers

lab-context.js wraps enabled Genome output in [section:genetics]. The three source controls map to independent buildGeneticsContext() options: Each priority line carries association direction, evidence grade, and relevance grade. Routine context intentionally leaves out the longer evidence.scope and relevance.context prose. When more than 12 eligible findings exist, the serializer states how many were omitted and tells the model to use a focused gene/rsID question. buildFullGeneticsContext() enables the complete inventory, full evidence details, and an unbounded priority list for explicit Genome questions. Ask AI on one row uses the narrower buildSnpAIInterpretationPrompt() path: it includes only the selected call’s supported claim, interpretation context, sources, and available profile context in an editable user prompt. Both paths state that the catalog is a grounded baseline rather than a limit on broader model knowledge and require the model to distinguish evidence from inference.

Empty-Card Guards

Cards with no meaningful content are omitted entirely (not sent as empty sections). This happens after the Insight Context Cards source gate. Each card checks for actual data:
  • diagnoses: conditions array not empty OR note has text
  • diet: type set OR any meal content OR note
  • exercise: frequency set OR types array not empty OR note
  • sleepRest: duration set OR quality set OR issues not empty OR note
  • stress: level set OR sources not empty OR note
  • loveLife: status set OR libido set OR concerns not empty OR note
  • environment: setting set OR water set OR air not empty OR note

Data Flow Diagram

Prompt Composition Per Feature

1. Chat Panel (sendChatMessage)

The most complex composition — layers 4 components. Lab context is placed before personality so the AI processes data first, then adopts the persona:

2. Focus Card (loadFocusCard)

Uses the lightweight buildFocusContext() (~200-400 tokens) instead of full buildLabContext() (~2000-8000 tokens). Health-goals-aware system prompt:

3. Context Card Health Dots (loadContextHealthDots)

Requests structured JSON for only the stale (changed) cards. JSON.parse is wrapped in try-catch — on malformed AI responses, stale cards get gray dots while cached good data is preserved:
The implementation lives in context-card-health-dots.js; context-cards.js keeps compatibility exports and owns the visible enable/disable controls. Normal profiles can recalculate stale cards whenever AI is available. Demo profiles follow a stricter policy:
  • Bundled summaries, dots, and tips are seeded into the per-profile cache at demo import, so ordinary exploration uses no inference.
  • A non-cloud Ollama/local model recalculates stale demo cards automatically.
  • Cloud and other potentially paid providers require explicit consent for the active demo profile, provider, and model. Consent is held only in memory and is cleared on reload or provider/model change.
  • Without live consent, an edited card is retained but its stale insight is marked as not recalculated.
The cache stores a fingerprint per card. A fingerprint includes lab entries, the card value, shared context notes and interpretive lens, sex, and date of birth; only changed cards are sent on the next live recalculation.

Context-card field presence

buildLabContext() distinguishes unanswered values from explicit answers. Falsy/absent fields are omitted, but stored strings such as none and normal are meaningful evidence and must be serialized. In particular, the Diet & Digestion block includes explicit negative symptom answers and normal appetite, while Light & Circadian includes skinType. This formatter is shared by in-app AI and sync-messenger.js. The in-app path honors source toggles. Agent Access calls it with ignoreContextToggles: true because external sharing is controlled by the separate Agent Access permission; the plaintext output is then encrypted into the gateway envelope.

4. PDF Import (parseLabPDFWithAI)

Completely different context — no user profile, just schema + raw PDF. Filename is included in the user message for multi-file disambiguation:

5. Persona Generator (generateCustomPersonality)

Standalone creative prompt — no lab data involved:

6. Per-Marker AI (askAIAboutMarker)

Not a separate API call — builds a user message and injects it into the chat panel. Uses effective (phase-aware) reference ranges and includes trend direction when 2+ values exist:

Caching Strategy

Each AI feature has independent caching to avoid redundant API calls:

Token Budget

Key Design Decisions

  1. Two serializers: buildLabContext() for full context (Chat, Health Dots) and buildFocusContext() for slim context (Focus Card, ~200-400 tokens). The focus card only needs flagged markers and notable changes for a 40-word response.
  2. Priority-ordered sections: buildLabContext() outputs sections in priority order — goals and lens first, then lab values and flags, then medical context, then lifestyle cards. This exploits LLM primacy bias so the most important context gets the most attention.
  3. Data before persona: In Chat, lab context is placed before the personality layer in the system prompt. This ensures the AI processes the medical data first, then adopts the persona style.
  4. Priority-tiered system prompt: CHAT_SYSTEM_PROMPT uses a 4-tier structure (Core Rules → Priority Context → Lifestyle Context → Style) instead of a flat bullet list. Health goals and interpretive lens are at the top of Priority Context.
  5. Empty-card guards: Cards with no meaningful content are completely omitted rather than sent as empty sections. Each card has a specific content check (not just truthiness).
  6. Phase-aware values: Estradiol, progesterone, LH, and FSH can include per-date phase labels and phase-specific reference ranges inline. Explicit recorded draw context wins. Prediction requires a female profile with an active natural regular cycle, period history, and no hormonal contraception; uncertain and non-cycling contexts are omitted.
  7. Effective ranges: askAIAboutMarker() uses the dated effective range—phase, collection-context, personal/lab, or static fallback—not blindly the static schema range. It also includes trend direction with percentage change.
  8. Robust JSON parsing: loadContextHealthDots() wraps the AI response JSON.parse in try-catch. On malformed responses, stale cards get gray dots while cached good data is preserved.
  9. Context source gates: In-app Chat, Biology Score context, and source-driven warnings respect contextSourceSettings. Current Focus uses its own compact serializer and currently honors the lab, insight-card, supplements, and specialty-lab group gates it imports directly. Agent Access does not reuse the in-app gates; it has a separate Settings permission and pushes an encrypted full-profile summary only when enabled.
  10. Bounded therapy projection: supplement-context.js owns therapy selection and formatting. Compact context has a 6,500-character ceiling, detail mode has 12,000, and Biology Score records have 4,500. Compact mode selects current records or periods overlapping the lab range; a therapy-specific query can select bounded history. Quality-result AI toggles are respected in every projection, passing duplicate potency rows default out, and detailed methods/limits appear only in detail mode.
  11. Explicit collection context only: [section:labCollectionContext] includes known sample time, fasting state, cycle day, and recorded phase by lab date. Missing fields stay omitted. The serializer does not reinterpret processing/report time as collection time or infer fasting from the clock, ordered tests, or marker values.

Prompt Improvement Methodology

Versioning

Context assembly changes follow the scheme YYYY-MM (e.g., 2026-02). Each version is documented in the changelog below.

Evaluation Criteria

When evaluating context assembly changes, assess these 5 dimensions:
  1. Completeness — Does the AI receive all relevant user data? Are any fields missing from the context?
  2. Primacy positioning — Are the most important sections (goals, lens, lab values) at the top where LLMs pay most attention?
  3. Signal-to-noise — Are empty cards omitted? Is the context lean and relevant, or padded with empty sections?
  4. Specificity — Do values include proper units, reference ranges, phase context, and trend direction?
  5. Token efficiency — Is the context appropriately sized for each feature’s needs? (Focus card: ~200-400 tokens, not ~8000)

Testing Methodology

Changes are verified through 4 layers:
  1. Source inspectiontest-audit.js assertions read source code and verify structural properties (function existence, string patterns, section ordering)
  2. DOM verification — Browser tests check that rendered output includes expected elements
  3. Manual console checkwindow.buildLabContext() in browser console to inspect actual output
  4. Cross-feature check — Verify that Chat, Focus Card, and Health Dots all receive appropriate context

Changelog Format

Each entry documents:
  • Version identifier (YYYY-MM)
  • Changes made (grouped by category)
  • Files modified
  • Rationale for non-obvious decisions

Context Assembly Changelog

2026-02 — Priority Reordering + Enriched Context

Enriched header
  • Added age (computed from DOB), current date (ISO), and unit system label to buildLabContext() header
  • Consolidated 3 inline date formatters into single fmtDate helper
Section reordering (primacy bias optimization)
  • Health Goals → Interpretive Lens → Lab Values → Flagged Results → User Notes → Marker Notes → Per-Value Notes → Medical History → Supplements → Menstrual Cycle → Diet → Exercise → Sleep → Light → Stress → Love Life → Environment → Context Notes
  • Rationale: AI sees “what to solve” first, then data, then medical context, then lifestyle
Empty-card guards
  • All 7 lifestyle/medical cards check for actual content (not just object truthiness)
  • Prevents sending ## Diet\n with no content when user has an empty diet card object
System prompt restructure (CHAT_SYSTEM_PROMPT)
  • Restructured from flat 20-bullet list to 4 priority tiers (Core Rules → Priority Context → Lifestyle Context → Style)
  • Promoted health goals + interpretive lens to top of Priority Context
  • Consolidated 4 separate cortisol/HPA mentions into one cross-cutting note
  • Removed duplicate creatinine/urea from exercise section (already in diet)
Chat prompt order
  • Moved personality layer after lab data (was before): SYSTEM_PROMPT + lab data + personality + search
  • Rationale: AI should process data first, then adopt persona style
Focus card optimization (buildFocusContext)
  • New lightweight serializer: ~200-400 tokens vs ~2000-8000 from buildLabContext()
  • Includes: profile (sex, age, today), major health goals, flagged markers, notable changes >20%
  • Health-goals-aware system prompt: connects findings to goals when present
Per-marker AI improvements (askAIAboutMarker)
  • Uses effective (phase-aware) reference ranges instead of static schema ranges
  • Adds trend direction with percentage change when 2+ values exist
PDF import fixes (parseLabPDFWithAI)
  • Moved WBC differential rule from position 6 to position 3 (most error-prone extraction rule)
  • Added filename to user message for multi-file disambiguation
Robustness
  • JSON.parse try-catch guard in loadContextHealthDots() — malformed AI responses degrade gracefully (gray dots on stale cards, cached good data preserved)
Files modified: js/chat.js, js/constants.js, js/views.js, js/context-cards.js, js/pdf-import.js, service-worker.js (v50→v51), test-audit.js

2026-02b — Staleness Signals + Absent Field Awareness + Gate Broadening

Staleness signals (buildLabContext)
  • Global: When most recent lab results are >90 days old, inserts explicit NOTE: line with date and approximate months since last test
  • Per-category: After each category’s markers, if that category’s latest data is >90 days old, appends ⚠ Last tested ~N months ago line — catches stale categories even when other data is recent (e.g., old fatty acids alongside fresh CBC)
  • System prompt instructs AI to recommend retesting stale categories and discuss what similar/changed results would suggest
Focus card staleness (buildFocusContext)
  • Added last labs <date> to compact header so focus card can caveat stale data
System prompt additions (CHAT_SYSTEM_PROMPT)
  • Core Rules: instruction to note when data age affects analysis relevance
  • Lifestyle Context: two bullets teaching AI that missing fields = user didn’t provide (not assumed default), and missing sections = user hasn’t filled that area
Auto-gating with hasCardContent() (v53)
  • Replaced 7 hand-written card gates with generic hasCardContent(obj) from js/utils.js
  • Returns true if any field has content: strings non-empty, arrays non-empty, note field trimmed
  • Cards using auto-gate: diagnoses, diet, exercise, sleep, stress, loveLife, environment
  • Light & Circadian keeps custom lc || autoLat gate (external latitude injection)
  • Eliminates bug class: new fields added to any card are automatically included in AI context without manual gate updates
Previously broadened empty-card gates (v52, now superseded by auto-gating)
  • Diet: added restrictions, pattern, snacks to gate (user who only sets restrictions was silently dropped)
  • Exercise: added intensity, dailyMovement to gate
  • Sleep: added schedule, roomTemp, environment, practices to gate
  • Love Life: added relationship, satisfaction, frequency, orgasm to gate
  • Environment: added climate, waterConcerns, emf, emfMitigation, homeLight, toxins, building to gate
Files modified: js/utils.js, js/chat.js, js/constants.js, js/views.js, js/changelog.js, service-worker.js (v52→v53), test-audit.js, test-changelog.js

2026-03 — Change History

Context card change tracking (buildLabContext)
  • importedData.changeHistory populated by recordChange() in context-cards.js whenever a card save detects a field value change
  • New ## Context Change Timeline section (17) appended to buildLabContext() output, showing human-readable diffs between consecutive snapshots per field
  • Enables temporal correlation: AI can connect a diet change on March 1 to a lab shift on March 15
Files modified: js/state.js, js/profile.js, js/context-cards.js, js/cycle.js, js/chat.js, js/export.js, tests/test-change-history.js

2026-03b — Section Tags

Machine-parsable section tags (buildLabContext)
  • Every section wrapped in [section:name]...[/section:name] tags for programmatic extraction (used by getbased-mcp, Hermes Agent, OpenClaw)
  • Flagged results wrapped in [critical]...[/critical]
  • Lab category sections include updated:date attribute with last data date
  • New [index] block listing available lab category keys
  • No change to content within sections — tags are additive
Files modified: js/chat.js, tests/test-prelab.js

Source Files