Inspiration
AI agents are starting to explain medical bills to patients. One wrong coinsurance line and a patient is told they owe $651 instead of $551, and nothing in a normal pipeline notices: unit tests pass, the code is "cleaner", and the deploy goes out. Prompt changes, model swaps and refactors ship faster than anyone re-runs evals by hand.
I wanted the life after code of an AI product to work like this: the evals decide whether a release ships, an agent does the tedious part (reading logs, writing up evidence, proposing a fix), and a person still makes the final call.
What it does
EvalGate wraps BillPilot, a small medical-bill explainer with 27 evals covering money math, safety guardrails, payment-sandbox safety and end-to-end cases, in a release workflow that runs entirely on GitLab.
The loop that ran on GitLab.com (23 Oct 2026, all public in the project):
- Regression. MR !1, "refactor(billing): simplify coinsurance calculation", changes one line in
billpilot/billing.pyso coinsurance is applied to the allowed amount instead of the remaining balance. - Gate blocks. The pipeline runs flow-lint → evals → SAST + Secret Detection →
evalgate. The gate returns BLOCK: 24 passed, 3 failed, shows the golden value against the value produced for each failing eval (for example, ER golden $551, got $651), and confirms that the file it blames changed in this commit. The pipeline fails, so the manualpagesdeploy never becomes available. - Duo takes over (Supervised). The Pipeline events → Failed trigger starts our custom Duo Agent Platform flow.
triage_agentuses a small model. It reads the failing job's log withget_pipeline_failing_jobsandget_job_logs, then posts the release decision with the evidence table as a note on the MR (create_merge_request_note). - Router → fixer. A conditional router sends only BLOCK decisions on to
fixer_agent, which uses a larger model. It creates a branch (create_branch), commits the smallest fix to the product file (create_commit), and opens Draft MR !2, "EvalGate fix - apply coinsurance to remaining balance" (create_merge_request). The change isowe += remaining * plan.coinsurance. - Can't cheat. On
evalgate-fix-*branches, theeval-integrityjob fails the pipeline if the agent touchedevals/,evalgate/,.gitlab-ci.yml,.gitlab/duo/or the guardrails. A "fix" that rewrites the goldens would pass the evals and still fail the pipeline. - Human signs. On the fix branch (pipeline #2981363471)
eval-integrityis OK, the evals pass 27/27 and the gate says PASS. The agent stops there. A person reviews the Draft MR, marks it ready and merges; only then, onmain, can a person click the manualpagesjob, which re-checks the gate and publishes BillPilot plus the EvalGate report for that exact commit to GitLab Pages in theproductionenvironment. As of this submission MR !1 and Draft MR !2 are open, waiting for that human review; nothing has been deployed to production yet.
The flow also listens for Mentions, so a developer can ask it to re-triage any MR on demand.
How we used GitLab
GitLab Duo Agent Platform (the core).
- A custom flow in
.gitlab/duo/flows/evalgate.yml, written to the Flow Registry v1 schema (version: v1,environment: ambient, components, routers, flow inputs). - Two AgentComponents with inline prompts and explicit tool lists:
- triage:
get_pipeline_failing_jobs,get_job_logs,create_merge_request_note,create_issue; - fixer:
create_branch,create_commit,create_merge_request.
- triage:
- A conditional router on the triage decision.
- Model tags pick a small model for triage and a larger one for fixing.
- Enabled in the project with two triggers (Pipeline events: Failed, and Mention). It runs under a service account with Developer role only, so it can't merge, deploy or change protected settings.
evalgate/validate_flow.pychecks the flow YAML against the documented custom-flow rules in CI (flow-lint), before GitLab ever sees it. This caught two real schema errors:model_tagsmust be a list, andmax_cyclesis not allowed.
GitLab CI/CD.
- Five stages: lint → test → security → gate → deploy.
interruptible: truecancels superseded pipelines.- Artifacts carry
evals.log,evalgate.jsonandevalgate-report.mdfrom job to job.
GitLab Secure. The SAST and Secret Detection templates run on every pipeline, before the gate.
GitLab Pages + Environments. A manual pages job publishes to the production environment, so the environment history is the release log.
Merge requests, Draft MRs and notes. The agent's work is reviewable by design. It only ever proposes a Draft.
DevSecOps stages touched (Most Stages Covered)
| Stage | What touches it |
|---|---|
| Plan | Duo can open an "EvalGate BLOCK" issue with the evidence (create_issue). |
| Create | Duo creates the fix branch, the commit and the Draft MR. |
| Verify | The 27 evals, the 9 triage tests and the flow-lint job. |
| Secure | SAST and Secret Detection. |
| Release | The evalgate BLOCK/WARN/PASS decision plus the manual pages release to production. |
| Monitor | The per-run report with gCO2e, and the Duo session log under Automate → Flows. |
| Govern | The eval-integrity policy: agent-authored branches may not modify tests, the gate, CI or guardrails. The service account has least privilege. |
Not touched: Package (no registry) and Configure.
Sustainability (Most Environmentally Impactful)
- Right-sized models. The small model handles triage on every failed pipeline. The large model runs only when the router sees a BLOCK.
- No wasted compute.
interruptible: truecancels superseded pipelines. The gate is deterministic Python (no model call), and the fixer never runs on a green pipeline. - Measured per run. Every report prints an SCI-style estimate, energy × grid intensity (65 W runner × job time × 400 gCO2e/kWh). The gate's estimate is under 0.01 gCO2e per run (about 0.007 gCO2e in a local run on 26 Oct 2026).
- SCI per release. We use SCI = (E × I + M) / R with R = one release, and today we report only the E × I part from runner time. Honest limit: the figure omits embodied emissions (M) and model-inference energy for the Duo agents, which we can't measure from inside GitLab.
Challenges
- Keeping the agent from grading its own homework. The release decision is deterministic code, never the model. The agent can only propose a Draft, and
eval-integrityrejects any fix that edits the tests. - Schema drift. The custom-flow format changed recently. A local validator that runs in CI was faster than trial and error in the editor.
- Evidence a reviewer can read in 10 seconds. The gate writes golden-vs-got per eval and whether the blamed file changed in the commit, so the Duo note is evidence, not opinion.
Accomplishments
- A real regression loop on GitLab.com: regression commit → red pipeline with evidence → Duo note on the MR → Duo Draft fix MR about two minutes after the pipeline failed → green fix pipeline, waiting for a human to merge and deploy.
- Gate: 24/27 → BLOCK on the regression and 27/27 → PASS on the fix. 9/9 triage unit tests.
- Zero model calls in the gate itself, so the decision is reproducible on any laptop.
Limitations
- BillPilot uses synthetic plans and bills. It is not medical, financial or billing advice.
- Duo occasionally formats its evidence table imperfectly. One row in our recorded note repeated the same value on both sides; the gate's own report artifact is the source of truth.
- Single repo and a single plan-rule set. Per-payer rules are not modeled.
What's next
- Per-payer plan rules and more eval families (prior auth, out-of-network).
- SCI measured from runner telemetry instead of a fixed wattage.
- The same gate for prompt and model changes, not just code.
- A Google Cloud deployment for the bonus.
Testing instructions
git clone https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43492776/showcase.git evalgate && cd evalgate
python -m evals.run_evals | tee evals.log # 27/27
python -m evalgate.test_triage # 9/9
python -m evalgate.triage evals.log 4 # PASS + evalgate-report.md
bash demo/regression_demo.sh # PASS → BLOCK → fix PASS → eval-tampering rejected
On GitLab: CI/CD → Pipelines shows the loop, MR !1 shows the Duo BLOCK note, MR !2 is the Duo Draft fix, and Automate → Flows shows the EvalGate flow and its sessions. All data is synthetic.
Links
- Repo (public, MIT): https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43492776/showcase
- Regression MR !1: https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43492776/showcase/-/merge_requests/1 (Duo note "EvalGate Release Decision: BLOCK" in Activity)
- Blocked pipeline: https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43492776/showcase/-/pipelines/2981349924 · gate job log: https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43492776/showcase/-/jobs/17417614314
- Duo Draft fix MR !2: https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43492776/showcase/-/merge_requests/2
- Green fix pipeline: https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43492776/showcase/-/pipelines/2981363471
- Duo flow (visible to project members): https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43492776/showcase/-/automate/flows/1019358
Built With
- gitlab
- gitlab-ci
- gitlab-duo
- gitlab-duo-agent-platform
- gitlab-pages
- gitlab-sast
- python
Log in or sign up for Devpost to join the conversation.