←

gro-light

A schema for publishing scientific work as it is made · v0.4 · October 2026

a medium for publishing scientific work as it is made. In a collaborative graph rather than piles of pdfs.

the graph consists of typed nodes and edges between them. nodes include claims, evidence, methods, materials, code, datasets. representing research this way, one thing becomes obvious: methods, code packages and other shared resources are the real generators of progress. making them visible as first-class contributions makes them legible to institutions that can then reorganize their incentive mechanisms to take such contributions more explicitly into account.

claims are represented as relationships between two entities and are thus often nexuses of cross-team collaboration: is rs12979860 (a single-nucleotide polymorphism) associated with more spontaneous clearance of hepatitis c? this also means that multiple groups do, can and should add evidence to the same claim, debating, contesting or replicating its veracity. this makes contested points in the research visible and again, allows incentives to be built around resolving them.

this medium is not meant to replace papers entirely but to supplement the scientific record with a more comprehensive and faster-growing body of knowledge and insight. papers can still be written up once everything is done, and that’s great. but papers will never include all the important contributions to science that this graph captures, like code, specific tools, negative results.

the graph is also incredibly easy for llms to traverse and retrieve, allowing researchers to verifiably fetch all claims about entity x, or all datasets that have been analyzed with method y.

here’s an example graph of four papers on the genetics of hepatitis c treatment response. it looks messy, but it’s incredibly easy for a machine to tell you which contributions were key. and even at a glance, you can see which datasets (green squares), methods (orange diamonds) and software (purple triangles) were key, and which claims (blue circles, or hexagons where several papers state the same one) were central (literally). the grey rectangles are individual pieces of evidence — the single numbers behind each claim, and the most numerous thing in the picture.

A force-directed graph of four hepatitis C papers: claims, evidence, methods, datasets and software as typed nodes.

click the graph to open it full size

this is the schema for gro. note this is still evolving in order to be maximally helpful to scientists.

Claim

id: gro:2026-03-11-wakita-jfh1#cl1
kind: claim
subject:   {id: wd:Q18039731, label: IL28B}
predicate: {label: predicts}
object:    {id: wd:Q7111590, label: sustained virological response}
qualifiers:
  population:   [{label: patients with chronic HCV genotype 1}]
  system:       []                                  # experimental system node ids
  intervention: [{label: peginterferon alfa-2b plus ribavirin}]
  adjusted_for: [{label: baseline viral load}]
  contrast: CC vs TT
  direction: increase
  timepoint: week 72
  other: null
negated: false
hedged: false
basis: own_result          # own_result | from_other_work | interpretation
framed_as: extension       # replication | contradiction | extension
origin:
  by: [orcid:0000-0002-1825-0097]
  at: "2026-03-11"          # stamped on publication, not written by hand
grounding: {artifact: runs/2026-03-09/titration.csv, locator: "row 14"}
also_stated_in:
  - by: [person:m-thompson]
    in: doi:10.1002/hep.23192     # a paper here; any published id works
    quote: "…"
    locator: "Results, para 3"
    basis: own_result

Evidence

One number per evidence node. Spread and uncertainty are recorded.

id: gro:2026-03-11-wakita-jfh1#ev3
kind: evidence
description: odds of SVR for CC versus TT genotype
measure: odds ratio
value: 7.3
value_range: null          # {low, high} when the reported value is itself a range
unit: null
ci: {low: 5.1, high: 10.4, level: 0.95}    # level null if you don't report one
error: {type: se, value: 0.18}             # sd | se | iqr | range | other
p: "<0.0001"
test: logistic regression
n: 1137
population: {label: patients with chronic HCV genotype 1}
outcome: sustained virological response
reference_level: TT genotype
origin: {by: [orcid:0000-0002-1825-0097], at: "2026-03-11"}
grounding: {artifact: runs/2026-03-09/genotype-svr.csv, locator: "row 22"}

error takes {type, value} for sd and se, {type, low, high} for iqr and range. If you publish a bare ”±” without saying what it is, it records as other — the schema never guesses a standard deviation on your behalf.

outcome and reference_level say what the number is for, so it keeps its meaning away from the write-up that surrounds it: an odds ratio for persistence stays an odds ratio for persistence even when you go on to discuss clearance.

Evidence that only counts together is linked through jointly_with on its edges.

A negative or null result is an evidence node like any other, published the same way, and the claim it bears on carries negated: true. Nothing in the schema makes a result that did not work harder to publish than one that did.

Method

id: gro:2026-03-11-wakita-jfh1#me1
kind: method
label: JFH-1 subgenomic replicon assay
status: new                # grounded | new | composite | from_other_work | unattributed
from_other_work: null      # {named: "Lohmann 1999", id: doi:…} when status says so
used_in: [gro:2026-03-11-wakita-jfh1, doi:10.1002/hep.23192]
origin: {by: [orcid:0000-0002-1825-0097], at: "2026-03-11"}
grounding: {…}

Experimental system

The material a result is obtained in, as its own node, so that reuse shows up as one shared node rather than a repeated string. This is the node that makes “nine groups built on our construct” answerable.

id: gro:2026-03-11-wakita-jfh1#sy1
kind: system
label: pJFH1
system_kind: construct     # cell_line | primary_cells | replicon | construct |
                           # pseudoparticle | animal_model | other
identifiers:
  - {scheme: genbank, value: AB047640}
produced:
  by: [orcid:0000-0002-1825-0097]
  here: produced_here      # produced_here | reused | not_stated
  from_other_work: null
new: true
origin: {by: [orcid:0000-0002-1825-0097], at: "2026-03-11"}
used_in: [gro:2026-03-11-wakita-jfh1, doi:10.1038/nm1268]
grounding: {…}

identifiers takes the schemes genbank, rrid, atcc, addgene, doi, url and other. Systems derived from other systems are linked by derived_from, which records the modification.

Data

id: gro:2026-03-11-wakita-jfh1#da1
kind: data
label: IDEAL study
identifiers:
  - {scheme: nct, value: NCT00081770}
produced:
  context: >-
    phase 3 RCT of peginterferon alfa-2b vs alfa-2a with ribavirin,
    genotype 1, US sites
  by: [person:j-mchutchison]
  from_other_work: {named: "McHutchison 2009", id: doi:10.1056/NEJMoa0808010}
  here: reused             # produced_here | reused | not_stated
status: public_registered
access: not_stated
origin: {by: [orcid:0000-0002-1825-0097], at: "2026-03-11"}
used_in: [gro:2026-03-11-wakita-jfh1]
grounding: {…}

identifiers records every identifier the dataset has, each with a scheme: nct, doi, zenodo, dbgap, geo, sra, genbank, pdb, dbsnp, ega, url, other. An empty list means none was given.

produced.context is the study or setting the data came from — trial, cohort, lab system, collection period, site. produced.by is who made it. origin stays what it is for every node: whoever first brought the data into the graph, which for reused data is rarely whoever produced it.

Code

id: gro:2026-04-02-wakita-svr-model#co1
kind: code
label: hcv-svr-glm
code_kind: project_code    # package | project_code
status: released           # released | stated_not_released
url: https://github.com/…
version: v1.2.0
produced: {by: [person:…], here: produced_here}
origin: {…}
grounding: {…}

Person

A person is a node so that credit is a link rather than a repeated string, and so that the same person publishing twice is one node. This is what makes the graph answerable in the direction that matters to the people in it: what have I put into the world, and what has been built on it.

id: orcid:0000-0002-1825-0097
kind: person
name: "Takaji Wakita"
also_published_as: ["T. Wakita"]
ids:
  - {scheme: orcid,     value: "0000-0002-1825-0097"}
  - {scheme: gscholar,  value: "xxxxxxxxxxxx"}
  - {scheme: ror,       value: "https://ror.org/…"}     # affiliation handle
first_seen_in: gro:2026-03-11-wakita-jfh1

ids is a list because one person is reachable through several handles, and because the handle one record carries is not always the one you want to join on. Schemes in use: orcid, gscholar, ror, isni, wikidata, email, url, other. The list may be empty — a person named with no identifier anywhere is still a person node, with a local person:<slug> id.

Credit lands in two places, which answer different questions:

An authored_by edge runs from any node to the people credited for it, so the question can be asked of the graph directly rather than by walking back through origins.

Edges

typefrom → to
supports, contradictsevidence → claim (optional jointly_with: [evidence ids])
produced_byevidence → method
uses_dataevidence → data
uses_codeevidence or method → code
uses_systemevidence or method → system
composed_ofmethod → method (records the modification)
derived_fromsystem → system (records the modification)
authored_byany node → person
matcher relationsclaim → claim; matcher runs only

Every edge carries from_kind and to_kind, so a consumer never has to look its endpoints up, and either stated_in or made_by.

Claim relations derived from the fields

For two claims from different groups, these follow from the fields alone and are not stored:

Anything else that looks alike is for a matcher, which emits the same relation vocabulary — same_claim, refines, same_relation_other_scope, conflicts, agrees — as a made_by edge. The division is that the field rules fire when subject, predicate and object all match and only the qualifiers are in question; a matcher fires when the entities match but the predicates differ, and has to read them to type the relation. The predicate is a matching input, not a matching key: with an open predicate vocabulary, most predicates are used once, and keying on them would stop the field rules from ever firing across groups.

What this draft leaves out