This article was created for the Gemini Live Agent Challenge hackathon.
The Problem
Every year, 2 million people are hospitalized from adverse drug reactions. Around 100,000 die. Not from their disease — from the treatment.
Most of these deaths are not accidents. They are written in the patient's genome, waiting to be read.
A patient who is a CYP2D6 Ultra-Rapid Metabolizer converts codeine into morphine at 4× the normal rate. Prescribe standard codeine — you've ordered a fatal overdose. The doctor didn't know. The pharmacist didn't know. Nobody checked the genome.
This is not a rare edge case. CYP2D6 Poor Metabolizers represent ~8% of the European population. HLA-B*5701 carriers — for whom abacavir causes fatal hypersensitivity — represent ~6% of HIV patients in Western cohorts. These variants are not exotic. They are common. They are documented. And they are routinely ignored at the point of prescription.
Pharmacogenomic tools exist. They live in separate systems, require manual lookup, and operate on a different timescale than a clinical consultation. By the time the alert surfaces, the prescription is already signed.
The problem is not data. It is latency.
Architecture: Two Layers That Never Blur
PharmaShield is built on one strict rule: the LLM makes zero clinical decisions.
This is not a stylistic choice. A language model that hallucinates a phenotype in a clinical context is not a UX problem — it is a liability. Clinical decisions must be deterministic, auditable, and reproducible. That is C++ territory.
The system splits into two layers with no overlap.
Gemini 2.5 Flash Live — the intelligence layer.
Listens to the ambient audio stream of the consultation in real-time. Extracts entities: patient ID, drug name, current medications, age, renal function. Fires a tool call the moment both a patient identifier and a drug name are identified — without waiting for the doctor to finish speaking. It does not evaluate risk. It extracts and delegates.
C++23 Sentinel Core — the deterministic layer.
Receives the tool call. Binary searches a memory-mapped file in ~40 nanoseconds. Runs a 5-step pharmacogenomic scoring pipeline — entirely hardcoded from CPIC clinical guidelines. Returns a structured RiskResult. No inference. No probability. No temperature.
Doctor speaks → Gemini Live (audio stream)
↓
Extracts: patient_id + drug + current_meds
↓
Tool call: check_pharmacogenomics()
↓
C++23 Sentinel Core — mmap + binary search — 40ns
↓
5-step risk pipeline (PGx + physiology + DDI)
↓
If critical → Gemini voice barge-in interrupts doctor
End-to-end latency from the doctor saying the drug name to the audio alert: ~500ms. The C++ engine contributes 40 nanoseconds. The bottleneck is Gemini's TTFT — the intelligence layer, not the safety layer. That asymmetry is intentional.
Why C++23 for Medicine
The question is not why C++23. The question is why anything else would be acceptable.
This is a safety-critical path. Every millisecond of latency between a prescription and a contraindication alert is a window where the doctor moves forward. We cannot afford allocations. We cannot afford virtual dispatch. We cannot afford a garbage collector deciding to run mid-consultation.
So we did what the hardware demands.
Memory mapping over database.
patients.bin is a flat binary file. One record per patient, 16 bytes, alignas(16). At startup, a single mmap(PROT_READ, MAP_PRIVATE) maps the entire dataset into the process address space. No I/O on the hot path. No query planner. No connection pool. The dataset is a typed C++ span:
std::span<const PatientRisk> m_data = m_region.get_span();
150,000 patients. ~1.8 MB. Fits entirely in L3 cache. Once warm, lookups never touch DRAM.
Binary search over index.
The file is sorted by subject_id at build time — enforced by builder.py, asserted at startup. The lookup is a single call:
auto range = std::ranges::equal_range(m_data, patient_id, {}, &PatientRisk::subject_id);
O(log N). ~40 nanoseconds with warm cache. No hash table. No tree traversal. No pointer chasing. The CPU's prefetcher handles the rest.
constexpr tables over runtime logic.
Every clinical rule — CYP phenotype matrices, HLA flags, drug interaction pairs, inhibitor strengths — lives in constexpr arrays evaluated at compile time. The phenoconversion transition matrices are branchless lookups:
constexpr PhenoType DOWNGRADE_MATRIX[5][3] = {
{PhenoType::UNKNOWN, PhenoType::UNKNOWN, PhenoType::UNKNOWN},
{PhenoType::PM, PhenoType::PM, PhenoType::PM},
{PhenoType::IM, PhenoType::PM, PhenoType::PM},
{PhenoType::EM, PhenoType::IM, PhenoType::PM},
{PhenoType::UM, PhenoType::EM, PhenoType::IM},
};
No branches. No heap. The scoring pipeline runs entirely in registers and L1 cache.
Hash collision detection at compile time.
Drug names are hashed with FNV-1a at compile time. Collisions are caught by a consteval function — the binary will not compile if two drugs hash to the same value. The safety check costs zero nanoseconds at runtime.
consteval bool check_drug_hash_collisions() { ... }
static_assert(check_drug_hash_collisions(), "FATAL: Hash collision in DRUG_RULES!");
Software prefetching on keystrokes.
As the doctor types a patient ID, every keystroke fires a prefetch. The engine estimates the ID range from the partial prefix and issues __builtin_prefetch on the corresponding cache lines. By the time Gemini fires the tool call, the patient's record is already in L1.
for (ptrdiff_t i = 0; i < prefetch_count; i += 4) {
__builtin_prefetch(reinterpret_cast<const char*>(&it[i]), 0, 3);
}
The result: the 40ns figure is not a best-case benchmark. It is the steady-state cost of a warm-cache lookup on a properly structured dataset. The CPU does not execute algorithms. It executes memory access patterns. We designed for the machine.
Phenoconversion: The Risk Nobody Models in Real-Time
Pharmacogenomics has a known blind spot. Most systems stop at the genome.
The genome tells you what enzymes a patient can produce. It does not tell you what enzymes they are currently producing. These are not the same thing.
A patient who is genetically CYP2D6 Normal Metabolizer — EM, textbook — has normal codeine metabolism on paper. Prescribe codeine. Safe, correct, defensible.
Except they are currently taking paroxetine.
Paroxetine is a strong CYP2D6 inhibitor. It does not care what the genome says. It physically blocks the enzyme. The patient's functional phenotype is now PM — Poor Metabolizer. Codeine accumulates. The active morphine metabolite builds. The patient stops breathing.
The genome said EM. The clinical reality was PM. No static PGx lookup catches this. You need to know what the patient is currently taking — and you need to model the interaction at runtime.
This is phenoconversion. And it is the primary clinical differentiator of PharmaShield.
The mechanism.
Every Gemini tool call accumulates the patient's current medication list across the consultation. The C++ engine receives these as a std::vector<std::string> in ClinicalContext. Each drug name is hashed with FNV-1a and matched against two compile-time tables: inhibitors and inducers, classified by CYP and strength.
struct PhenoModifier {
uint32_t drug_hash;
CypOffset cyp_index;
uint8_t strength; // 0=weak 1=moderate 2=strong
};
constexpr PhenoModifier INHIBITORS[] = {
{fnv1a("paroxetine"), CypOffset::CYP2D6, 2}, // strong
{fnv1a("fluoxetine"), CypOffset::CYP2D6, 2}, // strong
{fnv1a("bupropion"), CypOffset::CYP2D6, 2}, // strong
{fnv1a("clarithromycin"), CypOffset::CYP3A5, 2}, // strong
{fnv1a("amiodarone"), CypOffset::CYP2C9, 2}, // strong
// ...
};
The transition is branchless. Inhibition strength maps directly into a downgrade matrix:
Genetic phenotype: CYP2D6 EM ← from patients.bin
Current meds: [paroxetine] ← from ClinicalContext
↓
INHIBITORS match: strength = 2 (strong)
↓
DOWNGRADE_MATRIX[EM][2] = PM
↓
Functional phenotype: CYP2D6 PM ← this is what the scorer uses
Strong inhibitor — EM becomes PM, IM becomes PM. Moderate inhibitor — EM becomes IM. Two moderate inhibitors stack to strong. The matrix handles every case in a single array lookup. No branches. No conditionals.
The warning surface.
When phenoconversion fires, the RiskResult carries a PHENOCONV:Y flag in the structured header. The frontend renders a dedicated badge. Gemini's vocal alert explicitly names the mechanism:
"Warning Doctor — patient 99999004 is genetically a normal metabolizer, but paroxetine is converting them to a functional poor metabolizer. Codeine will accumulate. Consider a non-opioid alternative."
The doctor hears not just a contraindication — they hear the mechanism. That is the difference between an alert that gets dismissed and one that changes the prescription.
Why nobody does this in real-time.
Static PGx tools operate on the genome alone. EHR-integrated alerting systems check drug-drug interactions separately, in a different module, on a different trigger. Nobody combines genomic phenotype, phenoconversion via current medications, physiological context, and drug-drug interactions into a single scoring pipeline that runs in under 500 microseconds.
We do. In 40 nanoseconds of C++ and a Gemini barge-in.
Results
Latency.
| Operation | Latency |
|---|---|
| Binary search (warm cache) | ~40 ns |
| Phenoconversion + scoring | ~200 ns |
| Full tool call → RiskResult | < 500 µs |
| Doctor says drug → audio alert | ~500 ms |
The 500ms end-to-end is Gemini's TTFT. The C++ engine is not the bottleneck and was never going to be. We over-engineered the deterministic layer on purpose — because in a safety-critical path, the safety layer should never be the constraint.
Dataset.
patients.bin is generated from published allele frequency distributions — gnomAD v4 for EUR, AFR, EAS, AMR populations — using Hardy-Weinberg simulation. No real patient data. No PII. Fully reproducible with a fixed seed.
python tools/synthetic_data_generator.py --patients 10000 --seed 42 --out patients.bin
10,000 patients. 1.8 MB. Realistic phenotype distributions across 9 CYP enzymes, HLA alleles, G6PD, SLCO1B1, VKORC1, OPRM1.
Demo scenarios — all guaranteed to trigger.
| Patient | Variant | Drug | Alert |
|---|---|---|---|
| 99999001 | CYP2D6 UM | codeine | Fatal morphine accumulation |
| 99999002 | CYP2C19 PM | clopidogrel | Stent thrombosis |
| 99999003 | CYP2C9 PM + VKORC1 | warfarin + amiodarone | Triple hit hemorrhage |
| 99999004 | CYP2D6 EM + paroxetine | codeine | Phenoconversion |
| 99999005 | HLA-B*5701 | abacavir | Absolute contraindication |
| 99999006 | TPMT PM | azathioprine | Myelosuppression |
| 99999007 | UGT1A1*28 | irinotecan | Severe neutropenia |
| 99999008 | Severe CKD | metformin | Lactic acidosis |
Patient 99999004 is the one worth watching. Genetically normal. Functionally lethal. Static PGx tools miss it entirely.
What the scoring pipeline covers.
Five steps, executed in sequence, every lookup:
- HLA / G6PD — absolute contraindications, exits immediately on match
- Phenoconversion — genetic → functional phenotype override
- PGx base score — CPIC-hardcoded rules per drug × phenotype
- Physiological deltas — renal severity, hepatic impairment, age >65 (Beers 2023), pregnancy, QTc >450ms
- Drug-drug interactions — 50 critical pairs from CPIC/FDA/CredibleMeds, bidirectional
Score is capped at 10. Every point is traceable to a specific guideline reference. No black box.
What it is not.
PharmaShield is a research prototype and a hackathon submission. It is not a certified medical device. It must not be used for real clinical decisions without formal validation and regulatory clearance. The drug interaction database covers ~50 critical pairs — a comprehensive formulary would require Micromedex or Lexicomp. The synthetic dataset covers the major pharmacogenes; some edge cases remain on the production roadmap.
We know exactly where the gaps are. That is what production roadmaps are for.
Built Together
PharmaShield is a joint project. I built the C++23 engine and the backend. Lilia Ouadah — PhD candidate in computational genomics at the University Medical Center Groningen (UMCG) — brought the bioinformatics: the star allele calling pipeline, the CPIC guideline implementations, the phenoconversion logic, the allele frequency tables, and all the clinical verification work that makes the science defensible. We built the frontend together.
The project does not exist without her.
Stack
| Component | Technology |
|---|---|
| AI model | Gemini 2.5 Flash Live |
| AI SDK | Google GenAI Python SDK |
| Backend | FastAPI + uvloop + WebSockets |
| PGx engine | C++23, pybind11, mmap, std::ranges::equal_range
|
| Cloud compute | Google Cloud Run (gen2) |
| Data storage | Google Cloud Storage |
| Frontend | Vanilla HTML5/JS, Web Audio API |
GitHub: PharmaShield
#GeminiLiveAgentChallenge
Top comments (0)