Recording data right
The itemDef-first flow
Section titled “The itemDef-first flow”When you ask the agent to record or import health data, it should always look the data up in the HDS data model first, then write events matching the canonical shape:
1. search_items("basal body temperature") → top match: body-temperature-basal2. get_item("body-temperature-basal") → streamId: "body-temperature-basal" eventType: "temperature/c" type: "number"3. import_batch([ { streamIds: ["body-temperature-basal"], type: "temperature/c", content: 36.6, time: <epoch-seconds> }, ... ])Skipping this lookup and inventing stream and type names breaks the
cross-system mapping (HealthKit / FHIR / SNOMED CT / ICF) that HDS items
carry. The MCP’s tool descriptions explicitly tell the agent to call
search_items first; if no item matches, the agent should ask you before
inventing a type.
New datasets: structure first, notes last
Section titled “New datasets: structure first, notes last”When you bring a dataset that HDS has no item for yet — an export from an
app we don’t bridge, a spreadsheet of measurements, a tracker’s CSV — the
agent must not dump it into note/txt events if the information can
be structured. Notes are opaque text: they can’t be charted, converted,
compared across sources, or mapped to HealthKit/FHIR/SNOMED. A note is a
dead end; a structured event keeps its meaning everywhere.
The expected investigation order:
- Search the item catalogue (
search_items, or browse model-browser.datasafe.dev) — most common observations already have a canonical item. - Check the event-type catalogue (
pack.jsoneventTypes) for a fittingcategory/unittype (e.g.mass/kg,temperature/c,count/generic) when no full item matches. - Ask before inventing. If neither matches, the agent should surface the gap to you — and you’re welcome to open an issue so the data model team can add a proper item.
- Only genuinely free-form content (a diary entry, a doctor’s letter
text) belongs in
note/txt.
If your agent reaches for notes as the easy way out, push back — and expect it to push back on you, too.
Bulk imports: safe to run twice, easy to remove
Section titled “Bulk imports: safe to run twice, easy to remove”An import of an archive (letters, lab reports, another app’s export) will be run again: the source grows, a mapping gets fixed. It must update what it wrote rather than duplicate it, and the whole import must be findable and removable. The contract (details and code in llms-full.txt §5f):
- Every imported event carries
clientData.sourceKey, written<tool>:<kind>:<id>: the importer’s name, the kind of record, and the record’s own identity in the source, the same on every run. - The import has its own access, so each event’s author names it.
- That access is scoped:
manageon exactly the root streams it writes, never on everything (*). - Sources first: the document or the provider is written before the values that cite it.
- A re-run updates in place by
sourceKey, and removing the import is one query on thesourceKeyprefix (<tool>:) followed by deleting what it returns.
Dates known only to the month or the year (“March 1999”), and the range and flag a lab printed beside a value, have their own conventions in the data model: PARTIAL-DATES.md and BLOOD-CHEMISTRY.md.
Worked pilot — fertility-charts folder → HDS
Section titled “Worked pilot — fertility-charts folder → HDS”You drop a folder of Excel sheets and scanned paper from fertility charting into your machine and ask your agent to upload it to a demo HDS account.
“Sign me in to HDS demo, then process the folder at
~/fertility-2026/and import every reading.”
The agent:
- Calls
hds.connect()→ you sign in to demo. - Reads the folder via its filesystem tool.
- Uses vision to OCR scanned BBT sheets; uses code interpreter to parse the Excel.
- For each distinct data type (BBT, cervical fluid, LH test, …) calls
hds.search_items(...)thenhds.get_item(...). - Calls
hds.import_batch(...)with the canonicalstreamIds+eventTypefrom the item spec. - Confirms with
hds.list_streams()/hds.get_events(...).