PROPOSAL 0.7 PT4 — adopting AlphaGenome: a derived artifact, a two-package client, and the API as a resolver¶
Status: live, and the first live proposal since PT3 closed on 2026-09-03. Drafted 2026-09-10 against eleven measured rounds of probes/ALPHAGENOME_ATLAS.md, and decided with the maintainer the same night. It wins over the roadmap files until its items land.
Scope: five items, RM191–RM195. Four are builds; RM195 is a blocker that gates one field on one of them. Nothing here is designed from scratch — every shape below is already measured, and where a measurement refused a shape the refusal is recorded rather than argued around.
Release class: additive, fits the uncut 0.7.0. A new cache lane, a new optional extra, new
enricher checks and a new draft source are each minor-legal under P3/P8. No model field is removed,
promoted to required, or retyped. Against the 0.6 charter amendment that prices the authored
layer: RM191–RM194 add no authored column at all. They add a cache lane (free), an extra
(free), enricher checks that report (free) and a drafting provider that writes rows into tables
that already exist (free). The one authored-surface question — whether sources.csv grows a
fourth licence axis — is deliberately not proposed here; §RM195 says why.
Not in scope, named so nobody widens into them:
- The merged-splicing and SHAP artifacts. Both are non-commercial-only and both stack third- party terms (SHAP carries AlphaMissense, Cactus and phastCons columns). Only the AVI artifact is in the Permissive class. Everything below reads AVI and nothing else.
- A gnomAD allele-frequency lane. The rarity axis stays where the probe left it (§5): the
Ensembl snapshot's
MAFis 94% null andPHREDis not a rarity proxy. Not this round. - The model service. Only the Atlas service (precomputed scores) is adopted. The model service computes on demand, is the only route to indels, and is a separate decision.
- Anything AlphaGenome-derived in a published module by default. The compile gate is data-driven and stays that way.
The evidence this rests on, and the two claims it must not repeat¶
probes/ALPHAGENOME_ATLAS.md is ~1,400 lines of measurement over eleven rounds. Four of its own claims were refuted by later measurement, and every one failed the same way: a plausible reading of upstream prose that the bytes then contradicted.
- "The 22 MB client is not declarable" — refuted by reading the upstream repository, which ships
the
.protosources under Apache-2.0. - "The rarity axis is already inside the calibrated score" — inferred from the FAQ's common-variant background, refuted by measuring the corpus, which matches an exact within-corpus rank instead.
- "AVI does not fit whole" — contradicted by the document's own size table three paragraphs later.
- "
PHREDis capped at 50" — taken from the FAQ, refuted by an artifact reaching 89.451.
The standing rule for this round is therefore: measured, not inferred. Every claim an item lands must be pinned by a test or a recorded measurement. A reading of upstream prose is a hypothesis until bytes confirm it.
The material this round starts from, all already committed:
| ALPHAGENOME_ATLAS.md | the measurements, §§1–7 |
| alphagenome_poc/ | a working two-package Atlas client, 25 tests, 21 offline |
| alphagenome_knots/avi_knots.parquet | 41,474 knots, 466,370 bytes, genome-wide, verified |
| vendor/alphagenome_protos/ | the three Apache-2.0 .proto sources, PROVENANCE.txt pinning upstream aa6fc8f |
| vendor/alphagenome_*.pdf | four terms documents, hashed |
Build order, and why¶
- RM192 first — the client. Everything else that touches the network depends on it, and it is the only item whose shape is already fully built and tested. Moving the PoC into the package is mechanical; doing it first means RM193 and RM194 are written against a real surface rather than against a plan.
- RM191 second — the derived artifact. Independent of the client (it reads a local file), long (~4 h of passes), and the thing the overnight window exists for. Start its build as soon as its builder is tested, so the passes run while RM193 is written.
- RM193 third — the resolver checks. Needs RM192's client and RM191's knot table.
- RM194 last — gene-scoped drafting. Needs RM192, and is the item most likely to be cut for time. Cutting it loses nothing the others depend on.
- RM195 is not built. It is a blocker filed against RM191's
SourceRow, and it resolves by the maintainer saving one web page.
RM191 — the AVI derived artifact: an Int32 parquet plus the knot table¶
What it is¶
A cache lane, in the shape the repo's existing licence-gated lanes already have
(@gated-source-caches): an operator-built snapshot under <base>/alphagenome_avi/, holding
the AVI corpus re-encoded as parquet, plus the knot table, plus release.json. Nothing fetches it;
cache pull may serve it later, and RM195 gates whether it may be published at all.
The measurements that fix its shape¶
| decision | measurement |
|---|---|
Int32×10⁵, not Float32 |
both columns print at most 5 decimals, so the integer scale is exactly lossless while Float32 silently rounds the 5th — and is larger: 3.154 B/row against 2.413 (§4.9) |
PHRED column dropped |
it is a rank, monotone in raw_score; the knot table reconstructs it with max |Δ| 3.56e-4 and zero threshold misclassifications among unflagged rows across all 50 integer thresholds (§4.6, §4.8) |
| knot table shipped beside it | 41,474 knots / 466,370 bytes, and it carries the curve, the per-knot ambiguity interval, and decidable threshold safety at once (§4.7.2) |
| no threshold by default | the full corpus is 34.4 GB keeping raw_score alone — inside the stated budget with the sign intact. A threshold is a consumer's slice, not the artifact's shape (§4.4.3) |
raw_score signed, kept |
49.30% of the corpus is negative and abs() would discard what half of it says (§1.4) |
The build¶
Under enricher/src/just_dna_enricher/alphagenome_build.py, a typer command
just-dna-enricher alphagenome build, following clinvar_build/constraint_build:
--inputthe operator's extractedalphagenome_variant_impact_score_snvs.tsv.gz(+.tbi). Never downloads — the artifact is 88.5 GB behind a sign-in.--outdefaults torepro_out("alphagenome_avi"); the AST guard over the CLI already refuses a literal (@a-default-spelled-per-command-is-a-rule-in-prose).- Per contig via
tabix, 12-way, writingdata/alphagenome_avi-<contig>.parquet. The measured passes ran 24 contigs in 41–46 minutes; budget an hour and a half for the write. - Columns:
chrom(categorical),posUInt32— the 1-based VCF position, passed through unchanged (@start-1based) —ref/altcategorical,raw_score_e5Int32. - The knot table is rebuilt by the same command, not copied from
docs/probes/, and the builder asserts it reconciles:sum(n)over knots must equal the row count written. The committed copy is evidence; the lane's copy is the artifact. release.jsonrecords the source file'ssha256, itsLast-Modified, the AVI artifact's own 2026-08-27 stamp, and the row count. The stamp is load-bearing, not hygiene: the Output Terms pin the applicable licence version to the date the Output was generated (§2.7).LICENSE.txtbeside the data, carrying the Output Terms' "Use restrictions" section, because restriction 3b requires it to travel inside a derivative rather than as a link (§2.7). The layout constantSNAPSHOT_LICENSE_FILENAMEalready exists for exactly this.
Tests¶
- Round-trip on one contig: build, read back, and assert
raw_score_e5 / 1e5equals the source's printed value exactly for every row. That is the losslessness claim, and it is checkable. - Knot reconciliation:
sum(n)equals rows written; everyraw_scorein the data has a knot. - The threshold-safety property, which is the item's real contract: for a sample of thresholds,
rows whose knot does not straddle the threshold classify identically whether you use the stored
PHREDor the knot reconstruction. Zero tolerance, not a bound — that is what §4.8 measured. - A
SourceRowis written, and a test that stripsdeclared_useasserts the compile refuses (@write-the-sourcerow).
What it must not do¶
- Not write a
PHREDcolumn. It is 24.7 GB of a rank the knot table already carries. - Not read the splicing or SHAP artifacts. Different licence class, and mixing them into one lane makes the lane non-commercial.
- Not collapse "unscored" to zero. AVI covers ~95% of the assembly, not all of it, and it
writes 672,931 genuine zeros — so absence must be row-absence, never
0.0(§1.4).
RM192 — the Atlas client, on two packages, and the alphagenome extra deleted¶
What it is¶
docs/probes/alphagenome_poc/ moved into
enricher/src/just_dna_enricher/atlas_client.py, with the vendored protos and the generation step
coming with it. The alphagenome extra is deleted entirely.
The dependency decision, settled¶
grpcio + protobuf go in a new optional extra, [atlas] — not core. The maintainer's rule:
"while we can let it be extra; if 2+ APIs want that, we'll move to core." So the extra is the
resting place until a second gRPC surface appears, and moving it to core later is a one-line change
that needs no redesign.
protobuf is a runtime dependency, not a build one — the generated _pb2 modules
from google.protobuf import descriptor, so it is imported on every call. Only grpcio-tools
is build-only, and it joins dev. Measured, not assumed: the working client runs in a venv holding
exactly grpcio, protobuf and the generated bindings, with import alphagenome raising
ModuleNotFoundError.
| packages | size | |
|---|---|---|
| default enricher install | 45 | unchanged |
just-dna-enricher[atlas] |
47 | ~22 MB added |
the deleted [alphagenome] extra |
81 | 255 MB |
The build¶
docs/vendor/alphagenome_protos/stays where it is — it is upstream source kept for reference, which is what that directory is for.PROVENANCE.txtpins upstreamaa6fc8f(2026-09-08).- Generation moves to
enricher/and its output stays git-ignored. The staged package path must keep the private prefix: staging at upstream's ownalphagenome/protos/produces a package literally namedalphagenomethat shadows the real wheel, and the PoC'stest_the_generated_bindings_do_not_shadow_the_upstream_packageexists because that happened. - The error contract moves unchanged. It is the part that carries the house rules: three refusals
with three different remedies, and
AtlasNotScoreddeliberately not deriving fromAtlasRefusedso anexceptcannot swallow an indel and record a zero. - The 25 PoC tests move with it and join
testpaths. The AST walk pinning the import floor to{grpc, …}is the test that keeps the 22 MB claim true, and it must survive the move. enricher/pyproject.tomllines 59–63 must be rewritten in the same commit. That comment block argues the light client "is not declarable here" and calls vendoring "a decision with a maintenance cost attached" — an argument commit1f9a84aalready refuted by doing it. Left alone it survives as a stale case against what this item ships.
What a first cut owes but need not have¶
- No retry layering. The vendored
grpc_service_config.jsoncarries upstream's policy; nothing layerstenacityon top the way the enricher's other clients do (@retry-attempt-floor). File as debt rather than improvising. - No interval RPC.
ListDenseVariantScoresneeds anx-goog-fieldmaskheader and 32 bp chunking, and hand-built requests returnedINVALID_ARGUMENT. RM194 needs it; RM192 does not.
RM193 — the Atlas as a resolver: edge cases, and refusals as findings¶
What it is¶
An enricher check surface that uses the Atlas for the two things the local artifact provably cannot
do, and only those. It reports; it never repairs (@enrichment-is-validation).
The three capabilities, each measured¶
(a) Knot-straddle refinement. RM191's artifact reports an ambiguous row as the interval
[phred_lo, phred_hi]. Where a caller needs a point and the interval straddles their threshold,
the Atlas resolves it: its raw_score is a float32, ~7 significant digits against the file's
4, and at the exact atom that causes every threshold-3 flip, six rows the file prints identically
as 0.00076 come back as 0.000758832 … 0.000764675, reproducing the published PHRED exactly
at all five decimals (§4.7.1).
The scope is small and decidable in advance: ~633,000 rows genome-wide at threshold 3, and zero at every other integer threshold from 1 to 50. Rebuilding the whole column is 272 days and ~92 M RPCs, and prohibition 3 makes it the wrong shape of request anyway — so the check must refuse to run unbounded. A hard cap on rows per invocation, and a refusal naming the knot table as the cheaper answer.
(b) REF/ALT resolution fallback. The Atlas validates REF against GRCh38 and names the
real base: "Variant: chr22:36200000:A>T reference base does not match the expected reference
base: G." That is a finding the local artifact cannot produce — a file lookup on a wrong REF
simply misses, and @va-omits-ref is the standing note that a VA does not encode ref. Parse it
into AtlasRefMismatch (already implemented) and surface it as a finding with the observed base
quoted, in the shape @ref-mismatch-causes already prescribes: one window read, withhold when
ambiguous, group by reason.
(c) UNIMPLEMENTED as "not scored". An indel returns UNIMPLEMENTED, which is the third
state and not a refusal — the request was legal and the answer does not exist. AtlasNotScored
already carries it. The check must record could-not-ask, never a zero
(@unreachable-not-absent), and --offline must produce the same third state rather than a
silent skip.
On commercial / non-commercial extras beyond raw_score¶
The Atlas serves 22 scorers, and the question was whether any of them earn a place beside
raw_score for drafting or validation. Measured answer: the AVI carve-out covers the AVI Score
and nothing else. AVI_SCORE_FEATURE_IMPORTANCE, SPLICE_SITES, CHIP_TF and the rest are
ordinary Output, so they are non-commercial and carry the notice obligation. Two of them still earn
their place for validation, where the output is a finding rather than a stored value:
AVI_SCORE_MODEL_FEATURES/AVI_SCORE_FEATURE_IMPORTANCE(18 values each) explain a score rather than restating it, and they are what established that a negative AVI is low conservation rather than down-regulation (§4.7). A check that reports why a variant scored high is worth more than one that repeats the number.ALPHAMISSENSE, inside that feature vector, returnsnanon non-coding variants — a genuine missing value whereMERGED_SPLICINGwrites0.0. That distinction is directly usable.
So: no new stored column from any of them. They enter as check output under
declared_use=non_commercial, and a module that records one is tainted for sale by the existing
machinery. The gate is data-driven and needs no new axis.
Tests¶
- Every refusal path against a fake stub, asserting no
grpc.RpcErrorescapes and each arm lands on the type its remedy needs — the PoC's parametrised test, moved. - The unbounded-refinement refusal, with a cap small enough to test.
- Network tests opt-in behind
JUST_DNA_NETWORK_TESTS=1(@network-tests-optin), and a test that means "no credential" says so withsetenv(VAR, ""), neverdelenv(@test-no-credential).
RM194 — gene-scoped subslices, and the ±512 kb horizon¶
The problem this exists for¶
A gene panel wants "the SNVs that matter for these genes". Slicing the artifact by gene position answers a narrower question than it appears to: it captures coding and near-splice variants and silently drops promoters, enhancers, chromatin-altering and other distal variants that act on the gene without sitting in it.
What the API actually offers, measured¶
The Atlas attributes a variant to genes across the model's whole input window, and the reach is a hard measurable horizon:
| offset from the gene | gene-filtered scores returned |
|---|---|
| 0 | yes, max|score| 0.098 |
| +100 kb | yes, 0.0095 |
| +250 kb | yes, 0.0080 |
| +400 kb | yes, 0.0085 |
| +500 kb | yes, 0.0071 |
| +700 kb | nothing — (0, 371) |
So gene attribution reaches ±512 kb, the half-window of the 1 MB input, and stops dead beyond it. An unfiltered 400 bp interval query returns 48 genes at a 1 MB window; a gene-filtered one returns 1,200 variants × 371 tracks for one named gene in 1.1 s.
The gene filter is not an optimisation, it is required: the unfiltered query at that window
fails with RESOURCE_EXHAUSTED — "Received message larger than max".
The build¶
A drafting provider, in the shape the existing ones have: just-dna-enricher alphagenome draft
--gene <SYMBOL>, writing rows into tables that already exist. Per gene it queries the interval
spanning the gene ±512 kb with gene_names=[symbol] and requested_scorers=["RNA_SEQ"], keeps
variants above a --min-score, and writes them with the attribution recorded.
- Cost, measured: ~1,091 SNVs/s ⇒ a gene plus its ±512 kb flanks is ~3.3 M SNVs ⇒ ~50 minutes per gene. Feasible for a panel of a few genes; not feasible genome-wide, and the provider must say so rather than let an operator discover it.
- The distal decay is real and must reach the author: scores at 100–500 kb run ~10× lower than
at the gene. A single
--min-scoreapplied across the window silently keeps only proximal variants, which is the failure this item exists to prevent. Record the distance beside the score so the threshold can be distance-aware, and default to reporting rather than filtering. @gene-map-is-another-sources-attributionapplies directly: a source with no gene column is drafted by gene through another source's per-record attribution, never a span. Here AlphaGenome is the attributing source, which is the point — but the attribution must be recorded as its own claim, not folded into a positional range.
What a first cut owes¶
- The interval RPC on the two-package tier. RM192 does not build it; RM194 needs it, including the
x-goog-fieldmaskheader and 32 bp chunking that hand-built requests got wrong. - A
SourceRowfor the lane, and a second source name.@write-the-sourcerowkeys on(source, layer), and one name cannot carry two licence classes — the AVI artifact is Permissive whileRNA_SEQoutput is not. Proposealphagenome_aviandalphagenome_atlasas distinct sources, which also keeps RM195's gate on the right one.
RM195 — Permissive-class membership is unpinned, so commercial_use is None¶
Not a build. A blocker, filed against RM191's SourceRow, and it resolves when one web page is
saved.
The Additional Terms define the Permissive class and grant it commercial use, but they delegate
membership to "the 'Permissive Use Downloadable Artifact' section of the AlphaGenome Services
website" (§2.5). That page is a sign-in-gated single-page app; four terms documents are pinned in
docs/vendor/ and none of them says which artifacts are Permissive (§2.8). Every claim that AVI
is commercially usable rests on the maintainer's reading of a page the repository cannot verify.
So the AVI SourceRow carries commercial_use=None, not True. Unknown is a value here and
None is never False — this is the house algebra applied to a licence rather than to data, and
the existing @no-named-licence line already says unknown commercial terms warn rather than
gate. A None means a module carrying AVI data warns under declared_use=commercial instead of
either refusing or silently permitting.
Resolving it takes one action: save the Atlas download page the way the four terms documents were
saved. Then commercial_use=True becomes assertable and the warning goes away.
Measured beside this round: the ClinVar spectrum¶
Queued as evidence for whoever chooses a threshold. The AVI corpus's PHRED is an exact
within-corpus rank (§4.4.1), so it says nothing on its own about whether high-scoring variants are
the clinically interesting ones. Joining ClinVar's classified SNVs to the artifact answers that
directly: if pathogenic variants are not enriched at high PHRED, a threshold slice is not a
triage.
The join is tabix -R over 3,523,241 ClinVar regions against the 88.5 GB artifact, covering
3,887,455 classified SNVs (2,252,696 VUS, 1,109,507 likely-benign, 178,749 benign, 157,591
conflicting, and the pathogenic/likely-pathogenic set). It was still running when this file was
written; the run is scheduled rather than reported, and its results belong in the probe document
under §4.10 with the corpus baseline 10^(-p/10) beside each class. Whoever lands it should read
the result before treating any threshold in RM191 as a default.
What "done" looks like for this round¶
just-dna-enricher[atlas]installs a working Atlas client in 47 packages; thealphagenomeextra is gone;enricher/pyproject.toml's stale comment block is rewritten.just-dna-enricher alphagenome buildproduces<out>/data/*.parquet,avi_knots.parquet,release.jsonandLICENSE.txt, and the genome-wide run has completed intodata/repro/.- The losslessness, knot-reconciliation and threshold-safety tests pass, and the full suite is green at its previous count plus the new tests.
- RM193's checks report the three states and refuse an unbounded refinement.
- RM194 lands or is cut cleanly; nothing else depends on it.
- RM195 is filed in ROADMAP as open, with
commercial_use=Noneshipped rather than guessed.
Implementation debt, filed rather than improvised¶
Anything below is a decision the maintainer has not made and that the night run must not invent.
File each as an RMn in ROADMAP if it becomes load-bearing:
- Retry/pacing layering for the Atlas client (
@retry-attempt-floor,@shared-pacing-gate). Filed as RM280 on 2026-09-27. - Whether the lane is
cache pull-able. It rests on RM195 and on whether an HF-published snapshot counts as an "open source release" under the Terms' §2.4/1b carve-out — a legal question, not ours. - A distance-aware threshold for RM194's distal variants.
- Whether
PHREDshould ever be materialised for a thresholded export, and if so, whether it carries the interval or a point. - Wide-by-position layout for AVI (saved 17% on splicing; unmeasured here). Measured 2026-09-10
while building RM191 and filed as RM197: 3.882 B/row long against 3.371 wide on chr22, so a ~13%
question rather than a forcing one. This line briefly said 31%, against a 4.878 B/row baseline
that was the builder's
sink_parquetfragmenting the delta encoding rather than the layout's cost — the 34.4 GB this file quotes was right, and the corrected build lands at 34.2. The episode is@a-disagreement-with-a-document-may-be-in-the-instrument.