How I built a Gramps Web connector for cognee, so you can ask a family tree questions in plain English.
A family tree is basically a graph already: people connected to parents, partners, children, places and dates. So when I went looking for something to build for Mergetober, connecting a family tree to cognee felt like an obvious match. Cognee turns your data into a knowledge graph that AI agents can remember and search, and a family tree is already made of links.
This is my first real open source contribution, so I'll keep it honest: what I built, what was harder than I expected, and what I learned.
What is Gramps Web?
Gramps is a popular free, open source genealogy app. Gramps Web is its web version that you can host yourself, and it comes with a REST API. That API is what my connector reads.
Normally you explore a tree by clicking through charts. With the connector, you can just ask: "Who were Mary Smith's grandparents, and where were they born?"
What the connector does
The connector lives in the cognee-community repo at packages/connector/gramps/. It gives you one function, gramps_source(...), which returns a dlt resource that you pass straight to cognee.remember. I copied the shape of the existing Notion connector, which is what the Mergetober guide recommends.
import cognee
from cognee_community_connector_gramps import gramps_source
await cognee.remember(
gramps_source(
base_url="https://gramps.example.org", # or GRAMPS_URL
username="...", # or GRAMPS_USERNAME
password="...", # or GRAMPS_PASSWORD
resource_name="gramps_family_tree",
),
dataset_name="family_tree",
primary_key="id",
write_disposition="merge", # incremental upsert by record id
max_rows_per_table=0, # let cleanup see the whole tree
)
answer = await cognee.recall(
"Who were Mary Smith's grandparents, and where were they born?",
datasets=["family_tree"],
)
It reads people, families, events, places and notes from Gramps Web. Then it writes one readable document per person and per family (places too, if you turn them on). Gramps stores links as internal IDs called handles, which mean nothing to a language model, so the connector swaps every handle for a name. A person's document ends up reading like a family-group sheet:
Born: 21 Nov 1840, London, England
Parents: John Smith (born 1819) (father); Anna Jones (born 1819) (mother)
Adding "(born YEAR)" helps tell apart relatives with the same name, which happens a lot in family trees. Events and notes don't get documents of their own. They're folded into the person or family they belong to, so each fact sits next to the person it's about. Gramps tags become node sets, so you can narrow a search to records tagged "Family", for example.
The tricky part 1: what counts as "changed"?
My first plan was simple: every Gramps object has a change timestamp, so only fetch what changed since last time.
Then I hit a problem. Mary's document mentions her parents by name. If you fix a typo in her father's name, his record changes, but hers doesn't, so her document would keep the old spelling.
So the connector works like this:
- A cheap quick check first. About 13 tiny requests: the tree id, then for each object type its total count and how many objects changed since the newest change it saw last time. If nothing moved, the run stops there.
- If something changed, it reads the tree, renders every document, and compares each one with a fingerprint saved from the last run. Only documents whose text actually changed get sent to cognee again.
- As a safety net, a full pass still runs at least once every 24 hours by default.
It also checks that the listing didn't shift while it was paging through. If the count changes mid-read, it tries again, up to 3 times. If it still doesn't add up, it stops without syncing or forgetting anything.
The tricky part 2: forgetting safely
If someone deletes a person in Gramps, cognee should forget them too. But "missing from the list" isn't proof of deletion. It could be a paging glitch or a network hiccup, and forgetting the wrong people from memory is much worse than keeping someone a bit too long.
So a missing record is only forgotten once it's confirmed, either in Gramps Web's transaction history (/api/transactions/history/) or by asking for that record and getting a 404. Anything unconfirmed is kept and checked again next run. Documents that mentioned the deleted person are re-sent without them.
Records also leave memory if they're marked private, lose the tag you filter by, or belong to a type you switch off.
The tricky part 3: privacy
Family trees include living people, so privacy really matters here. By default, anything marked private in Gramps (people, names, events, notes) is left out, and other people's documents don't mention it. You have to opt in with include_private=True. You can also sync only records with a certain tag, like tags=["Family"].
Passwords and tokens are only kept in memory. The connector logs in with a token, refreshes it when it expires, and retries rate-limited or failing requests while respecting the server's Retry-After header.
A friendly reminder if you try this: mark living relatives private, or use a tag filter, before you share a dataset with anyone.
How I tested it
- 61 offline tests with a fake Gramps Web API, so nobody needs a real account or password to run them. They cover paging, listings that shift mid-read, retries, token refresh, deletions, privacy and tags.
- A real dlt merge and a full cognee remember-then-forget run, with the LLM mocked.
- A light, read-only check against the public Gramps Web demo tree, plus add, edit and delete runs against my own self-hosted Gramps Web, to see sync and forgetting work for real.
-
ruff checkandruff format --checkboth pass.
Try it
The package isn't on PyPI yet, so for now you install it from the PR branch:
- Clone my branch, then run
cd packages/connector/gramps && uv sync. - Set
GRAMPS_URL,GRAMPS_USERNAME,GRAMPS_PASSWORDand your LLM key (LLM_API_KEY). - Run
python examples/example.pyand ask your tree a question.
No Gramps server? The public demo at demo.grampsweb.org works with the demo login listed on grampsweb.org.
What I learned
-
Read the API's quirks early. For example, Gramps stores the
changetime in whole seconds, so an edit made in the same second as the last sync can slip past the quick check. That's one reason the daily full pass exists. - Design deletions before the happy path. Adding data is easy. Removing it safely is where the real thinking goes.
- Tests make you braver. With a fake API I could break things on purpose (a shifting list, an expired token, a rate limit) and watch the code handle it.
- Honest note: I used an AI assistant to help me learn the codebase, debug, and polish my writing. I tested everything myself and made sure I understand the code I submitted.
Next ideas: sources and citations as evidence, media captions, and questions like "how are these two people related?"
Links
- Pull request: topoteretes/cognee-community#543
- Issue: topoteretes/cognee#6387
Thanks to the cognee maintainers, WeMakeDevs for running Mergetober, and the Gramps Web community for such a well-documented API.
Top comments (0)