Skip to content

A minimal AlphaGenome Atlas client — the blueprint, test-proven

Adopted on 2026-09-10 by RM192. The code left this directory and now lives as enricher/src/just_dna_enricher/atlas_client.py (the client), atlas_protos.py (the generator, run as just-dna-enricher atlas generate) and enricher/tests/test_atlas_client.py (the tests, now inside testpaths). The dependencies are the [atlas] extra, and the alphagenome extra is gone. This file stays as the record of what the blueprint cost and what it refused, which is the half that does not travel with the code; everything below describes the probe as it stood, in the past tense as of that date.

Not a shipped surface, and not adopted. Nothing in just-dna-enricher imports this, it sits outside testpaths so uv run pytest does not collect it, and no RMn covers it. It exists to turn one sentence of ALPHAGENOME_ATLAS.md § 6.2 into something a test can fail: the Atlas service is reachable on grpcio + protobuf alone.

That claim was first written here as "a 22 MB client exists and is not declarable". Reading the upstream repository showed the second half was wrong — github.com/google-deepmind/alphagenome is Apache-2.0 and ships the .proto sources its own wheel generates bindings from, so the light path is declarable after all. This directory is what that costs.

Run it

uv run --with grpcio-tools python docs/probes/alphagenome_poc/generate.py
uv run --with grpcio-tools pytest docs/probes/alphagenome_poc/ -vvv

Twenty-one of the twenty-five tests need no network and no key. The four live ones need both ALPHAGENOME_API_KEY and the repo's opt-in switch JUST_DNA_NETWORK_TESTS=1 (@network-tests-optin); they are skipped otherwise.

What it costs

declared runtime dependencies grpcio, protobuf
build-time only grpcio-tools, declared nowhere
vendored source 28 KB — three Apache-2.0 .proto files in docs/vendor/alphagenome_protos/
installed size 22 MB, against 255 MB for uv add alphagenome
decode struct.unpack from the standard library — not even numpy

What the tests actually pin

Five of them carry the argument; the rest keep the client honest.

  • test_imports_stay_within_the_declared_floor walks the client's AST and asserts its third-party imports are exactly {grpc, docs}. An AST walk rather than a sys.modules check, because by the time the module is imported another test's heavier import would already be resident and the assertion would pass for the wrong reason. This is what makes "22 MB, not 255 MB" a property of the code instead of a claim in prose.
  • test_bindings_regenerate_from_the_vendored_sources regenerates into a scratch directory. The repository carries .proto sources, not generated code, so the bindings are reproducible from what is committed rather than from what a wheel happened to ship.
  • test_the_generated_bindings_do_not_shadow_the_upstream_package is the one that came out of a real mistake. protoc bakes the staged path into every cross-import, so staging at upstream's own alphagenome/protos/ produces a package literally named alphagenome — which shadows the real wheel for anyone who installs both, and fails with an import error far from its cause. The sources are staged under the full package path instead, so the cross-imports read from docs.probes.alphagenome_poc.generated._alphagenome_atlas_protos import …: unambiguous, unshadowable, and importable without a sys.path insertion.
  • test_the_api_reproduces_the_downloaded_file is the finding that matters most for adoption. It asserts the Atlas returns what the 88.5 GB AVI artifact contains — raw to 5e-6 and the derived PHRED to 1e-4 — so the download and the RPC are one source. If it ever fails, they have diverged and § 6.4 needs re-measuring.
  • test_the_api_saturates_where_the_download_still_has_a_value is its counterweight, and it came out of measuring the AVI file whole. calibrated_scores is a float32, so the largest quantile it can carry caps a derived Phred at 72.247 — while the published artifact reaches 89.451. Above the cap the API returns exactly 1.0 and the rank is unrecoverable. It affects about 1,300 rows genome-wide, which are precisely the highest-impact ones. An earlier version of this client clamped at the FAQ's Phred 50 and would have rewritten those as fifty; the clamp is gone, and a saturated quantile now raises AtlasNotScored while VariantScore.phred returns None.

The three-valued part

The error hierarchy is not decoration. The service says no in three different ways and each has a different remedy:

what happened type remedy
transport failed AtlasUnavailable retry
REF disagrees with GRCh38 AtlasRefMismatch fix the caller's data — and the server names the real base
an indel AtlasNotScored none: the answer does not exist
a quantile saturated at 1.0 AtlasNotScored read PHRED from the downloaded file instead

AtlasNotScored deliberately does not derive from AtlasRefused, so an except AtlasRefused cannot swallow it. A caller that recorded "no score" for a variant the service never claimed to have scored would be @unreachable-not-absent in one line, and test_not_scored_is_not_a_refusal is what stops it. test_handler_order_is_not_load_bearing_by_accident enumerates the ladder, because AtlasRefMismatch being a subclass makes a caller's except order load-bearing (@client-exception-contract).

What it does not do

  • No interval RPC. ListDenseVariantScores needs an x-goog-fieldmask header and 32 bp chunking; hand-built requests returned INVALID_ARGUMENT and the § 6.5 motif measurement was taken through the SDK instead. Adding it here is the obvious next step and was not taken, since nothing needs it yet.
  • No retry loop. The vendored grpc_service_config.json carries upstream's policy and is passed to the channel, but nothing here layers tenacity on top the way the enricher's own clients do (@retry-attempt-floor). Adoption would.
  • No caching, no rate limiting, no SourceRow. Every one of those is a real requirement for a shipped enricher lane (@write-the-sourcerow, @shared-pacing-gate) and every one is absent, because this proves reachability and nothing else.

The cost this blueprint hides

Generated code has no compile-time signal when the service's protos move. Upstream regenerates on every release; a vendored copy goes stale silently, and the failure surfaces as a decode error or a missing field rather than a build break. The blueprint answered that with a PROVENANCE.txt recording the commit (aa6fc8f, 2026-09-08) so re-vendoring was a diff — and RM196 later removed the vendored copy entirely: the repository carries a commit id and a sha256 per file, and atlas_protos.fetch_protos() refuses anything that does not match. Noticing a re-pin is due is still manual. That is the price of the light path, and it is the reason § 6.2 listed four shapes and picked none.