Inspiration
Most of the risk in a recovery app is not in the feature you ship. It's in the change you make three weeks later, when you tidy a formula and quietly break the one subtraction that was keeping a patient safe.
We wanted a project where that mattered. Insulin dosing is about as unforgiving as code gets: get a bolus wrong and you don't file a bug, you harm someone. The FDA recall history reads like a list of one-line diffs gone wrong. An Omnipod app that read "0,5" as a different number. A pump that stopped subtracting the insulin already working in the patient's body, so each dose stacked on the last.
So Aftercare is two things that belong together. It's a post-op app a patient could actually open at their kitchen table, and it's a GitLab Duo flow that stands over the repository afterwards and refuses to let the kind of change that caused those recalls reach main without a human looking at it first. That second half is the "Life After Code" part. The feature is written. What we automated is everything that has to keep being true about it from then on.
What it does
The app
Aftercare runs as one FastAPI process with two surfaces, each mounted under its own prefix so their /api/* routes never collide. The landing page is a toggle between them.
Wound check (/wound/) is for the days after you go home. You answer a short check-in — what the operation was, which day you're on, pain out of ten, temperature, what any discharge looks like, whether there's a smell, whether redness is spreading, whether the edges have opened, whether you just feel unwell — and you can attach a photo of the incision. A vision model reads the photo into plain sentences and nothing more; it describes, it never decides. Written warning signs live in code as a floor the model cannot go under: a fever with spreading redness is an emergency no matter what the prose says. Then Claude weighs the whole picture against the patient's own baseline, not an ideal wound, and returns one of four calls — no change, watch, call today, or go to A&E now — with a one-line headline, a reason per sign, two to four next steps, and a message the patient can paste straight to their surgical team. Any alarm gets a second, more cautious pass before it's shown. The decision can only ever go up, never quietly down.
Insulin dosing (/insulin/) is a test harness, not a calculator. It takes a real open-source bolus calculator, runs its actual code in a restricted Node subprocess, and checks what it displays against a reference formula and a set of safety rules drawn verbatim from open-access papers. Claude picks probe inputs and calls a run_test tool; seed probes taken straight from the FDA recall history (a leading-dot decimal, a comma decimal, a blank required field, a zero divisor) run every time regardless. Each result is scored pass or fail in plain Python, grouped by the Huckvale error class it falls under. The model is allowed to explain a failure and to judge whether it's a true defect, a spec ambiguity, or an unfair test. It is never allowed to call a dose safe.
The automation: the Aftercare Safety Auditor
The oracle in insulin/engine/oracle.py is the one piece of the system a reviewer is supposed to trust more than any model. The reference bolus is one line:
bolus = CHO/ICR + (G - G_target)/ISF - IOB
That's Equation 4 from Zhu et al., Sensors 2020 (PMC7570884). The - IOB on the end is the insulin already active in the patient. Drop it and every dose silently stacks on the one still working. That is the exact failure behind the Omnipod DASH recall, Z-2164/2165-2020.
The Aftercare Safety Auditor is a GitLab Duo flow that watches for a change touching that logic. Mention it on a merge request and three agents take over in sequence. An auditor reads the diff and every changed file under insulin/, wound/ and tests/, looking for one thing: has a guard been weakened? IOB dropped from a dose path, the 30-unit ceiling removed, a blank or negative or comma input let through, a verdict path that can now return "pass" for a dose it should have stopped, a safety test deleted or loosened. A generator then writes a pytest for each finding that fails on the defect and passes on a correct fix — a test that merely passes is worthless here, so it checks its own assertion against the broken code. A scribe posts one merge request comment: a verdict line, a table of findings with the Huckvale category and the matching FDA recall, and the exposing tests as suggestions. Then it stops and waits. It never commits, never merges, never approves. A clinician or a maintainer makes the call.
There's also a read-only companion agent, Aftercare Auditor, for the lighter case where you want a review comment without the full test-writing chain.
How it works
The reference formula and the six Huckvale 2015 error classes (BMC Med, PMC4433091) are encoded as deterministic rules in oracle.py: missing value, impossible input, divide-by-zero, locale decimal (the Omnipod 5 iOS recall, Z-2016-2025), no upper bound, and dropped IOB. Pass/fail for any single case is decided there, in Python, against a cited formula. Claude sits around that core — choosing probes, reading photos, explaining failures, writing exposing tests — but the verdict is never its to give.
The Duo flow and the standalone agent are both published to the GitLab AI Catalog by a catalog-sync CI component that runs on tag pipelines. Everything else in CI is the safety net around the oracle: a pytest gate that must flag every error class, a coverage floor of 90% measured on the oracle specifically, and a Playwright job that drives the running app in a real browser before anything builds. The container is served on AWS App Runner, though Path A doesn't require a deploy at all.
Both the flow's agents and the app reason with Claude. Inside GitLab, the flow uses the platform's managed Anthropic Claude Sonnet, which shows up at runtime as claude_sonnet_5_5_vertex — served on Google Vertex AI. The app ships cached results for every demo case, so you can open it and see real model output with no key set; a live run is the only thing that needs one.
How we built it
We wrote the oracle before anything else, because a test-writing agent is only as honest as the thing deciding pass from fail. Getting that core right meant reading the source papers rather than paraphrasing them, pulling the formula and the error taxonomy in verbatim with citations, and keeping every verdict in plain arithmetic the model can't reach.
The insulin harness came next: loading three real calculators (mxklb, Pancreas-Digital, and the Nightscout bolus wizard) in a sandboxed Node process so the agent probes the actual shipped code, not a reimplementation of it. Then the wound surface, with the rule floor and the second-opinion pass. The Duo flow was last, and it was the part we rewrote most, because an auditor that generates a test which passes on broken code is worse than no auditor.
Challenges we ran into
The hardest problem was keeping the model out of the verdict while still using it everywhere it's genuinely good. It's tempting to let Claude say "this looks safe." The whole design says it can't. It explains, it proposes, it adjudicates cause — but a dose is called safe only by a line of Python tied to a paper.
Making the generated tests earn their place was the other one. A passing test is easy and useless. We prompt the generator to assert the safe behaviour and to verify, by reading the defective code, that its assertion would actually fail on it. The demo is the sharpest version of this: the change on feature/iob-stacking edits the oracle's own reference_bolus to "fold IOB into the correction step," which drops the - iob term. Because that rewrites the reference the oracle checks against, the oracle itself would now wave through a stacked dose. The thing you were trusting is the thing that broke. The auditor's job is to catch exactly that and name it: Huckvale iob_subtraction, Omnipod DASH recall Z-2164/2165-2020.
We also kept the flow inside a network-restricted sandbox, which meant the agents had to reason from reading the code when they couldn't install or run everything, and the prompts had to tell them to say so rather than fake a result.
Accomplishments we're proud of
A safety reviewer that writes the failing test instead of leaving a comment, and then gets out of the way for a human. An app where the model reads a wound photo and talks a patient through it without ever being the thing that decides. And a demo that breaks the safety net itself and watches the auditor notice.
What we learned
The useful division of labour with a model on safety-critical code is narrower than it looks. The model is excellent at the parts around the decision — reading source, choosing adversarial inputs, explaining why a number is wrong in plain language, drafting a test — and it should own none of the decision. Drawing that line explicitly, in code, is what made the rest trustworthy. We also learned how much a single real citation changes a review: "this drops IOB, see Omnipod DASH Z-2164/2165-2020" lands differently than "this looks risky."
What's next
Mutation testing, so the generated tests have to prove they catch the bug they claim to. A wider library of reference formulas so the harness can check more than bolus maths. And a confidence signal on the auditor's findings, so a low-confidence flag asks for a human earlier rather than later.
Built With
- amazon-web-services
- anthropic-claude-(sonnet
- app
- docker
- fastapi
- gitlab-ai-catalog
- gitlab-ci/cd
- gitlab-duo-agent-platform
- google-vertex-ai
- managed)
- node.js
- playwright
- pytest
- python
- react
- vite
Log in or sign up for Devpost to join the conversation.