Virtual Trial
Repo
Judge modeEach section shows the criterion it speaks to and a link to the evidence.Full evidence map

How it works

From code change to safe release

Seven steps. A person starts it and people own every limit. Agents plan, explain, and respond to incidents; the pipeline tests, decides, and deploys; a person approves every merge.

Switch to see the engineering version of each step.
  1. A change is proposed

    A developer opens a merge request that changes the dosing code. That human action starts everything.

    Merge request on the GitLab project (oref0 imported with full history). The pipeline runs tests, security scans, the agent boundary check, the trial, and the gate.

    .gitlab-ci.yml

  2. An agent plans the trial

    When the merge request is opened, the planning agent starts on its own. It reads the change, names what could go wrong, and can make the trial bigger (all 30 patients). It cannot make it smaller or skip it.

    GitLab Duo flow plan_trial (AI Catalog; merge request created trigger, or a mention). Tools: read the MR and diff, add the trial::full label, post a note. No tool can change code.

    .gitlab/duo/flows/plan_trial.yaml

  3. The trial runs

    Old and new code each dose the same virtual patients through ten realistic days: a normal day, a forgotten bolus, a large dinner, a basal rate set too high, and five faults modeled on real bugs. Then a hunter searches dozens of varied days for the one that hurts the new code most.

    trial/run.py: base (git worktree of the MR's base) and head run in parallel; each decision comes from oref0 via a persistent Node worker; 1-minute physiology (simglucose UVA/Padova), 5-minute loop, fixed seeds. trial/hunt.py: seeded search over meal times and sizes, mis-counted meals, sensor gaps, false lows, and basal errors, split across the same CI shards.

    trial/scenarios.py

  4. The gate decides

    If low or high blood sugar risk rises past limits that people set, the change is blocked. It is also blocked if risk rises a little but consistently across almost every patient and day, or if the hunter finds a day where the new code causes a severe low the old code did not, and that holds up when the day is replayed with small changes. If the change also clearly lowers low risk overall, the gate does not decide alone: it asks a person to weigh the two.

    Pooled limits plus a paired one-sided exact sign test on LBGI and HBGI (block if mean rise of 15% or more and p of 0.01 or less); a new severe low on the hunted day, confirmed on 12 variations of it (more severe lows, and lower glucose in a consistent majority by sign test). If paired LBGI also falls 15% or more consistently, the result is REVIEW (CI exit 3, a warning) and a person decides. Limits in gate/thresholds.yaml, owned through CODEOWNERS.

    gate/thresholds.yaml

  5. An agent explains

    When a merge request pipeline fails, the explain agents start on their own. They read the gate's numbers and the code change and posts a plain-language explanation, quoting the numbers exactly.

    GitLab Duo flow explain_verdict, two agents. An investigator reads the MR, diff, and job logs with read-only tools; a writer turns its findings into one note and never sees raw logs. Forbidden from recomputing metrics or suggesting a dosing patch.

    .gitlab/duo/flows/explain_verdict.yaml

  6. Ship and watch

    A merged change deploys the dosing engine as a canary: no traffic until 151 test decisions stay inside oref0's safety limits. Then ten virtual patients live on it, around the clock.

    Keyless deploy (Workload Identity Federation) to Cloud Run with --no-traffic and a tag URL; engine/smoke.js gates promotion; automatic rollback on failure. Virtual clinic: Cloud Run job every 10 minutes.

    infra/ci/deploy.gitlab-ci.yml

  7. Agents respond to an incident

    If the virtual patients do worse on the new engine, an incident opens on its own. Agents find the dosing change behind it and open a merge request that undoes exactly that change. The trial checks the undo, and a person approves it by merging.

    Hourly scheduled pipeline: ci/clinic_watch.py opens the incident and mentions the respond_to_incident flow. Investigator (read only) names the suspect commit; responder runs git revert on a new branch and opens an MR with trial::full. ci/agent_boundary.py accepts an agent commit under lib/ only if it is the exact inverse of a commit already on the target branch (same patch-id).

    .gitlab/duo/flows/respond_to_incident.yaml

What one trial measures

For every virtual patient and every scenario, under both versions of the code, on true blood glucose:

  • Time in range, 70 to 180 mg/dL
  • Time below 70, and below 54 (serious lows)
  • Severe low events, below 54 for 15 minutes or more
  • Time above 250, a warning sign for ketoacidosis
  • Risk indexes for lows and highs, LBGI and HBGI

The faults we inject

Real dosing bugs often hide in messy data. Each fault is modeled on a reported failure:

  • A 45-minute sensor dropout after dinner (Tandem Class I recall, Trio #989)
  • A false low overnight from pressing on the sensor
  • A sensor that returns after a gap with a 60 mg/dL jump (Trio #989)
  • Pump history that starts with a resume (Trio #898)
  • A dinner bolus missing from history while it is delivered (AndroidAPS #2580, Loop #632)
  • A forgotten lunch bolus, and a dinner almost twice the usual size

Where it runs

GitLab

Merge requests, two Duo agent flows and a chat agent, CI/CD pipelines, security scans, the agent boundary check, and the gate.

Google Cloud

The dosing engine, website, and virtual clinic on Cloud Run, deployed with no stored keys, monitored with uptime checks and alerts. All defined in Terraform.

Open source

OpenAPS oref0 (MIT) is the dosing code under test. simglucose (MIT) provides the UVA/Padova virtual patients.