Designing T-cell receptors with evolution, not gradients
tcrga is a research project at
Friedrich-Alexander-Universität Erlangen-Nürnberg
that uses a genetic algorithm to search for T-cell receptor sequences predicted to
recognise a peptide you choose. This page explains the problem, why evolution is the
right search strategy for it, and — importantly — what the results do and do not mean.
Given a short target peptide, tcrga evolves a population of candidate
receptor sequences and keeps the ones a predictive model scores as most likely to
bind — producing a shortlist of hypotheses, not confirmed binders.
The immune problem, in plain terms
T-cells patrol the body for signs of disease. They do not see whole diseased cells; they see short fragments of protein, called peptides, displayed on a cell's surface by a molecular presenting tray (the MHC complex). Sitting on the T-cell's surface is a T-cell receptor (TCR) — a Y-shaped protein built from two chains, called alpha and beta. If the shape of the receptor's tip fits the presented peptide well enough, the T-cell activates and the cell displaying that peptide is marked for destruction. This is, in outline, how the immune system recognises viral infection and some cancers.
The part of each chain that actually makes contact with the peptide is a short, highly variable loop called the CDR3 (complementarity-determining region 3) — typically 8 to 24 amino acids long. Its exact sequence, on both chains together, determines what the receptor recognises.
Now flip the problem around: given a peptide of interest — say, one associated with a particular infection or tumour — can you find a pair of CDR3 sequences (alpha and beta) predicted to bind it? This is a search over an enormous space. Each CDR3 is a string drawn from a 20-letter amino-acid alphabet at up to 24 positions, and the two chains have to work together: they contribute jointly and non-additively to recognition, so you cannot optimise one chain and then the other. It is a genuine needle-in-a-haystack problem, and the haystack is bigger than can be enumerated.
Why a genetic algorithm
The usual way to search a huge space efficiently is gradient descent: nudge a candidate solution in the direction that a differentiable scoring function says improves it. That requires the scoring function to hand back a gradient — a mathematical derivative — alongside its score.
The scoring functions available for TCR-peptide binding do not do that. They are black boxes: statistical distance measures over known binders, positional weight matrices, gradient-boosted classifiers, or wrapped neural network predictors. Some of them are differentiable internally, but the project deliberately treats every scorer the same way — as a function that takes a candidate sequence pair and peptide and returns a probability, nothing more — so that scorers can be swapped, combined, or replaced without touching the search itself.
A genetic algorithm (GA) is a search strategy modelled loosely on
evolution: keep a population of candidate solutions, score each one, preferentially
keep and recombine the better-scoring ones, introduce small random changes
(mutations), and repeat over many generations. Because it only ever needs a score —
never a gradient — it works with any scoring function you can plug in, including
ones nobody has made differentiable. That is precisely the situation here, which is
why tcrga is built around
pymoo, a Python optimisation library, rather than a
deep-learning training loop.
What the algorithm is actually optimising
The primary objective is a single number: the predicted probability that a given (alpha, beta) sequence pair binds the target peptide. Two further checks act as guard rails rather than co-equal goals:
| Term | Role | Direction |
|---|---|---|
| P(bind | CDR3-alpha, CDR3-beta, peptide) | the objective | maximise |
| Biological plausibility (generation probability) | feasibility constraint | must stay above a threshold |
| Predicted off-target / cross-reactive binding | feasibility constraint | must stay below a threshold |
A candidate that violates a constraint is treated as infeasible rather than merely penalised, so the search stays pointed squarely at binding while still ruling out sequences that no real thymus would ever produce, or that would likely attack the wrong target.
Where the training data comes from
McPAS-TCR is a manually curated database of TCR sequences linked to known diseases, maintained by the Friedman lab at the Weizmann Institute. It pairs CDR3-alpha and CDR3-beta sequences with the peptide each receptor is known to respond to, which is exactly the kind of labelled example the scoring models need. Anyone using results from this project should cite the database (see the project README for the full citation).
The database's own export has no stable download link — its CSV export is generated
per browser session — so tcrga fetches it with a small script
(scripts/fetch_mcpas.py) that drives a headless browser to click the
site's own download control, the same way a person would, rather than trying to guess
a URL.
The raw export is large but the usable signal inside it is much smaller: of 40,731 rows, only 4,988 survive cleaning as usable paired alpha/beta records (most rows are beta chain only), and just 38 peptides have 10 or more distinct paired receptors behind them — the minimum needed for a scorer to learn anything peptide-specific. About 59% of all rows carry no epitope annotation at all. Knowing these numbers before designing the scorers, rather than after, is itself an example of the project's spec-first approach.
What this is not
Every sequence this tool produces is a predicted binder according to a computational model, not a laboratory-confirmed one. Published TCR-peptide binding predictors are known to generalise poorly to peptides they were not trained on — a well-documented limitation of the whole field, not a shortcoming specific to this project. Treat the output as a prioritised, diverse shortlist of hypotheses for experimental validation, never as a validated result on its own.
Optimising hard against a single predicted score also has a sharp edge: a genetic algorithm will cheerfully find sequences that score very well and mean nothing biologically, if the scoring function has any exploitable weakness. The spec-first design page describes a case of exactly that happening during development, and how it became a permanent safeguard.
Read more
Spec-first design
Why this project wrote its README and plan before any code, and what that bought it when working with an AI coding assistant.
Prompts used
The actual prompts that drove the project, verbatim, as a worked reference.
Getting started
How to set up the same toolchain: Claude Code, a GitHub account, and GitHub Pages.
Environment notes
Why the project pins Python 3.12 and several dependency versions, with the platform evidence recorded.
Predictor survey
What was found when probing whether existing TCR-specificity predictors could be installed and run offline.