MA

mattjoyce/anam

Developer tools
0 stars Quality 58 Trend 20Apache-2.0

A personal, longitudinal record of one person's life, kept as immutable facts. Health readings, workouts, medicines, lab results, trips, documents and notes land on one timeline, each with where it...

Overview

A personal, longitudinal record of one person's life, kept as immutable facts. Health readings, workouts, medicines, lab results, trips, documents and notes land on one timeline, each with where it came from and when it was true. The current state is always worked out from the facts, never stored, and nothing is ever overwritten. This is a personal project, published so others can read it and borrow from it. The sources, units and time zone in the examples are the author's. The inspector, served by the daemon, over a month of made-up facts. Each mark is a facet at its fact time; the green strip is the checkpoints. - A trip, an illness or a training block is an optional lens over it, never a requirement. - A correction is a new version beside the old one. --as-of shows what Anam knew at any earlier moment. - Fact time is when something was true. Anam time is when Anam learned it.

README

Anam

anam: Irish for soul.

A personal, longitudinal record of one person’s life, kept as immutable facts. Health readings, workouts, medicines, lab results, trips, documents and notes land on one timeline, each with where it came from and when it was true. The current state is always worked out from the facts, never stored, and nothing is ever overwritten.

This is a personal project, published so others can read it and borrow from it. The sources, units and time zone in the examples are the author’s.

The inspector, served by the daemon, over a month of made-up facts. Each mark is a facet at its fact time; the green strip is the checkpoints.

Ideas

  • The whole-person timeline is the default read. A trip, an illness or a training block is an optional lens over it, never a requirement.
  • Facts accrete. A correction is a new version beside the old one. --as-of shows what Anam knew at any earlier moment.
  • Two clocks. Fact time is when something was true. Anam time is when Anam learned it.
  • One way in. Every fact arrives through an admission gate that checks it against a Definition, so a bad row never enters the record.
  • Proof of what was shown. A projection prunes the timeline for a reader, and a receipt proves exactly what it served.
  • Anam reads no source itself. Importers do that, and Anam can be rebuilt from them.

Facet definitions

Every fact is a facet of a named type, and each type has a versioned Definition: a JSON Schema, which field names the facet, and which field says when it was true. Definitions are immutable once published, so a change is a new version. Anam bundles the 28 below, and importers register more.

Bundled with Anam

Type Version What it records Keyed by Fact time
health.blood-pressure 5 One blood pressure reading, as an Open mHealth body date_excel, reading date_excel
health.weight 3 One body weight id date
health.body-composition 3 A period’s body metrics (fat, muscle and the rest) group, period_type, period_start period_start
health.heart-rate 3 A period’s heart rate metrics group, period_type, period_start period_start
health.heart-rate-alert 3 One abnormal high heart rate alert from a watch source, kind, fact_time fact_time
health.sleep 3 A period’s sleep metrics group, period_type, period_start period_start
health.metric-group 3 A period’s metrics for the health groups with no type of their own group, period_type, period_start period_start
exercise.session 5 One session of exercise, as an IEEE 1752 physical activity id session start
gym.visit 4 One gym visit id date
med.started 4 A medicine course starting id date
med.dose-changed 4 A change of dose within a course id date
med.stopped 3 A course ending id date
med.missed-dose 4 A dose the person says they missed id date
med.compliance-estimate 4 The person’s own estimate of how well they kept to a course id date
condition.noted 4 A condition named in a document id date
illness.started 3 The day an illness began id date
illness.ended 3 The day an illness ended id date
illness.hangover 3 One hangover, a single day id date
lab.panel 4 One pathology request, the header of a draw’s report id collected
lab.result 4 One analyte’s result from one draw id collected
donation.blood 4 One visit to give blood id date
visit.clinic 4 One appointment with a clinic or practitioner id date
travel.departed 4 Leaving home on a trip id date
travel.flight 4 One flight leg id date
travel.arrived 4 Arriving at one place during a trip id date
travel.returned 4 Getting home from a trip id date
note.annotation 3 A note in the person’s own words, about some facets or none id at
doc.reference 3 A pointer to one file in the documents library; the bytes never enter the ledger path date

Registered by importers

These come with anam-importers, which publishes them to the daemon on first use.

Type Version What it records Importer
health.measurement 3 One Withings measurement withings
health.step-count 1 One day’s steps, as an Open mHealth body garmin
health.heart-rate-daily 1 One figure for a day’s heart rate, as an Open mHealth body garmin
sleep.episode 1 One sleep episode, as an IEEE 1752 body garmin
garmin.day 1 One day of a Garmin watch’s daily summary garmin
sleep.cpap-session 1 One therapy session from a DreamStation card dreamstation
location.track 1 One UTC hour of a phone’s positions owntracks
location.stay 1 One place a phone stayed owntracks

The bundled files are in internal/def/manifests/, and a running daemon lists what it holds at GET /v1/definitions and in the inspector’s definitions page. Adding a type is a Definition file, not code.

Getting facts in

Every source reaches Anam through an importer, in the sibling anam-importers repository. An importer reads one source (Garmin, Withings, a blood pressure export, a DreamStation card, OwnTracks, Concept2), shapes its rows to a Definition, and posts them to the daemon under its own token. Two formats are Anam’s own:

  • records: hand-written YAML records for labs, medicines, travel and notes.
  • documents: a folder of files with sidecars, each a doc.reference. The importer posts the bytes and Anam keeps them in its library.

The importers are plugins for the ductile gateway, a small scheduler and event router, but that is only how the author runs them. Each is a script that can be run by hand, and anything that can post an envelope can feed Anam (docs/IMPORTERS.md).

Letting an AI read it

An AI reaches the record through the daemon’s API, and there are three ways to do that.

  • MCP. anam-mcp is an adapter for any MCP client. It offers a few tools (describe the record, describe a type, read the timeline, verify a receipt) that cite their evidence, and one to save the person’s own words as a note.
  • The CLI. The anam command talks to the daemon when ANAM_SERVER is set, so a local model with a shell tool can run anam timeline and anam changes and read the output.
  • curl. The API is plain HTTP and JSON, documented in docs/API.md. A skill for Claude Code, .claude/skills/anam-service, teaches the routes and the checks.
export ANAM_SERVER=http://127.0.0.1:8787 ANAM_TOKEN_FILE=~/.config/anam/token ANAM_ZONE=Australia/Sydney

anam timeline --from 2026-09-17 --to 2026-10-17
anam changes --since 2026-10-01

curl -H "Authorization: Bearer $(cat $ANAM_TOKEN_FILE)" \
  "$ANAM_SERVER/v1/timeline?zone=$ANAM_ZONE&type=health.blood-pressure"

Reads are narrowed by role. A token sees only what its role allows, and an importer’s token sees only its own source’s snapshots.

Quick start

You need Go (see go.mod for the version). Nothing here needs a server, a token or any other machine; everything runs on loopback against two SQLite files.

make build

# person_id has no default on purpose: it is pinned into the ledger at init, so a
# defaulted one would name a person nobody chose for the life of the file.
export ANAM_PERSON_ID=me

# The ledger and the operational store are two SQLite files. Both default to
# ~/.local/share/anam/ (ledger.db and tracking.db); override with
# ANAM_LEDGER_PATH and ANAM_DB_PATH.
./anam init

# Run the daemon. On loopback it needs no token. Leave it running and use another terminal.
./anam daemon
curl http://127.0.0.1:8787/healthz                 # ready
ANAM_SERVER=http://127.0.0.1:8787 ./anam status   # the ledger id, W, and each importer's last batch

Open http://127.0.0.1:8787/ for the inspector. If ANAM_SERVER is set in your shell, init and daemon refuse to run, because a configured server is treated as the owner of the files. Unset it for those two commands.

Read. The whole-person timeline is the default, and everything else is a lens over it.

./anam timeline                                    # the canonical, whole
./anam timeline --from 2025-05-01 --to 2026-07-01  # narrowed by fact time
./anam changes --since 2026-07-10                  # what Anam learned since, by Anam time

Publish and prove. A projection prunes the canonical, and a receipt proves what was served.

./anam serve                        # the menu: you select a projection, you cannot define one
./anam serve bp                     # publish: a served value and a receipt, content-addressed
./anam receipt list
./anam receipt show           # what was served, from the artifact alone
./anam receipt verify         # re-derive it from the ledger, and re-hash the file

Check a projection before publishing it, and ask why a facet is in or out.

./anam timeline --projection bp                                # the served view, with its receipt hash
./anam timeline --projection bp --explain                      # every facet's verdict, and the reason
./anam timeline --projection bp --as-of 2026-07-10T20:00:00Z   # what it stood on when Anam knew less

Mint. A checkpoint caches the fold, one value per monthly bucket, to bound replay.

./anam checkpoint --dry-run         # which buckets would mint, and to what hash
./anam checkpoint                   # mint every bucket whose value moved
./anam checkpoint list              # every checkpoint ever minted

How it fits together

importers (envelopes, one token per source) → admission gate → append-only ledger (SQLite)
    → checkpoints → the fold → projections and the CLI → receipts

The ledger is the record. It is a SQLite file whose triggers refuse any update or delete. A second SQLite file holds what Anam itself authors, and it can be rebuilt by replaying the ledger. Facts reach the ledger only through the gate, which checks each one is compliant with its Definition, authenticated, and associated with the person.

Docs

Status

A working prototype that holds one person’s record and is in daily use. Expect the code to change and old data to be rebuilt rather than migrated.

Licence

Apache-2.0, see LICENSE. Third-party material, the IEEE 1752 schemas bundled for conformance tests, is listed in NOTICE.

View this README on GitHub

Recommended Tools

Try a different keyword or remove a filter.

Install

npx skillfish add mattjoyce/anam