2026 — in development
lifespan-extract
Structured longevity-intervention data from papers, measured against a hand-labeled gold set built before the pipeline.
github.com/sofiapetrusenko/lifespan-extract
- gold records
- 26
- papers labeled
- 10
- hard negatives
- 15
- tests in CI
- 485
- schema version
- 0.4.0
gold records
papers labeled
hard negatives
tests in CI
schema version
Problem
Quantitative lifespan-intervention results are locked inside thousands of papers in inconsistent formats, and the curated databases that collect them lag the literature by years. Extracting them with an LLM is easy; extracting them so that every record is auditable — and the system knows when to refuse — is the actual problem.
Approach
The measurement comes before the pipeline. A hand-labeled gold set of 10 papers and 26 intervention records defines what correct extraction means, and every field in it carries a verbatim source quote, a confidence and whether it was read from the abstract or the full text. Ingestion is built: PubMed and bioRxiv clients, DOI dedup across preprint and publication, raw abstracts stored in Postgres. Classification and extraction are next, behind that standard rather than ahead of it.
Pipeline
Select a stage for what it does and the artifact it reads or writes. Counts are the committed gold set, not a model run.
direction, 26 gold records
organism, 26 gold records
10 hand-labeled papers, 26 intervention records, counted from data/gold/. These are labels, not extractions — the model has not run against them yet.
fetchbuilt
PubMed E-utilities and bioRxiv clients pull abstracts and preprint metadata, retrying with backoff on rate limits. Preprint and published versions of the same work are deduplicated by DOI, and re-running the command does not duplicate rows. Remote XML is parsed with defusedxml rather than the standard library, because the payload is untrusted input.
iter 1 | REQUIRED: 4 | ingest/{pubmed,biorxiv,db,http,cli,dedup,models,errors}.py + 99 tests; reviewer flagged PubmedBookArticle silent drop, cross-run preprint->publication duplicate, unvalidated `published` DOI, dead self-throttleVerification
The gold set is labeled by me, never by the model, because a model grading its own extractions measures nothing. A deterministic checker verifies that every source quote appears character-for-character in the text it claims to come from — PubMed abstracts, or PMC open-access full text resolved PMID to PMCID — collapsing whitespace and nothing else, so a changed word or a changed case is a failure. A quote in a paper outside PMC open access is reported as unverifiable rather than as passing: a quote nobody can check is not a quote known to be wrong, and it is not one known to be right. Absent data is `not_reported` or null, never inferred, including where the likely answer is obvious. A 15-paper set of hard negatives across five categories — aging-without-lifespan, lifespan-without-intervention, reviews, wrong organism, lifespan-adjacent outcomes — is the other half of the classifier eval.
The gold set
A hand-labeled record, and the deterministic quote check running against the checker's own fixtures.
martinmontalvo2013-mmusculus-metformin-high-dose
Metformin improves healthspan and lifespan in mice
2013 · PMID 23900241 · data/gold/martinmontalvo2013.json
- organismM. musculusabstract · “Here we show that long-term treatment with metformin (0.1% w/w in diet) starting at middle age extends healthspan and lifespan in male mice, while a higher dose (1% w/w) was toxic.”
- strainC57BL/6full_text · “We determined the long-term effects of two doses of metformin in male C57BL/6 mice.”
- sexmalefull_text · “We determined the long-term effects of two doses of metformin in male C57BL/6 mice.”
- sample_size90full_text · “n = 90 for metformin 1% group and n = 88 for their untreated counterparts”
- intervention.typepharmacologicalabstract · “Metformin is a drug commonly prescribed to treat patients with type 2 diabetes.”
- intervention.agentmetforminabstract · “a higher dose (1% w/w) was toxic”
- intervention.dose1% (w/w) in dietfull_text · “The second dose (1% w/w) yielded a concentration of 5.03 ± 0.87 mM in serum and 3.67 ± 0.32 nmoles.mg−1 protein in the liver”
- intervention.age_at_start54 weeksfull_text · “The arrows at 54 weeks indicate the age at which metformin treatment was initiated.”
- mechanism—
- lifespan_effect.directiondecreasefull_text · “a higher concentration of the drug (1% w/w) was toxic and significantly shortened mean lifespan of C57BL/6 mice by 14.4% (Fig. 1b), likely due to renal failure”
- lifespan_effect.median_change_pct—
- lifespan_effect.mean_change_pct-14.4full_text · “a higher concentration of the drug (1% w/w) was toxic and significantly shortened mean lifespan of C57BL/6 mice by 14.4%”
- lifespan_effect.max_change_pct—
- lifespan_effect.p_value< 0.001full_text · “χ2 = 51,70 and P< 0.001 in Gehan-Breslow survival test”
11 populated · 0 not_reported · 3 null · 11 quoted
Quotes are shown without the surrounding source text: the abstracts are not committed to that repository, and a quote is shown with its verification status rather than fetched from anywhere.
Keeping the standard fixed
If an agent can edit the gold set, then a disagreement between the pipeline and the gold set can be resolved by changing the gold set, and the eval stops measuring extraction accuracy and starts measuring how readily the standard bends. So a PreToolUse hook blocks agent writes to `data/gold/` at the filesystem level while still allowing reads, and blocks agent invocation of the two flags that write there. Protection is layered because each layer fails differently: the hook catches the write itself, a reviewer subagent catches code paths that would write there, and CI catches schema and structure on every push. The hook is the only one of the three that cannot be talked out of its position.
How it's built
Autonomous implementation is only worth trusting if the review of it is independent of it, so the loop separates the two roles. An implementer subagent writes; the orchestrator runs ruff and pytest itself; a reviewer subagent then runs in a fresh context and sees the complete diff, never a summary and never the implementer's reasoning — a review that inherits the argument for a change tends to inherit its blind spots. The loop repeats until the reviewer returns zero required findings, capped at five iterations, and on hitting the cap it stops and reports rather than continuing. Each iteration appends one line to a loop log: the ingestion phase ran four, going 4 → 2 → 0 required findings before a human-requested follow-up pass. Gold-set labeling, eval design and PR review between phases are reserved to the human and cannot be reached from inside the loop.
Status
Phase 0 (foundation and gold set) and Phase 1 (ingestion) are complete; 485 tests pass in CI. Phase 2 is classification and extraction, and Phase 3 is the eval harness the gold set was built for. Nothing has been extracted by the model yet — the standard exists first, on purpose.
Stack
- Python
- PubMed / bioRxiv APIs
- PostgreSQL