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
{ system, messages, maxTokens, onStream? }. The caller assembles the prompt; the router just delivers it.
Data Sources
Everything the AI can know about the user comes from these sources:buildLabContext() — The Central Serializer
buildLabContext() in lab-context.js is the single function that converts all user data into a plain-text block. It is used by Chat and Health Dots. 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. 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.
Empty-Card Guards
Cards with no meaningful content are omitted entirely (not sent as empty sections). Each card checks for actual data:diagnoses: conditions array not empty OR note has textdiet: type set OR any meal content OR noteexercise: frequency set OR types array not empty OR notesleepRest: duration set OR quality set OR issues not empty OR notestress: level set OR sources not empty OR noteloveLife: status set OR libido set OR concerns not empty OR noteenvironment: 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:
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:| Feature | Cache Key | Fingerprint Inputs | Invalidation |
|---|---|---|---|
| Focus Card | labcharts-{profile}-focusCard | entries + sex + DOB + 9 cards + lens + notes + cycle + supps | Any data change |
| Health Dots | labcharts-{profile}-contextHealth | Per-card: lab data + card data + sex + DOB | Only changed cards re-fetched |
| Chat | labcharts-{profile}-chat-t_{id} | N/A (conversation history) | Never invalidated, 50-thread cap |
| PDF Import | N/A | N/A | No caching |
| Persona | N/A | N/A | No caching (user saves manually) |
Token Budget
| Feature | maxTokens | Typical Context Size |
|---|---|---|
| Chat | 4096 | ~2,000–8,000 tokens (scales with data) |
| Focus Card | 100 | ~200–400 tokens (lightweight context) |
| Health Dots | 500 | ~2,000–8,000 tokens (full context) |
| PDF Import | 8192 | ~1,000–3,000 tokens (marker schema) |
| Persona Generator | 2048 | ~400 tokens (fixed) |
Key Design Decisions
-
Two serializers:
buildLabContext()for full context (Chat, Health Dots) andbuildFocusContext()for slim context (Focus Card, ~200-400 tokens). The focus card only needs flagged markers and notable changes for a 40-word response. -
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. - 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.
-
Priority-tiered system prompt:
CHAT_SYSTEM_PROMPTuses 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. - 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).
- Phase-aware values: For female profiles with active cycle data, estradiol, progesterone, LH, and FSH values include per-date phase labels and phase-specific reference ranges inline. Disabled for hormonal contraception and non-cycling statuses (postmenopause, pregnant, breastfeeding).
-
Effective ranges:
askAIAboutMarker()uses effective (phase-aware) reference ranges, not the static schema ranges. Also includes trend direction with percentage change. -
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.
Prompt Improvement Methodology
Versioning
Context assembly changes follow the schemeYYYY-MM (e.g., 2026-02). Each version is documented in the changelog below.
Evaluation Criteria
When evaluating context assembly changes, assess these 5 dimensions:- Completeness — Does the AI receive all relevant user data? Are any fields missing from the context?
- Primacy positioning — Are the most important sections (goals, lens, lab values) at the top where LLMs pay most attention?
- Signal-to-noise — Are empty cards omitted? Is the context lean and relevant, or padded with empty sections?
- Specificity — Do values include proper units, reference ranges, phase context, and trend direction?
- 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:- Source inspection —
test-audit.jsassertions read source code and verify structural properties (function existence, string patterns, section ordering) - DOM verification — Browser tests check that rendered output includes expected elements
- Manual console check —
window.buildLabContext()in browser console to inspect actual output - 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
fmtDatehelper
- 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
- All 7 lifestyle/medical cards check for actual content (not just object truthiness)
- Prevents sending
## Diet\nwith no content when user has an empty diet card object
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)
- Moved personality layer after lab data (was before):
SYSTEM_PROMPT + lab data + personality + search - Rationale: AI should process data first, then adopt persona style
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
askAIAboutMarker)
- Uses effective (phase-aware) reference ranges instead of static schema ranges
- Adds trend direction with percentage change when 2+ values exist
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
- JSON.parse try-catch guard in
loadContextHealthDots()— malformed AI responses degrade gracefully (gray dots on stale cards, cached good data preserved)
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 agoline — 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
buildFocusContext)
- Added
last labs <date>to compact header so focus card can caveat stale data
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
hasCardContent() (v53)
- Replaced 7 hand-written card gates with generic
hasCardContent(obj)fromjs/utils.js - Returns
trueif any field has content: strings non-empty, arrays non-empty,notefield trimmed - Cards using auto-gate: diagnoses, diet, exercise, sleep, stress, loveLife, environment
- Light & Circadian keeps custom
lc || autoLatgate (external latitude injection) - Eliminates bug class: new fields added to any card are automatically included in AI context without manual gate updates
- Diet: added
restrictions,pattern,snacksto gate (user who only sets restrictions was silently dropped) - Exercise: added
intensity,dailyMovementto gate - Sleep: added
schedule,roomTemp,environment,practicesto gate - Love Life: added
relationship,satisfaction,frequency,orgasmto gate - Environment: added
climate,waterConcerns,emf,emfMitigation,homeLight,toxins,buildingto gate
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.changeHistorypopulated byrecordChange()incontext-cards.jswhenever a card save detects a field value change- New
## Context Change Timelinesection (17) appended tobuildLabContext()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
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:dateattribute with last data date - New
[index]block listing available lab category keys - No change to content within sections — tags are additive
js/chat.js, tests/test-prelab.js
Source Files
| File | Key Functions |
|---|---|
js/utils.js | hasCardContent() — generic empty-card gate for context assembly |
js/constants.js | CHAT_SYSTEM_PROMPT — priority-tiered system prompt |
js/lab-context.js | buildLabContext() — central serializer (full context) |
js/chat-send.js | sendChatMessage() — chat prompt composition |
js/chat-marker-prompts.js | askAIAboutMarker() — per-marker prompt (effective ranges + trend) |
js/chat-personalities.js | generateCustomPersonality() — persona generator prompt |
js/views.js | buildFocusContext() — lightweight focus card context (~200-400 tokens) |
js/views.js | loadFocusCard() — focus card prompt (health-goals-aware) |
js/context-cards.js | recordChange(field) — timestamps context field snapshots into changeHistory |
js/context-cards.js | loadContextHealthDots() — health dots prompt (JSON.parse guarded) |
js/pdf-import.js | parseLabPDFWithAI() — PDF import prompt (filename included) |
js/pdf-import.js | buildMarkerReference() — marker schema serializer |
js/api.js | callClaudeAPI() — provider router |
js/data.js | getActiveData() — data pipeline (feeds buildLabContext) |