Skip to content

Usage Guide

A complete walkthrough of GenealogyKG's CLI and MCP server. For the graph model, phased build history and design rationale, see docs/DESIGN.md. For what each test corpus is and what it exercises, see docs/CORPORA.md.

Installation

pip install genealogy-kg                     # core: build, query, pack, lineage, MCP
pip install "genealogy-kg[adapter]"           # + kg-rag, for KGRAG federation
pip install "genealogy-kg[viz]"               # + plotly/pyvis, for `genkg viz`
pip install "genealogy-kg[viz3d]"             # + PyVista/PyQt5, for `genkg quilt`/`viz3d`

Extras compose: pip install "genealogy-kg[adapter,viz,viz3d]" gets everything.

Building a store

genkg build --source family.ged

Creates .genealogykg/ in the current directory (graph.sqlite + vectors.sqlite) and records family.ged in .genealogykg/config.json, so every later command run from the same --repo finds it automatically:

genkg build                    # rebuilds using the recorded source
genkg build --no-wipe          # keep existing data instead of wiping first
genkg build --db /path/to/graph.sqlite --vectors /path/to/vectors.sqlite
genkg build --model sentence-transformers/all-MiniLM-L6-v2

Multiple sources: repeat --source. Or pin sources in pyproject.toml instead of passing --source every time:

[tool.genealogykg]
sources = ["family.ged"]

Precedence: --source on the command line > .genealogykg/config.json from the last build > [tool.genealogykg] sources in pyproject.toml.

*.ged is gitignored everywhere except tests/fixtures/ and corpora/entries/ (see The curated corpus below) -- GEDCOM exports contain personal data about living people and don't belong in version control by default.

Searching: query and pack

genkg query "emigrated from Yorkshire"
genkg query "chemist" -k 20 --hop 2

query returns ranked nodes as JSON: a hybrid of vector similarity over a prose summary of each record and graph expansion along lineage/event/place edges. -k/--k bounds the result count (1-100, default 8); --hop bounds graph expansion from each seed (0-5, default 1).

genkg pack "Hartwell marriages" --output context.md
genkg pack "chemist" -k 15

pack returns the original GEDCOM record behind each hit -- with line numbers -- as a Markdown snippet pack ready to drop into an LLM context window. It reads the source .ged file in place at query time rather than copying it into the store, so a built .genealogykg/ directory can be shared without the source file traveling with it -- unless living-person redaction is on, in which case it must.

Lineage: ancestors and descendants

genkg ancestors I7 --generations 3
genkg descendants I1

Prints a pure-ASCII family tree (no Unicode box-drawing), nearest generation first, with spouses and life spans inline:

John Hartwell (1820-1891) m. Mary Ashcombe
+-- William Hartwell (1848-1922) m. Anna Kessler
|   `-- Robert Hartwell (1876-1949) m. Louise Brandt
`-- Eliza Hartwell (b. 1851) m. Samuel Pryce

XREF accepts three forms interchangeably everywhere in the CLI and MCP server: I7, @I7@ (GEDCOM's own pointer syntax), and person:I7 (a node id echoed back from a previous query/pack result). --generations is bounded 1-50 (default 4).

Visualization

2-D: viz (needs the viz extra)

genkg viz I1 --output tree.html                                    # pedigree/descent chart
genkg viz I1 --view network --color-by generation --output family.html

Writes a single self-contained HTML file -- the rendering library is inlined, so it opens straight from the filesystem and can be sent to someone with neither the GEDCOM file nor Python installed. --view pedigree (default) draws the descent chart, the visual upgrade path from ancestors/descendants's ASCII tree; --view network draws the person/family graph, bounded by --max-nodes (2-5000, default 250) since it gets unreadable well below that ceiling.

3-D: quilt and viz3d (need the viz3d extra)

genkg quilt I1                          # render a Looking Glass quilt to renders/
genkg quilt I1 --cast                   # ...and push it to Looking Glass Bridge
genkg viz3d I1                          # open an interactive orbit/zoom/pan viewer
genkg quilt I1 --ancestors 3 --descendants 2   # fewer generations each way
genkg quilt I1 --shape canopy           # the older descent-only tree

By default both draw xref's hourglass. xref and their spouse(s) are the trunk at ground level. Ancestors are the roots below the ground plane: parents one tier down, grandparents two, and so on. Descendants are the canopy above it: children one tier up, grandchildren two. Every limb is a kinship edge, so a child's wood branches from its parent and a parent's root from its child. --ancestors (default 5) and --descendants (default 4) cap the generations drawn; spouses of the last descendant generation are still drawn. Without a cap, a file that traces one line back to legendary kings draws a long single-file chain.

--shape canopy draws the older descent-only tree instead: xref plus every descendant and married-in spouse, grown by space colonization, with no ancestors. Pass --schematic with either shape for the cheap straight-line layout instead of organic wood. XREF can be omitted if [tool.genealogykg] default_xref is set in pyproject.toml.

quilt needs pyvista+quiltwright; viz3d additionally needs PyQt5+pyvistaqt for the interactive window.

Exports for The Knowledge Press: The Family

The Family (kpress_family) reads a family as files, not through this package. Two commands write them:

genkg export-web --out family-web --living-cutoff 100
genkg export-swift --out family-swift --living-cutoff 100 \
    --embedder-from ../gutenberg_kg/bundles/gutenberg-all/swift --verify

export-web writes the web orchard's data:

  • catalog.json: every line, person, family, cross-line marriage and place. A line is a surname by birth: the people born with that surname, joined by parent-to-child edges, from its earliest recorded member, with an id such as hartwell-I1. A spouse who married in belongs to their own birth line, so a marriage between two lines is a links entry (the orchard's marriage roads). A person with no surname, a redacted living person among them, takes a parent's line, the father first. What is left with no surname above it is cut into houses, each named for its head, the father (or, with no father recorded, the mother) of at least three children, and holding their descent down to the next head; so a file with no surnames, such as a biblical genealogy, still plants. Anyone still left over is in the unknown line. Places carry their WITHIN parent and an event count.
  • people/<xref>.json: each person's page for the record reader: the prose summary, events in date order with places, parents, spouses, children, and the GEDCOM record lines with line numbers.
  • species.json: the tree-species table from kg_utils.viz3d.species.

export-swift writes the on-device search pack, in the format the Knowledge Press Swift kit reads (kg_utils.devicepack):

  • family.pack: one passage per person, family, event, place and source, with an FTS5 index. genre is the line, so the app's scope picker is a line picker; author is the surname; an event's title is its owner and its timestamp its date. A person's or family's content is the prose summary followed by the GEDCOM record lines. Living people are left out.
  • family.vectors: the store's own vectors, re-encoded to int8 (--dtype float keeps them exact). No re-embedding: the store and the app's Core ML embedder are both BAAI/bge-small-en-v1.5.
  • manifest.json (with the privacy settings) and golden.json, the reference rankings the app's search must reproduce.
  • --embedder-from <dir> copies in BGEEmbedder.mlpackage, vocab.txt and embedder.json (as gutenkg export-embedder writes them) and refuses an embedder converted from another model. Without it the app cannot open the pack; embedder files already in --out are kept across a --force rerun.
  • --verify reports the sidecar's dense recall@10 against the store. On a family it runs lower than on books (0.875 on the Kennedy file) because many passages are near-duplicates ("Occupation ...", "Birth ... in Boston"): every miss there was within 0.003 cosine of the 10th hit, a tie int8 rounding reorders. golden.json's tolerances allow for it.

Both commands require the living-person filter: the store must have been built with at least the export's cutoff, and each export scans its own output for living people's names before it is moved into place. See "Living-person privacy" below. --force replaces an earlier export, never a directory that is not one.

Analysis and status

genkg status               # per-genre entries/built/registered/node/edge counts for corpora/entries/
genkg status --json        # same, machine-readable
genkg analyze               # generation depth, surnames, date coverage, orphans
genkg analyze --output report.md

status reads <repo>/corpora/entries/ (override with --root) and shows a table like genkg corpus survey, but rolled up per genre with node/edge totals read straight from each entry's own .genealogykg/graph.sqlite, plus registration counts from the KGRAG registry when the adapter extra is installed. If no corpora/entries/ tree is present -- e.g. a plain --repo/--source dev store -- it falls back to reporting that single store's build state, sources and counts instead.

Snapshots

genkg snapshot save                      # capture current metrics, keyed by git tree hash
genkg snapshot list
genkg snapshot show <key>
genkg snapshot diff <key-a> <key-b>

Snapshots live in .genealogykg/snapshots/ and are meant to be tracked in git, so a repo's history shows how the graph grew over time. genkg install-hooks wires this into a pre-commit hook automatically (below).

The curated corpus

corpora/entries/<genre>/<slug>/*.ged is a committed, curated set of public GEDCOM trees -- unlike the benchmark corpora below, these ship in the repo and are safe to build against directly. Genres span royalty, US presidents, corporations, fictional-character trees, religious figures, and more; see corpora/entries/NOTICE.md for provenance and licensing. Safety is enforced at the query/pack/MCP boundary (the living-person filter, on by default for anyone plausibly still alive), not by curating which files are allowed in.

genkg corpus survey                       # which entries are built, by genre
genkg corpus survey --genre royalty
genkg corpus ingest --dry-run             # preview: build + register every unbuilt entry
genkg corpus ingest --genre samples       # build (and register with KGRAG) just one genre

ingest builds each entry's own .genealogykg/ store, then registers it with the shared KGRAG registry as genealogy-<genre>-<slug>, grouped into genealogy-<genre> and genealogy-all corpora -- registration needs the adapter extra; pass --no-register to build without it.

Living-person privacy

Before sharing a store, turn on the living-person filter and rebuild:

[tool.genealogykg]
sources = ["family.ged"]
living_cutoff_years = 100
unknown_birth_policy = "redact"

Anyone with no death or burial record who was born within the last 100 years is stored as a bare person node named Living: lineage edges and sex are kept so trees still walk through them, but name, dates, events, notes and citations are dropped, and the name is withheld from every family, spouse and parent mention too. A family with a living spouse keeps its members but not its marriage details. pack()'s own source-grounding is covered too -- no snippet returned by pack (CLI or MCP tool) contains a line from a redacted person's real GEDCOM record, even when a neighbor's context padding would otherwise run into it.

unknown_birth_policy controls records with neither a usable birth date nor a death/burial record. The default, "keep", preserves historical data completeness. The conservative "redact" mode withholds those too -- this may also hide historical people whose dates are simply incomplete. A death or burial record always counts as affirmative evidence that someone is not living, regardless of policy.

pack reads the GEDCOM file in place at query time, so a .genealogykg/ store built with redaction on can be shared without the .ged file -- and should be, since the redaction guarantee depends on the source file being unreachable to whoever receives the store.

export-web and export-swift re-check this rather than trust it: see "Exports for The Knowledge Press: The Family" above.

MCP server

genkg mcp --repo .                                        # stdio transport (default)
genkg mcp --repo . --transport streamable-http --port 8765  # HTTP, for a networked client
genkg mcp --manifest                                      # the tools as JSON, no graph needed

genkg-mcp is the same command under its older name. The server is built on the fleet's shared base, kg_utils.mcp: the first five tools have the same names and arguments on every fleet KG server, then come GenealogyKG's own. All are validated the same way the CLI is (bounded ranges, normalized xref, non-empty queries) -- a malformed call returns a clear tool error rather than a stack trace or silently-wrong result:

Tool Arguments Returns
stats -- Node and edge counts by kind and relation (the fleet stats shape)
query q, k (1-100), hop (0-5), max_nodes, rels Ranked nodes and the edges among them
pack q, k (1-100), hop, max_nodes (1-500), rels GEDCOM snippet pack (Markdown)
get_node node_id One node by id (person:I7, family:F1)
neighbors node_id, rels, direction, limit A node's edges and the nodes at their far ends
get_person xref One person node as JSON, or null
ancestors xref, generations (1-50) Ancestors, nearest generation first (JSON)
descendants xref, generations (1-50) Descendants, nearest generation first (JSON)
family_tree xref, direction, generations (1-50) ASCII tree (plain text)
analyze_genealogy -- Markdown analysis report

Configure it in an MCP client (e.g. Claude Code's .mcp.json):

{
  "mcpServers": {
    "genealogykg": {
      "command": "genkg",
      "args": ["mcp", "--repo", "/path/to/your/repo"]
    }
  }
}

The server opens the graph when a session starts and closes it when the session ends, and refuses a repo with no built store rather than serving it empty. Living-person redaction holds on every tool: query, get_node, neighbors and the lineage tools read the redacted graph, and pack strips redacted records as described above. The cutoff is read from [tool.genealogykg] in the served repo's pyproject.toml. Before the port to kg_utils.mcp, stats, query and pack were graph_stats, query_genealogy and pack_genealogy.

KGRAG federation

GenealogyKG registers with kg-rag as kind genealogy (KGKind.GENEALOGY); genkg corpus ingest is the usual way entries get registered (above). Once registered, kgrag query and kgrag pack include people, families and events from every registered GenealogyKG store alongside the fleet's other knowledge graphs, and a federated query can scope by time using the occurred_start/occurred_end metadata derived from birth, death and marriage dates.

Storage layout

.genealogykg/
  graph.sqlite     nodes and edges
  vectors.sqlite   sqlite-vec index (BAAI/bge-small-en-v1.5, 384-d)
  snapshots/       point-in-time metrics, tracked in git
  config.json      source path(s) used by the last build

Git hook

genkg install-hooks

Writes a pre-commit hook that runs the repo's own pre-commit checks on every commit. Set GENKG_SNAPSHOT=1 on a commit to also rebuild the store and save a snapshot; that's off by default because a snapshot staged into the commit it describes can never carry that commit's own tree hash.

Test corpora (benchmarks, not shipped)

./scripts/fetch_corpora.sh downloads public GEDCOM files -- the 1992 European royalty file, the US presidents file, the GEDCOM 5.5 torture test, and a 200,000-person scale benchmark -- into the gitignored corpora/ directory (everything under it except corpora/entries/, which is committed). docs/CORPORA.md lists each file, its provenance and license, and what it exercises. Tests that need these are marked integration and skip automatically when the files aren't present.