Tardigraph

The graph, for people who build graphs

This project has been running for eight days. In that time it has become a knowledge graph of 3,038 nodes and 9,591 edges about one millimetre-long animal, built from 274 papers, with a simulation, a genome database and a public site hanging off it. Everything is in git and everything is open. This page is the engineering: what the graph actually is, what we used, what we wrote, and — more usefully — what broke and how we caught it.

3,038nodes
9,591edges, 22 relation types
1,690claims, each with a source and a locator
161recorded contradictions, kept not resolved
280open questions, most posed by the papers themselves
20data-integrity flags raised against published work
101tools written, 18,060 lines of Python
291commits in 8 days
The whole tardigrade body rendered as translucent glass, with the brain in blue at the head and four purple ganglia joined by paired connectives running along the underside
What the graph is about. The nervous system of one Hypsibius exemplaris, segmented from an X-ray nano-CT scan: the brain in blue, four trunk ganglia in purple, and the paired connectives between them. Every structure here is also a node in the graph, with the papers that describe it attached. Scan: Gross et al. 2019, CC BY 4.0.

What kind of graph it is

It is a property graph of scientific claims, stored as two JSONL files — one node per line, one edge per line — in an ordinary git repository. No database server. The whole thing is about 4 MB and you can download it. That choice is deliberate: a text file in git gives you line-level diffs, blame, and the ability to see when a claim changed and why, which is most of what provenance means in practice.

Four rules do the real work, and they are the part worth stealing:

RuleWhy
Nothing enters without a source and a locator. A claim cites a page, figure, table, supplementary file or database record — never just a paper. If only an abstract was read, the source says so and the claim's confidence drops. You can always get from an assertion back to the sentence it came from.
Contradictions are kept. When two papers disagree we add both claims and a contradicts edge with a note. We do not pick a winner and delete the loser. There are 161 of these. A graph that resolves its conflicts silently is a graph that has lost its evidence.
Assumptions are first-class nodes. Where the simulation needs a number no paper provides, that guess is a node with evidence: assumed, and the rule that uses it points at it. One command lists every guess in the system. Nothing is quietly hardcoded.
Failures stay in. Every comparison with data is a test node recording seeds, date and a result in words. Failing tests are not deleted. Several of our own published claims have been corrected by later ones through a refines edge, with the original left readable.

One schema decision changed the whole picture, and it generalises. Species membership started as a relation. That meant 2,477 of the graph's edges pointed at just 92 species nodes, and in a force layout those hubs became the entire image — every claim about Hypsibius exemplaris dragged into one knot, the actual structure invisible behind it.

We made it a node property instead. You can still filter by it; it exerts no pull. The rule we took away: a relation that is really a category should be an attribute. If most of your edges terminate on a handful of nodes, you have probably encoded a label as a link.

The bug that visualisation found and querying did not

We had been querying this graph for a week. Then we rendered it, and someone clicked the biggest node in the picture. The inspector read degree 48, degree_in 48, degree_out 0.

Counting that across the whole graph gave this:

Node typeOutgoingIncoming
process0907
species02,492
tissue6158
structure2241,931

Sixty-seven biological processes, 907 claims pointing at them, and not one relation leaving one. The graph could say that a paper made a claim about moulting. It could not say that moulting follows a stage, requires a gene, or produces a cuticle. All of that knowledge was sitting in claim prose that the schema had no relation to express — so it could not be queried, drawn, or contradicted.

No query we wrote found that, because you do not write a query for a relation you have not noticed is missing. A picture is a different query engine. It is a whole-population view that makes an absence visible as a shape, and it is the strongest argument we have for rendering your data even when you think you already understand it.

Adversarial by default

Science and security share a failure mode: it is easy to confirm what you already believe, and the work of not doing that has to be built into the process rather than left to good intentions. Four rules run on everything here.

RuleWhat it caught
Inventory before you ask for more. Before requesting or generating data, list what is already on disk. We had 17 embryos and had used 2. The missing 15 held the most important result of that day.
Sweep every free parameter before believing a trend. Report the plateau, not one setting. A domain-enrichment result survived a threshold sweep; a "peak time" result turned out to be an artefact of a 12-hour gap in the sampling, visible only when we drew the gaps.
Make every check symmetric. If you test whether something grew, test whether it shrank. A gene "switching on" looked remarkable until the symmetric test showed 2% of the transcriptome does it. The identity was the finding; the statistic was not.
A confound test only rules out the confounds you thought of — and your own method is rarely on that list. A null result we published turned out to be a property of the statistic, not the biology. A different measure on the same data reversed it. Both are in the write-up.
We also run integrity checks on other people's published data. Duplicated image regions, panels that should differ and do not, numbers that contradict the figure they describe, impossible values. There are 20 flags on the register so far, each with what was observed, the innocent explanations, and what would settle it. A flag is not an accusation — it is a finding with an open disposition, which is a distinction this audience already understands.

A war story you will recognise

A dataset we use gives sample times in minutes. We established on day 2 — by matching it against an independent dataset — that those minutes are half of real time. We wrote that into four documents and two analysis scripts.

It never reached the other five scripts. For six days they divided the stored minutes by 60 and labelled the result "hours post laying". Nothing failed, because every statistic involved — rank correlations, permutation tests, embeddings — is invariant under a monotone rescaling of time. Re-running afterwards returned identical correlations, p-values and tree topology. Every number in the results was right. Every number in the discussion was half of what it said, in the graph, in a manuscript, and on this website, where curves were being drawn at half speed against stage markers that were in real hours.

The lesson is not the arithmetic. It is that a correction recorded in prose cannot reach a script. A note can go stale; code cannot. So the fix was three mechanisms and not a better note: one converter everything calls, an assertion inside the loader that refuses to return a series ending before the animal hatches, and a scanner that fails if any file computes hours from minutes by itself.

The scanner found two more tools immediately. Then it missed a browser-side copy of the same bug, because it only read Python — so it reads the web pages now too. And we checked it the only way worth checking a detection: we put the bug back and confirmed the check failed.

What we used, and what we wrote

UsedFor
Graphistry / PyGraphistry GPU rendering of the whole graph, and GFQL for querying it. The explorer on this site is their renderer in an iframe. Their author suggested the embedding and pointed us at UMAP, which became a result.
HMMER, MAFFT, IQ-TREE, DIAMOND, BLAST, minimap2, samtools, seqkit All from version-pinned BioContainers under unprivileged Docker. Which version produced a result is recorded rather than being whatever was on the machine that day.
igraph, NumPy, umap-learn, Playwright Layouts, statistics, embeddings, and headless verification of our own pages — every visualisation on this site is screenshotted and checked by a script before it ships.
WrittenDoes
tg.pyThe graph itself: validate, query, show, list every assumption, export. validate must pass before any commit.
graphistry_view.pyNine slices of the graph, exported, laid out and uploaded; writes the embed manifest this site reads.
connect.pyFinds relations that claim prose asserts and the graph does not carry — the fix for the degree_out 0 problem.
family_tree.pyTakes a protein family and three genomes, and returns where an unknown gene sits in it, with bootstrap support and the placement read off the tree.
check_timeaxis.pyThe scanner from the war story above.
supp_scan.py, future_work.pyFinds supplementary data we are missing, and sentences where a paper poses a question — those become nodes, and later work gets checked against them.

All 101 tools are in the repository. Nothing here is a product and nothing is for sale.

Where code and science actually meet

The honest version: the bottleneck in this field is not measurement, it is that nobody has joined the data up. Most of what we have found came from putting two existing public datasets next to each other and asking a question neither paper asked. The genome was published in 2017. The developmental transcriptome in 2016. The result that a tardigrade builds its synapses last was sitting in both of them for eight years and required no new experiment — only somebody to ask.

That is a software problem more than a biology problem, and it is the reason a project like this can move in a week. If you write code and you have ever wondered whether it is useful anywhere outside your industry: this is where it is useful.

Fly through the graph The whole thing rendered, filterable by type — click a hub and see what it rests on Download it Both JSONL files, the schema, and the tools. CC BY 4.0 Work on it What needs doing, what it needs from you, and how to start How the whole thing is built Sources, simulation, tests and the rules above in full

Written for BSides Canberra, September 2026. If you are reading this after finding it another way: the animal is a tardigrade, it is about a millimetre long, it survives being dried to 3% water and being fired into a vacuum, and there is a great deal more of it here.