BoltzMaker: Vibe-Coding a Campaign-Scale Boltz-2 Pipeline
8 Jul 2026 · updated 17 Sep 2026 · 20 min read
BoltzMaker: Vibe-Coding a Campaign-Scale Boltz Pipeline
- Spec-driven, end-to-end: One annotated file drives generation, validation, prediction, and reporting for an entire Boltz-2 campaign.
- Chemistry checked before compute: RDKit flags stereocentres, salts, and protonation ambiguity before hours of folding, not after.
- Built for modest hardware: Memory-aware defaults and resumable runs make campaign-scale prediction viable on a 64GB MacBook Pro M1.
- Expert plus AI, not either alone: A domain scientist who cannot code professionally can still ship a real, documented tool.
- Checked against known biology:
compare-ssereports apo-versus-holo movement as motif language, and reproduced the textbook TM6 outward swing on Gq coupling in 6 of 6 pairs. - Failures made survivable: An exact attention-chunking patch and automatic per-target retry turned a hard MPS crash and a mid-run OOM into campaigns that simply finish.
- Boltz-2: Towards Accurate and Efficient Binding Affinity Prediction: Passaro et al., bioRxiv, 2025
- Accurate structure prediction of biomolecular interactions with AlphaFold 3: Abramson et al., Nature, 2024
- PLIP 2025: introducing protein-protein interactions to the protein-ligand interaction profiler: Schake et al., Nucleic Acids Research, 2025
- RDKit: Open-source cheminformatics: Landrum et al., rdkit.org
- The Properties of Known Drugs. 1. Molecular Frameworks: Bemis and Murcko, J. Med. Chem., 1996
- GPCRdb in 2025: adding odorant receptors, data mapper, structure similarity search and models of physiological ligand complexes: Taracena Herrera et al., Nucleic Acids Research, 2025
- KLIFS: an overhaul after the first 5 years of supporting kinase research: Kanev et al., Nucleic Acids Research, 2021
How five small single-purpose tools became one spec-driven workflow that folds, scores, and reports a full Boltz-2 affinity campaign on modest local hardware
Structure prediction has quietly become the cheap part of the problem. Boltz-2 will fold a protein and a ligand together and estimate their binding affinity, but running a real campaign by hand still means writing dozens of near-identical YAML files, remembering the correct flags for whatever hardware you’re on, and picking through prediction JSONs afterward.
Those are repetitive, error-prone steps that do not need a human. BoltzMaker collapses them into one spec-driven pipeline: a single annotated file becomes generated inputs, validated chemistry, a monitored run, and a report you can read at a glance.
And the name? It is a nod to Boltmaker, Timothy Taylor’s Champion Beer of Britain, and a personal favorite; the pipeline runs whether or not there is a pint in hand.
A candid disclaimer belongs here. I am not a professional software engineer, and BoltzMaker is not production software. It is an exercise, and the latest entry in my ongoing vibe-coding series, in which a subject-matter expert can build with their own data on a laptop, for the price of some electricity and a bit of patience. That framing is the whole point: the value is not the code; it is that the person who understands the science can now assemble the tooling around it themselves, rather than waiting on someone who does not.
“Crucially, it achieves strong correlation with experimental readouts on many benchmarks, while being at least 1000× more computationally efficient than FEP.” – Boltz-2: Towards Accurate and Efficient Binding Affinity Prediction, Passaro et al., bioRxiv, 2025
Installation
1. Open the Terminal app (Applications -> Utilities -> Terminal, or search for “Terminal” with Spotlight), then make a folder for BoltzMaker and go into it:
mkdir -p ~/Desktop/BoltzMaker
cd ~/Desktop/BoltzMaker
2. Download BoltzMaker into that folder:
git clone https://github.com/bellcheddar/BoltzMaker.git .
The trailing . tells it to put the files directly into the folder you just made, rather than inside another new folder. If this is the first time you’ve used git, macOS may pop up a dialog asking to install “Command Line Tools” — click Install and wait for it to finish, then run the command again.
3. Run the installer:
./install.sh
Wait for it to finish. It downloads a few GB of software the first time, so it can take a while depending on your connection.
4. Check it worked:
pixi run preflight examples/t4_lysozyme/boltz_input.md
If you see a table of green PASS results, you’re ready to go — see Commands below for what to run next.
How BoltzMaker works, and what it grew from
BoltzMaker consolidates five smaller tools I vibe-coded earlier while learning to vibe, each doing exactly one job: generate_yaml for building Boltz input YAMLs, simple-zsh-script-to-run-boltz2 for driving prediction runs, analyze-boltz2-results for post-run analysis, cif2plip for protein-ligand interaction profiling, and smiles2grid for rendering a ligand set as a grid of 2D structures. Each vibe-coded project taught me one piece. BoltzMaker shares a single campaign format across all five stages, so the same input file drives every step rather than retyping target names between separate scripts.
The pipeline is five stages, and each stage reads only what the previous one wrote, so any stage can be re-run on its own:
+----------------+ +----------+ +-----------+ +-----+ +---------+
| boltz_input.md | --> | generate | --> | preflight | --> | run | --> | analyze |
+----------------+ +----------+ +-----------+ +-----+ +---------+
|
| opt-in, separate: needs an "Apo structure:" reference
v
+-------------+
| compare-sse |
+-------------+
setup create the managed .venv (Python 3.12; boltz pins numpy<2.0)
setup-plip optional .plip_env for cif2plip/PLIP interaction profiling
new interview in plain English -> writes a valid boltz_input.md
format auto-align comments/blank-lines (cosmetic only; --check to dry-run)
generate write per-target YAMLs + a manifest
preflight PASS / WARN / FAIL: CLI, GPU/MPS, disk, YAML, SMILES, chemistry, memory
run boltz predict, live progress + memory monitor, resumable
analyze boltz_summary.csv / .xlsx, boltz_dashboard.html, interaction files
all generate -> preflight -> run -> analyze
compare-sse apo vs holo motif shifts -> boltz_sse_comparison.csv / .html / .pml
The workflow is as follows, and the reasoning behind the roadmap lives in PROJECT_PLAN.md:
generatewrites the per-target YAMLs and a manifest.preflightis the sanity gate: it checks that the CLI is present, that a GPU or Apple MPS backend is available, that there is disk and memory headroom, and that every YAML, SMILES string, and chain ID is valid.runcallsboltz predictwith a live progress bar and resumes cleanly if it is interrupted.analyzeturns the outputs into a CSV, an XLSX workbook, and a self-contained HTML dashboard.
The ergonomic core is a family, partners, and ligand cross-product: write each protein and each ligand once, and BoltzMaker generates every combination as its own target. That is exactly the shape of a real selectivity or SAR panel, which is what makes the spec worth writing in the first place.
Running BoltzMaker
Everything installs into a dedicated virtual environment through one setup command:
python3 BoltzMaker.py setup
Available switches include all, which runs the full pipeline starting from a pre-written boltz_input.md, and new, which invokes a wizard that interviews you (proteins, partners, ligands, and the three constraint sentence types) and writes the file for you. It covers only the common cases; rarer fields (modifications, cyclic, MSA override, templates, homo-oligomer copies) are left for hand-editing the file it writes.
python3 BoltzMaker.py setup # create the managed .venv (--force, -y/--yes)
python3 BoltzMaker.py setup-plip # optional .plip_env for interactions (--force)
python3 BoltzMaker.py new [boltz_input.md] # write a new campaign by answering plain questions
python3 BoltzMaker.py format boltz_input.md # auto-align comments/blank-lines, cosmetic (--check)
python3 BoltzMaker.py generate boltz_input.md # write the target YAMLs + manifest
python3 BoltzMaker.py preflight boltz_input.md # environment + input sanity checks
python3 BoltzMaker.py run boltz_input.md # boltz predict, live progress, resumable
python3 BoltzMaker.py analyze boltz_input.md # CSV / XLSX / HTML dashboard
python3 BoltzMaker.py all boltz_input.md # generate -> preflight -> run -> analyze
python3 BoltzMaker.py compare-sse boltz_input.md # opt-in: apo vs holo secondary-structure shifts
python3 BoltzMaker.py boltz_input.md # same as `all` (subcommand is optional)
SETUP & FORMAT OPTIONS
======================
Option Applies to Description
---------------------------- ------------------- --------------------------------------------------
--force setup, setup-plip Recreate the .venv / .plip_env from scratch
-y, --yes setup Skip the download-confirmation prompt
--check format Report whether reformatting is needed without
writing (exit 1 if so)
COMMON OPTIONS
==============
Option Default Description
---------------------------- ------------------- --------------------------------------------------
--output-dir settings.output_dir Override where generated YAMLs are written
--out-dir ./boltz_output Boltz's own --out_dir, next to the md file
--accelerator auto auto / gpu / cpu
--limit N none Cap how many pending targets 'run' submits
(smoke test before a full batch)
--strict off Promote preflight WARN to FAIL
--skip-interactions off Skip cif2plip interaction analysis during
analyze, even if setup-plip has been run
MEMORY-CONTROL OPTIONS (unified memory / Apple Silicon)
=======================================================
Option Default Description
---------------------------- ------------------- --------------------------------------------------
--workers 2 Matches Boltz's own default
--mps-watermark 1.0 PYTORCH_MPS_HIGH_WATERMARK_RATIO cap
--max-parallel-samples 1 Boltz --max_parallel_samples
--recycling-steps Boltz default Passthrough
--sampling-steps Boltz default Passthrough
--diffusion-samples-affinity Boltz default Passthrough
--sampling-steps-affinity Boltz default Passthrough
--max-msa-seqs Boltz default Passthrough
--memory-warn-tokens 1000 Preflight size-heuristic WARN threshold
COMPARE-SSE OPTIONS
===================
Option Default Description
---------------------------- ------------------- --------------------------------------------------
--out-dir spec directory Where boltz_sse_comparison.csv / .html /
_sessions/ are written
--phi-psi-threshold see --help Flag per-residue backbone dihedral outliers
above this angle (degrees)
Python 3.12 is required because boltz pins numpy<2.0, which has no prebuilt wheel for newer versions. The script installs boltz, rich, pandas, openpyxl, pyyaml, rdkit, matplotlib, psutil, scipy, gemmi, biopython, plotly, and reportlab. It also pulls PyTorch (~2-3 GB), and the first timeboltz predict actually runs, Boltz downloads several GB of model weights over the network.
The dependency list is deliberately short:
# Core BoltzMaker dependencies (Python 3.12; boltz pins numpy<2.0)
boltz # Boltz-2 structure + affinity model
rich # live progress interface
pandas # tabular summaries
openpyxl # XLSX workbook output
pyyaml # generated Boltz input files
rdkit # ligand chemistry + validation, 2D depiction, scaffold matching
matplotlib # static figures
psutil # live memory monitoring
scipy # similarity ordering; motif geometry for compare-sse
gemmi # structure parsing; apo-to-holo superposition for compare-sse
biopython # sequence alignment (pocket-contact remapping, motif numbering)
plotly # interactive dashboard charts; compare-sse bar chart + heatmap
reportlab # printable ligand grid (PDF)
# Network services used at runtime by compare-sse (no install, no API key)
# GPCRdb -> generic numbering for GPCR motifs (TM1-7, H8, ECL/ICL)
# KLIFS -> 85-residue kinase pocket alignment (hinge, gatekeeper, HRD, DFG, aC-Glu)
# PDBe -> SIFTS residue mapping to Pfam domain boundaries (universal fallback)
Each package earns its place
boltz is the model itself. rich draws the progress interface. pandas and openpyxl produce the CSV and XLSX outputs. pyyaml writes the generated inputs. RDKit handles every piece of ligand chemistry. matplotlib and Plotly render the figures and the interactive dashboard charts. psutil watches memory during a run, which matters more than you might expect on unified-memory Macs. scipy orders ligand series by similarity, gemmi and biopython parse structures and run the sequence alignment behind pocket-contact suggestions, and reportlab writes the printable ligand grid. The whole thing is a single script, BoltzMaker.py, whose subcommands each transparently relaunch under the managed environment. Interaction profiling with PLIP and cif2plip is optional and lives in a separate conda-forge environment, because PLIP needs OpenBabel and PyMOL as in-process imports that do not install cleanly through pip.
The input file
The single input spec, boltz_input.md, is plain labeled text: Label: value lines with a blank line between blocks, comments starting with #, and field names written in plain English (Output folder, Predict affinity, Pocket contact) rather than Boltz-internal YAML format, which is finicky and difficult to parse. Constraint sentences for covalent bonds, distance restraints, and pocket contacts sit on their own lines and name the protein they belong to.
Here is an example of a 5-HT2A/2B/2C serotonin receptor screening campaign, agonist vs. antagonist, with vs. without the Gq heterotrimer, that ships as a worked example. This runs structure and affinity prediction for three receptors and their Gq alpha partner with an agonist and an antagonist pair for each:
# Example: 5-HT2A/2B/2C serotonin receptors, agonist vs antagonist, with vs
# without the Gq heterotrimer
#
# Three closely related Gq/11-coupled serotonin receptors (5-HT2A UniProt
# P28223, 5-HT2B UniProt P41595, 5-HT2C UniProt P28335), each crossed with
# a real agonist and a real antagonist, each ligand predicted BOTH with
# and without the Gq heterotrimer (GNAQ + GNB1 + GNG2) co-folded --
# following the same lesson learned from adrb2_gs_panel: co-folding a G
# protein biases Boltz toward an active-like fold regardless of ligand
# identity, so a fair agonist-vs-antagonist comparison needs the receptor
# alone as well as the physiologically-correct (agonist+Gq) condition.
# Every receptor gets two Protein: blocks sharing one sequence -- one
# with Partners: (the Gq trimer), one without -- exactly as
# adrb2_gs_panel does, extended to a 3x2x2 = 12-target panel: 3
# receptors x 2 ligands x (with/without Gq).
#
# Apo reference structures: genuinely ligand-free, not an antagonist-
# bound "inactive-like" stand-in. Checked entity-by-entity across all 59
# deposited structures for these 3 receptors (not trusting RCSB's summary
# fields, which turned out to have real gaps) -- every single one has a
# real ligand bound, including ones that first looked apo (e.g. what
# looked like a ligand-free 5-HT2B entry turned out to have ergotamine).
# No genuinely apo experimental structure exists for any of the three.
# Instead, each receptor's own apo reference is a Boltz prediction of the
# receptor ALONE (no ligand, no partner) -- a real BoltzMaker target via
# `Ligands: none` (see the 3 apo Protein: blocks below), staged and run
# in the exact same `boltz predict` batch as the 12 ligand-bound targets
# below (one manifest, one campaign, no separate invocation), producing
# reference/*_apo_predicted.cif via boltz_cif/. Clearly a computational
# model, not an experimental one, but unambiguously ligand-free.
#
# All 6 ligands are real, clinically/pharmacologically characterized
# compounds: Risperidone and Psilocin (5-HT2A antagonist/agonist),
# Balovaptan and LSD (5-HT2B antagonist/agonist), SB-242084 and
# Lorcaserin (5-HT2C antagonist/agonist).
Settings:
Output folder: ./boltz_yamls
Predict affinity: yes
# ===================== 5-HT2A =====================
Protein: 5HT2A
Sequence: MDILCEENTSLSSTTNSLMQLNDDTRLYSNDFNSGEANTSDAFNWTVDSENRTNLSCEGCLSPSCLSLLHLQEKNWSALLTAVVIILTIAGNILVIMAVSLEKKLQNATNYFLMSLAIADMLLGFLVMPVSMLTILYGYRWPLPSKLCAVWIYLDVLFSTASIMHLCAISLDRYVAIQNPIHHSRFNSRTKAFLKIIAVWTISVGISMPIPVFGLQDDSKVFKEGSCLLADDNFVLIGSFVSFFIPLTIMVITYFLTIKSLQKEATLCVSDLGTRAKLASFSFLPQSSLSSEKLFQRSIHREPGSYTGRRTMQSISNEQKACKVLGIVFFLFVVMWCPFFITNIMAVICKESCNEDVIGALLNVFVWIGYLSSAVNPLVYTLFNKTYRSAFSRYIQCQYKENKKPLQLILVNTIPALAYKSSQLQMGQKKNSKQDAKTTDNDCSMVALGKQHSEEASKDNSDGVNEKVSCV
Partners: GNAQ, GNB1, GNG2
Ligands: RISP, PSIL
Apo structure: boltz_cif/H2AAP_model_0.cif
Family type: gpcr
Group: 5HT2A
Protein: H2ANG
Sequence: MDILCEENTSLSSTTNSLMQLNDDTRLYSNDFNSGEANTSDAFNWTVDSENRTNLSCEGCLSPSCLSLLHLQEKNWSALLTAVVIILTIAGNILVIMAVSLEKKLQNATNYFLMSLAIADMLLGFLVMPVSMLTILYGYRWPLPSKLCAVWIYLDVLFSTASIMHLCAISLDRYVAIQNPIHHSRFNSRTKAFLKIIAVWTISVGISMPIPVFGLQDDSKVFKEGSCLLADDNFVLIGSFVSFFIPLTIMVITYFLTIKSLQKEATLCVSDLGTRAKLASFSFLPQSSLSSEKLFQRSIHREPGSYTGRRTMQSISNEQKACKVLGIVFFLFVVMWCPFFITNIMAVICKESCNEDVIGALLNVFVWIGYLSSAVNPLVYTLFNKTYRSAFSRYIQCQYKENKKPLQLILVNTIPALAYKSSQLQMGQKKNSKQDAKTTDNDCSMVALGKQHSEEASKDNSDGVNEKVSCV
Ligands: RISP, PSIL
Apo structure: boltz_cif/H2AAP_model_0.cif
Family type: gpcr
Group: 5HT2A
Protein: H2AAP
Sequence: MDILCEENTSLSSTTNSLMQLNDDTRLYSNDFNSGEANTSDAFNWTVDSENRTNLSCEGCLSPSCLSLLHLQEKNWSALLTAVVIILTIAGNILVIMAVSLEKKLQNATNYFLMSLAIADMLLGFLVMPVSMLTILYGYRWPLPSKLCAVWIYLDVLFSTASIMHLCAISLDRYVAIQNPIHHSRFNSRTKAFLKIIAVWTISVGISMPIPVFGLQDDSKVFKEGSCLLADDNFVLIGSFVSFFIPLTIMVITYFLTIKSLQKEATLCVSDLGTRAKLASFSFLPQSSLSSEKLFQRSIHREPGSYTGRRTMQSISNEQKACKVLGIVFFLFVVMWCPFFITNIMAVICKESCNEDVIGALLNVFVWIGYLSSAVNPLVYTLFNKTYRSAFSRYIQCQYKENKKPLQLILVNTIPALAYKSSQLQMGQKKNSKQDAKTTDNDCSMVALGKQHSEEASKDNSDGVNEKVSCV
Ligands: none
Family type: gpcr
Group: 5HT2A
# ===================== 5-HT2B =====================
Protein: 5HT2B
Sequence: MALSYRVSELQSTIPEHILQSTFVHVISSNWSGLQTESIPEEMKQIVEEQGNKLHWAALLILMVIIPTIGGNTLVILAVSLEKKLQYATNYFLMSLAVADLLVGLFVMPIALLTIMFEAMWPLPLVLCPAWLFLDVLFSTASIMHLCAISVDRYIAIKKPIQANQYNSRATAFIKITVVWLISIGIAIPVPIKGIETDVDNPNNITCVLTKERFGDFMLFGSLAAFFTPLAIMIVTYFLTIHALQKKAYLVKNKPPQRLTWLTVSTVFQRDETPCSSPEKVAMLDGSRKDKALPNSGDETLMRRTSTIGKKSVQTISNEQRASKVLGIVFFLFLLMWCPFFITNITLVLCDSCNQTTLQMLLEIFVWIGYVSSGVNPLVYTLFNKTFRDAFGRYITCNYRATKSVKTLRKRSSKIYFRNPMAENSKFFKKHGIRNGINPAMYQSPMRLRSSTIQSSSIILLDTLLLTENEGDKTEEQVSYV
Partners: GNAQ, GNB1, GNG2
Ligands: BALO, LSD1
Apo structure: boltz_cif/H2BAP_model_0.cif
Family type: gpcr
Group: 5HT2B
Protein: H2BNG
Sequence: MALSYRVSELQSTIPEHILQSTFVHVISSNWSGLQTESIPEEMKQIVEEQGNKLHWAALLILMVIIPTIGGNTLVILAVSLEKKLQYATNYFLMSLAVADLLVGLFVMPIALLTIMFEAMWPLPLVLCPAWLFLDVLFSTASIMHLCAISVDRYIAIKKPIQANQYNSRATAFIKITVVWLISIGIAIPVPIKGIETDVDNPNNITCVLTKERFGDFMLFGSLAAFFTPLAIMIVTYFLTIHALQKKAYLVKNKPPQRLTWLTVSTVFQRDETPCSSPEKVAMLDGSRKDKALPNSGDETLMRRTSTIGKKSVQTISNEQRASKVLGIVFFLFLLMWCPFFITNITLVLCDSCNQTTLQMLLEIFVWIGYVSSGVNPLVYTLFNKTFRDAFGRYITCNYRATKSVKTLRKRSSKIYFRNPMAENSKFFKKHGIRNGINPAMYQSPMRLRSSTIQSSSIILLDTLLLTENEGDKTEEQVSYV
Ligands: BALO, LSD1
Apo structure: boltz_cif/H2BAP_model_0.cif
Family type: gpcr
Group: 5HT2B
Protein: H2BAP
Sequence: MALSYRVSELQSTIPEHILQSTFVHVISSNWSGLQTESIPEEMKQIVEEQGNKLHWAALLILMVIIPTIGGNTLVILAVSLEKKLQYATNYFLMSLAVADLLVGLFVMPIALLTIMFEAMWPLPLVLCPAWLFLDVLFSTASIMHLCAISVDRYIAIKKPIQANQYNSRATAFIKITVVWLISIGIAIPVPIKGIETDVDNPNNITCVLTKERFGDFMLFGSLAAFFTPLAIMIVTYFLTIHALQKKAYLVKNKPPQRLTWLTVSTVFQRDETPCSSPEKVAMLDGSRKDKALPNSGDETLMRRTSTIGKKSVQTISNEQRASKVLGIVFFLFLLMWCPFFITNITLVLCDSCNQTTLQMLLEIFVWIGYVSSGVNPLVYTLFNKTFRDAFGRYITCNYRATKSVKTLRKRSSKIYFRNPMAENSKFFKKHGIRNGINPAMYQSPMRLRSSTIQSSSIILLDTLLLTENEGDKTEEQVSYV
Ligands: none
Family type: gpcr
Group: 5HT2B
# ===================== 5-HT2C =====================
Protein: 5HT2C
Sequence: MVNLRNAVHSFLVHLIGLLVWQSDISVSPVAAIVTDIFNTSDGGRFKFPDGVQNWPALSIVIIIIMTIGGNILVIMAVSMEKKLHNATNYFLMSLAIADMLVGLLVMPLSLLAILYDYVWPLPRYLCPVWISLDVLFSTASIMHLCAISLDRYVAIRNPIEHSRFNSRTKAIMKIAIVWAISIGVSVPIPVIGLRDEEKVFVNNTTCVLNDPNFVLIGSFVAFFIPLTIMVITYCLTIYVLRRQALMLLHGHTEEPPGLSLDFLKCCKRNTAEEENSANPNQDQNARRRKKKERRPRGTMQAINNERKASKVLGIVFFVFLIMWCPFFITNILSVLCEKSCNQKLMEKLLNVFVWIGYVCSGINPLVYTLFNKIYRRAFSNYLRCNYKVEKKPPVRQIPRVAATALSGRELNVNIYRHTNEPVIEKASDNEPGIEMQVENLELPVNPSSVVSERISSV
Partners: GNAQ, GNB1, GNG2
Ligands: SB24, LORC
Apo structure: boltz_cif/H2CAP_model_0.cif
Family type: gpcr
Group: 5HT2C
Protein: H2CNG
Sequence: MVNLRNAVHSFLVHLIGLLVWQSDISVSPVAAIVTDIFNTSDGGRFKFPDGVQNWPALSIVIIIIMTIGGNILVIMAVSMEKKLHNATNYFLMSLAIADMLVGLLVMPLSLLAILYDYVWPLPRYLCPVWISLDVLFSTASIMHLCAISLDRYVAIRNPIEHSRFNSRTKAIMKIAIVWAISIGVSVPIPVIGLRDEEKVFVNNTTCVLNDPNFVLIGSFVAFFIPLTIMVITYCLTIYVLRRQALMLLHGHTEEPPGLSLDFLKCCKRNTAEEENSANPNQDQNARRRKKKERRPRGTMQAINNERKASKVLGIVFFVFLIMWCPFFITNILSVLCEKSCNQKLMEKLLNVFVWIGYVCSGINPLVYTLFNKIYRRAFSNYLRCNYKVEKKPPVRQIPRVAATALSGRELNVNIYRHTNEPVIEKASDNEPGIEMQVENLELPVNPSSVVSERISSV
Ligands: SB24, LORC
Apo structure: boltz_cif/H2CAP_model_0.cif
Family type: gpcr
Group: 5HT2C
Protein: H2CAP
Sequence: MVNLRNAVHSFLVHLIGLLVWQSDISVSPVAAIVTDIFNTSDGGRFKFPDGVQNWPALSIVIIIIMTIGGNILVIMAVSMEKKLHNATNYFLMSLAIADMLVGLLVMPLSLLAILYDYVWPLPRYLCPVWISLDVLFSTASIMHLCAISLDRYVAIRNPIEHSRFNSRTKAIMKIAIVWAISIGVSVPIPVIGLRDEEKVFVNNTTCVLNDPNFVLIGSFVAFFIPLTIMVITYCLTIYVLRRQALMLLHGHTEEPPGLSLDFLKCCKRNTAEEENSANPNQDQNARRRKKKERRPRGTMQAINNERKASKVLGIVFFVFLIMWCPFFITNILSVLCEKSCNQKLMEKLLNVFVWIGYVCSGINPLVYTLFNKIYRRAFSNYLRCNYKVEKKPPVRQIPRVAATALSGRELNVNIYRHTNEPVIEKASDNEPGIEMQVENLELPVNPSSVVSERISSV
Ligands: none
Family type: gpcr
Group: 5HT2C
# ===================== Gq heterotrimer partners =====================
Partner: GNAQ
Sequence: MTLESIMACCLSEEAKEARRINDEIERQLRRDKRDARRELKLLLLGTGESGKSTFIKQMRIIHGSGYSDEDKRGFTKLVYQNIFTAMQAMIRAMDTLKIPYKYEHNKAHAQLVREVDVEKVSAFENPYVDAIKSLWNDPGIQECYDRRREYQLSDSTKYYLNDLDRVADPAYLPTQQDVLRVRVPTTGIIEYPFDLQSVIFRMVDVGGQRSERRKWIHCFENVTSIMFLVALSEYDQVLVESDNENRMEESKALFRTIITYPWFQNSSVILFLNKKDLLEEKIMYSHLVDYFPEYDGPQRDAQAAREFILKMFVDLNPDSDKIIYSHFTCATDTENIRFVFAAVKDTILQLNLKEYNLV
Partner: GNB1
Sequence: MSELDQLRQEAEQLKNQIRDARKACADATLSQITNNIDPVGRIQMRTRRTLRGHLAKIYAMHWGTDSRLLVSASQDGKLIIWDSYTTNKVHAIPLRSSWVMTCAYAPSGNYVACGGLDNICSIYNLKTREGNVRVSRELAGHTGYLSCCRFLDDNQIVTSSGDTTCALWDIETGQQTTTFTGHTGDVMSLSLAPDTRLFVSGACDASAKLWDVREGMCRQTFTGHESDINAICFFPNGNAFATGSDDATCRLFDLRADQELMTYSHDNIICGITSVSFSKSGRLLLAGYDDFNCNVWDALKADRAGVLAGHDNRVSCLGVTDDGMAVATGSWDSFLKIWN
Partner: GNG2
Sequence: MASNNTASIAQARKLVEQLKMEANIDRIKVSKAAADLMAYCEAHAKEDPLLTPVPASENPFREKKFFCAIL
# ===================== Ligands =====================
Ligand: RISP
# Risperidone -- 5-HT2A antagonist (also broad-spectrum antipsychotic)
SMILES: CC1=C(C(=O)N2CCCCC2=N1)CCN3CCC(CC3)c4c5ccc(cc5on4)F
Role: antagonist
Ligand: PSIL
# Psilocin -- 5-HT2A agonist (active metabolite of psilocybin)
SMILES: CN(C)CCc1c[nH]c2c1c(ccc2)O
Role: agonist
Ligand: BALO
# Balovaptan -- 5-HT2B antagonist
SMILES: CN1Cc2cc(ccc2-n3c(nnc3C4CCC(CC4)Oc5ccccn5)C1)Cl
Role: antagonist
Ligand: LSD1
# LSD -- 5-HT2B agonist (also 5-HT2A)
SMILES: CCN(CC)C(=O)[C@H]1CN([C@@H]2Cc3c[nH]c4c3c(ccc4)C2=C1)C
Role: agonist
Ligand: SB24
# SB-242084 -- 5-HT2C antagonist
SMILES: Cc1cc2c(cc1Cl)N(CC2)C(=O)Nc3ccc(nc3)Oc4cccnc4C
Role: antagonist
Ligand: LORC
# Lorcaserin -- 5-HT2C agonist (former anti-obesity drug)
SMILES: C[C@H]1CNCCc2c1cc(cc2)Cl
Role: agonist
Three receptors, six ligands scoped two apiece, and a Gq heterotrimer partner: that gives 15 targets, twelve ligand-bound (each run with and without Gq) plus three predicted-apo references. A single file drives the whole run and produces an interactive dashboard: ranked confidence and affinity, a confidence-versus-affinity scatter, per-ligand chemistry review, and, when interaction profiling is enabled, a residue fingerprint heatmap and a rotating 3D view of each binding site, generated using PLIP, PyMOL, and 3Dmol.js:
The wizard of vibe coding a wizard
If you would prefer not to write the input spec by hand, BoltzMaker.py new command interviews you in plain English and writes a valid file for you. Given a reference co-crystal or homology model, it can even suggest pocket-contact residues and remap them onto your target’s own numbering by sequence alignment, so the reference structure’s residue numbers do not have to match yours. The next step for the project is to integrate my vibe-coded, chemically and biologically aware ChemSage LLM to achieve more fine-grained control at this step.
Here is an example run of BoltzMaker.py new:
Keeping the chemistry honest
The part I care most about, having watched a bad SMILES quietly ruin a result more than once, is that the chemistry is checked before any compute is spent. Boltz folds whatever it is handed: an undefined stereocentre, an unintended protonation state, or a stray counterion does not raise an error, it just silently changes the predicted pose and affinity.
At parse time, every ligand SMILES is canonicalised with RDKit so the same molecule is represented identically everywhere downstream. Each ligand is then checked for undefined stereocentres (via FindMolChiralCenters), disconnected fragments that usually mean a salt left in the string (GetMolFrags), and ionizable groups whose protonation state a plain SMILES leaves unspecified (SMARTS matches for carboxylic acids, amines, phenols, and sulfonic acids). None of these is a hard failure, because any of them can be a deliberate modelling choice; they are flags worth a second look before you trust downstream numbers. Separately, the dashboard’s ligand grid asks whether any compounds share a chemical core, grouping them first by exact Bemis-Murcko scaffold and then, for near-analogues, by fingerprint similarity backed by a verified maximum common substructure, so a highlighted core is always a proven match rather than a bare similarity score.
For the predicted complexes themselves, cif2plip converts each Boltz output into a strict PDB and runs PLIP for real interaction fingerprints: hydrogen bonds, salt bridges, pi-stacking, halogen bonds, and metal coordination, re-rendered through PyMOL into a labelled binding-site view with distances.
This is where structure prediction, cheminformatics, and interaction analysis meet in one place. The same lineage of diffusion-based models that AlphaFold 3 established now produces poses in minutes, but those poses are only ever as trustworthy as the chemistry fed into them, which is why the validation steps that BoltzMaker performs are essential.
Watching the protein move by following Secondary Structure Elements (SSE): compare-sse
A confidence score tells you what Boltz predicted; it says nothing about how the protein moved when the ligand arrived. That is a real structural question whenever you hold both a reference apo (unbound) structure and a predicted holo one for the same protein, and it is the question compare-sse answers, in the terms a structural biologist reasons in (“TM6 swung out 4.2 Å”, “the DFG motif flipped from in to out”) rather than raw DSSP fragment coordinates.
Motifs are annotated by one of three pluggable sources, auto-selected per family or pinned with Family type. For a GPCR, generic numbering comes from GPCRdb (Ballesteros-Weinstein and GPCRdb schemes), giving TM1 to TM7, H8, and the extracellular and intracellular loops. For a kinase, KLIFS supplies its fixed 85-residue pocket alignment and the canonical motifs (hinge, gatekeeper, HRD catalytic loop, DFG, αC-Glu, catalytic Lys). Anything outside those two families falls back to Pfam domain boundaries via PDBe’s SIFTS residue mapping, the universal last resort. Before any motif is measured, apo is superposed onto holo using only the family’s stable, non-pocket residues (through gemmi’s superpose_positions), so a local, ligand-induced shift cannot quietly skew the global fit and inflate every downstream number.
Pseudo code for the SSE workflow is outlined below:
$ python3 BoltzMaker.py compare-sse boltz_input.md
for each target that supplies an "Apo structure"
family = Family type: # gpcr | kinase | auto (default)
motifs = annotate (family)
gpcr -> GPCRdb generic numbering (TM1-7, H8, ECL1-3, ICL1-3)
kinase -> KLIFS 85-residue pocket (hinge, gatekeeper, HRD, DFG, aC-Glu, Lys)
auto -> whichever fits, else Pfam / SIFTS domain boundaries
superpose (apo -> holo) # stable, non-pocket residues only (gemmi)
for each motif:
Ca RMSD / centroid shift
helix-axis rotation + kink (apo / holo / delta)
SSE boundary shift (HELIX/SHEET records, or optional dssp)
phi/psi outliers (> --phi-psi-threshold)
DFG-in/out, aC-in/out (kinases; coarse Ca-Ca classifier)
write boltz_sse_comparison.csv # one row per motif
boltz_sse_comparison.html # Plotly bar + motif-by-target heatmap
boltz_sse_comparison_sessions/<target>.pml # plain-text PyMOL, no venv dep
Each motif then earns a compact panel of metrics: Cα RMSD and centroid shift after superposition; a helix-axis rotation angle for helical motifs, which is where the classic TM6 outward swing on GPCR activation shows up; a helix kink angle reported as apo, holo, and delta; an SSE boundary shift for whether a helix or strand grew or shrank (the one metric that needs deposited HELIX/SHEET records or an optional mkdssp binary, while everything else works without it); and per-residue backbone dihedral outliers above a phi/psi threshold. Kinases add a coarse DFG-in/out and αC-in/out call read from Cα-Cα distances, built to catch a state change between apo and holo rather than to pass as publication-grade conformational classification. The command writes three artifacts next to the spec: a boltz_sse_comparison.csv with one row per family, target, and motif; a self-contained boltz_sse_comparison.html carrying a Plotly bar chart and a motif-by-target heatmap, vendored exactly like the main dashboard; and one plain-text PyMOL script per target that colors and labels each motif and flags the ones that moved. Those .pml files are just text, so they open in any local PyMOL without adding a PyMOL dependency to BoltzMaker’s own environment.
Important note: Currently, only GPCRs and Kinase family types are implemented.
To utilize compare-sse, the following “Apo structure” and “Family type” fields are required in the boltz_input.md:
Protein: ADRB2
Sequence: MGQPGNGSAFLLAPNGSHAPDHDVTQERDEVWVVGMGIVMSLIVLAIVFGNVLVITAIAKFERLQTV...
Apo structure: ./refs/adrb2_apo.pdb
Family type: gpcr
When the laptop says no, AI-assisted Vibe Coding helps you fix it!
The 5-HT2 panel’s six Gq-heterotrimer targets (roughly 1,250 to 1,280 tokens each) would not run on Apple Silicon at all, and for two entirely separate reasons.
The first was a hard crash: Boltz’s triangular attention builds the whole complex’s row-wise attention-score matrix in a single unchunked matmul, which scales as tokens × heads × tokens × tokens, and past about 1,250 residues, that one tensor exceeds the MPS backend’s size ceiling and takes the process down with a SIGSEGV inside PyTorch’s own tiled fallback. Because each row’s attention is computed independently, chunking the matmul along the row axis is mathematically exact rather than an approximation, so BoltzMaker patches it straight into the installed boltz package at setup time, idempotently, and only after checking the function against the source it expects, so a future Boltz release cannot be silently mis-patched.
The second was a genuine out-of-memory failure: with the crash fixed, running six large targets back to back in one process hit real memory pressure partway through, and two targets fell over on Boltz’s own clean-fail path, then dragged two already-successful targets down with them when the affinity phase went looking for an intermediate file that was never written. The fix is to run and all now retry any incomplete target automatically, isolating each one to its own single-target boltz predict invocation from the first retry onward, because a crashed subprocess only releases its MPS memory once it fully exits. Both are permanent, general behaviors rather than patches for this campaign: the chunking applies to any large complex on Apple Silicon, and the retry applies to any OOM, on any run.
What I learned…
- The dashboard was fine; the hosting wasn’t. It rendered perfectly locally and broke on the site. I spent far too long re-debugging the HTML before checking whether WordPress was rewriting it on the way out. It was. Check the delivery layer first, then the file.
- Vendored beats linked. The dashboard originally pulled Plotly from a CDN and rendered blank the moment it was opened somewhere without network access. Inlining the library made the file bigger and the problem disappeared. Same for
compare-sse‘s report. pdb_tidylies quietly. It renumbers TER records and leaves gaps in the CONECT serials, which OpenBabel then silently mis-reads as different bonds, giving PLIP wrong interactions with no error anywhere. The docs said nothing. Only diffing the actual output found it.- Compute once, format twice. The summary CSV and the dashboard originally each calculated the ranking themselves, and inevitably disagreed. One function now does the sums and the two outputs just render them.
- Grids will split a pair. The ligand grid put structures and their labels in separate cells the moment the set size changed, because a grid sees siblings, not units. Group them explicitly or they drift apart.
What comes next…
- Benchmark it against reality. Pull known actives and co-crystals for a target family from ChEMBL, BindingDB, and the PDB, then report predicted-versus-measured correlation and pose RMSD. A confidence score says the model is sure; this would say whether it’s right.
- A triage view, not another table. Plot confidence against predicted affinity and quadrant it, so choosing what to test next takes a glance rather than a spreadsheet.
- Flag the CPU fallbacks.
torch.linalg.svddrops from MPS to CPU with no warning and no error, just a much slower run. Preflight should catch it and say what it will cost. - Checkpoint per sample. Long runs fail near the end, and everything is lost. A crash should cost minutes, not hours.
- A
doctorcommand. Two environments and a dozen libraries, with failures that only surface three stages in. One command that imports everything and reports what’s broken.