Inspiration
Coding agents now write a change in minutes. Everything after that still waits for a person: reviewing it, checking its security, deciding whether it may merge, releasing it, watching it, and, when it breaks, working out why and undoing it. For a small team or a solo maintainer running a real service, such as a student society's events site, that life after code is where the hours and the risk are. I wanted to see how much of it GitLab Duo agents could own end to end, safely, with a person only setting the intent.
What it does
Campus Autopilot ships changes to Campus Events, a small FastAPI and Postgres app on Google Cloud Run, with no one in the loop. The app is only the target; the automation around it is the project.
- Issue to production. A code owner opens an issue. The Campus Autopilot flow, six Duo agents (triage, implement, review, security, finalize, report), plans it, writes it with tests, reviews it, checks its security and hands it off. A deterministic policy gate recomputes risk from the real diff. A merge steward, the only identity that merges, merges low-risk changes pinned to the commit it evaluated. Canaries release to staging and then production: 10% of traffic for 6 rounds, each path compared with stable, then promote or roll back. The issue gets a release summary with the evidence, the lead time and the CI carbon, and GitLab gets a release.
- Recovery. Every failed pipeline starts the Incident Response flow (detect, investigate, respond). It opens an incident naming the cause, and a revert. The steward merges that revert on its own only when git proves it exactly undoes a release that a canary stopped.
- Fix forward. A revert throws the feature away, so the steward files a re-land issue, which starts Autopilot again. Autopilot finds the cause in the original diff, re-applies the change with the fix and a test that fails on the original, and the result goes through the same reviews, gate and canaries. The release summary then posts the whole story on the incident.
- Agents propose, code decides. Agents never merge. Only small, additive changes with clean agent reviews and no new High or Critical findings merge on their own; everything else waits for a code owner. Approval policies live in a separate project agents cannot push to, and one variable switches between hands-off and supervised.
The run in the video (Oct 19, UTC). I merged one deliberately slow change at 09:00. The staging canary measured API reads at 729 ms against 62 ms and rolled it back at 09:07; production never saw it. Incident Response opened incident #12 naming the exact line at 09:09, and a revert. The steward merged the revert at 09:13 and filed re-land issue #13. Autopilot opened !42 at 09:16 with a plain SHA-256 digest, as RFC 9530 specifies, and a regression test. The gate computed low risk, the steward merged it at 09:20, and both canaries promoted it by 09:29: fixed in production 21 minutes after the incident opened, with no clicks after that first merge.
How I built it
- Two custom Duo flows in
.gitlab/duo/flows/, triggered by Work item created and Pipeline failed. Separation of duties comes from each agent's toolset, not its prompt: onlytriagereads the untrusted issue, onlyimplementcan edit files,reviewandsecuritycan only read and comment, and Incident Response's first agent has no tools at all. - GitLab CI/CD: lint, tests on SQLite and Postgres with 100% line and branch coverage, SAST, secret detection, dependency and container scanning, the policy gate, the image build, canary deploys, release summaries, GitLab releases, and a production watch on the schedule.
- Python scripts for the policy gate, merge steward, canary, release summary, production watch and carbon accounting.
- Google Cloud: Cloud Run, Cloud SQL, Artifact Registry and Secret Manager, all in Terraform with GitLab-managed state that holds no secrets, planned in merge requests without cloud credentials, and deployed keylessly through Workload Identity Federation.
- Governance: CODEOWNERS, approval policies in a separate security policy project, GitLab environments, and DORA metrics from GitLab's own analytics.
Challenges I ran into
- Flows cannot merge or start pipelines. A flow's token gets 403 on merges and on pipeline creation, so
finalizepushes an empty handoff commit that starts the merge request pipeline, and a scheduled steward merges. - Duo triggers ignore bots, so the steward uses a code owner's token, scoped to this project. GitLab therefore lists the steward's merges, notes and re-land issues under my name; its notes start with "Merge steward" and its issues say the steward filed them.
- Canaries that lied. Early canaries rolled back healthy releases. Cloud Run throttles idle CPU, so a baseline measured at another moment was not comparable (fixed by measuring canary and stable in the same round). One cold start became the p95 (fixed by per-path medians). A service-wide limit of 3 instances, split by traffic share, gave a 10% canary zero instances. The runner's DNS resolver added 2.5 s retries. Incident Response correctly declined to revert that last one.
- An agent that would not revert. Incident Response first declined a revert because the canary had already rolled back. Main still held the change, so I changed its instructions: revert whenever the diff explains the failure.
- A newly disclosed CVE in the base image blocked every merge request through the approval policy, Autopilot's included. I moved the image to Debian 13 slim, which halved its High and Critical findings, from 16 to 8.
Accomplishments that I'm proud of
- A complete loop, from issue to production and from a bad release back to a fixed one, with no clicks, all in the project's public history: 612 pipelines, 7 Autopilot merge requests merged (5 by the steward with no clicks), 4 incidents opened by the flow and all closed, and 2 re-lands.
- Incident #8: the flow declined to revert a healthy change because its diff did not touch the slow path. Knowing when not to act matters as much as acting.
- Measured carbon savings, from the platforms' own data: 39.5 to 0.67 vCPU-seconds per production request.
What I learned
- Agents are good at proposing and explaining; a deterministic judge has to decide. The gate ignores what agents claim and recomputes risk from the diff, so a wrong label cannot ship a risky change.
- Every automatic action needs evidence from the platform: git proves a revert is exact, and the canary's report proves a release was stopped.
- Compare measurements taken at the same moment, or the comparison lies.
What's next for Campus Autopilot
- The loop does not depend on the app: any service with a health check and a container deploy could use the flows, gate, steward and canaries. Package them as a reusable template.
- Set canary limits from real production traffic instead of fixed floors.
- Trigger the steward from events instead of a schedule, once a flow can start a pipeline safely.
- Measure model inference energy once GitLab reports it.
Built With
- alembic
- docker
- fastapi
- gitlab
- gitlab-ci
- gitlab-duo-agent-platform
- google-artifact-registry
- google-cloud-monitoring
- google-cloud-run
- google-cloud-sql
- google-secret-manager
- postgresql
- pytest
- python
- sqlalchemy
- terraform
- workload-identity-federation
Log in or sign up for Devpost to join the conversation.