StableFold PGK2 · CACHE / Target 2035 · Nov 2026

DNA-encoded libraries

Reading the
unreadable.

Seven and a half million molecules carry a signal almost nobody can extract. We built the estimator that extracts it — without a single binding label.

← → to navigate
Investor briefing 01 / 13
01 — The asset Why DEL counts go unused

One experiment. Millions of measurements. Almost all of them discarded.

7,465,100 molecules measured one target, one selection
10 libraries hit rates spanning 0.67% to 0.0028%
1.57% seen more than once 117,103 of 7,465,100

A DNA-encoded library screen is the cheapest way in pharma to ask millions of molecules a question at once. The answer comes back as a sequencing read count — and the standard practice is to apply a hand-picked cutoff and throw the rest away. The data is not the bottleneck. Reading it is.

Source: distribution_fits.json · del_model_v2 02 / 13
02 — The blocker qDOS30 · 1,727,652 molecules

Almost every molecule was seen exactly once.

1 read 98.960%
2 0.640%
3 0.147%
4+ 0.236%

A single read is close to no information. Across all ten libraries the singleton share runs 97.4% to 99.9%. Every per-molecule number computed from this data is dominated by the prior you impose on it — which is why the counts normally get thrown away.

Source: distribution_fits.json · del_model_v2 03 / 13
03 — The idea Don't threshold. Infer.

A cutoff is a guess. We let each library tell us where its own signal starts.

Every library's count histogram is fitted as two overlapping populations — one that is pure sequencing noise, one that is real binding. Five candidate zero-truncated families per library, chosen by BIC. What falls out is not a threshold but a probability.

count 1 P = 0.002
2 P = 0.136
3 P = 0.931
4 P = 0.999

P(signal | count) for qDOS30. The decision boundary lands at 3 — but it was derived, not chosen, and it comes out at 4 in qDOS18_3. Ten libraries, ten separate fits.

m1_distribution.py · BIC-selected mixture 04 / 13
04 — The pipeline Counts → labels → ranking

Six stages. No human ever labels a molecule.

16,104 positives inferred from counts alone, across five libraries
99,752 quarantined count ≥ 2 the noise model explains — kept with a reason, never silently dropped
0 binding labels used the model never sees a known active

The fitted distribution assigns every molecule a label. A gradient-boosted tree then learns to predict that label from chemical structure alone — so it can score molecules that were never in the library at all. Counts teach the model; structure lets it travel.

m1 → m6 · labels_summary.csv 05 / 13
05 — Why it is believable Leak-proof by construction

The easiest way to fake a good number on DEL data is to let the model mark its own homework.

Molecules built from the same two building blocks are near-identical. Split them at random across training and test and the model simply recognises what it has already seen — and every reported score is inflated.

So the folds are grouped by disynthon pair: a deterministic hash of the library and the (b1, b2) pair decides which fold a molecule belongs to. The guarantee is absolute — no molecule is ever scored by a model that saw its own building-block pair. All 7,465,100 labelled rows were scored this way.

common.py · fold_of(lib, b1, b2, K)

Cross-fitted, not cross-validated 06 / 13
06 — The result Full population, five libraries

It finds the signal.

0.940 – 0.984 cross-fitted AUC every molecule, no sampling
54× – 324× enrichment, top 0.1% qDOS23 lowest, qDOS18_3 highest
0.831 physicochemistry alone the honest floor — we measured how much is just library chemistry

Every design choice was settled by a 154-run bake-off on identical folds: how to treat single-read molecules, which fingerprint, which learner. The configuration that won programmatically was rejected — its features cannot be computed for a molecule outside the library. We measured the price of that constraint: 0.003 AUC.

full_population_performance.csv · bakeoff.jsonl 07 / 13
07 — Where we stand Active Learning phase

Two scored entries in the active learning phase.

[ rank ] entry 1 pending confirmation of hits / series
[ rank ] entry 2 pending confirmation of hits / series
0 scored with the count model both slots were spent before it was built

The challenge scores distinct series first, not raw hits — because a programme needs several independent chemical starting points, not fifty variations of one. Our scored entries came from the fingerprint selection model. The count model described here has never been scored, and nothing on the following slides claims otherwise.

Synapse queue 9619687 · active learning phase 08 / 13
08 — Controlled, not lucky The clean experiment

We can prove which part of the result was the model and which was the selection rule.

0.894892 ROC-AUC · both runs identical to six decimals
0.274679 PR-AUC · both runs identical to six decimals
1 variable changed the picking rule, nothing else

Two entries, byte-identical score columns, one difference: how widely the 50 picks were spread across chemical space. The identical AUCs are the proof the model was untouched — so the entire difference in outcome is attributable to the rule. That is a controlled experiment, run prospectively, against a held-out set we could not see. It also told us the rule is close to a spent lever: across cap settings the series count moves within a narrow band.

Active learning phase · paired submissions 09 / 13
09 — The headroom 178,052 molecules, two rankings

The two models barely agree. That is the asset, not the problem.

0.12 Spearman correlation 0.1155 – 0.1317 over 178,052 molecules
2 shared in the top 100 36× more than chance
1.2× agreement at top 50,000 barely above chance — they rank different things

A fingerprint model trained on 39 known binders and a count model trained on zero binders pick almost disjoint molecules — yet both concentrate their agreement exactly at the top. Two independent lines of evidence about the same chemistry. Combining them has never been tested end to end. That is the single largest piece of unspent headroom in the programme.

testrun/overlap_summary.csv 10 / 13
10 — The platform What transfers to the next target

This is not one answer. It is a procedure that runs on any DEL screen.

3¼ hrs counts to frozen model 7.5M molecules, one cloud VM
0.0 reproduction difference max absolute, re-scoring 178,052 molecules
4 pinned dependencies RDKit · LightGBM · scikit-learn · numpy

Nothing in the method is specific to PGK2. It needs read counts and structures — which every DEL screen already produces. The release is frozen, hash-manifested and bit-exact reproducible, and the discarded data is retained with a recorded reason for each exclusion. An auditor can rerun it and get the same numbers.

model_release_v2 · MANIFEST.json 11 / 13
11 — Where it breaks Measured, not hidden

We know exactly where this model is weak, because we went looking.

seen 0.959
unseen 0.827
worst 0.586

Held-out AUC when whole building blocks are hidden from training — the regime that applies when scoring molecules from outside the library. 0.959 drops to 0.827, and one library falls to 0.586. No published method solves off-library transfer; the literature does not demonstrate it either. We report the number that a competitor's deck would leave out, because it is the number that defines the next piece of work.

strict_generalisation.csv 12 / 13
12 — What comes next The next twelve months

Three pieces of work, in priority order.

01 Fuse the two signals Spearman 0.12 means unspent information. Combining count evidence with the labelled selector has never been run end to end — the cheapest available gain.
02 Close the transfer gap Lift 0.827 on unseen building blocks. Nobody in the field has demonstrated this; solving it is defensible IP, not catch-up.
03 Pre-register and price every rule Run the ablation that attaches a measured gain to each filter, declared before the data is seen.

[ the ask — amount, use of funds, milestone ]