Inspiration

Small teams ship without a release engineer. Somebody merges when the tests look green, tags a version, deploys and hopes. An AI autopilot could take that job, but an autopilot that can't say no will ship anything: a leaked key, a vulnerable dependency, a change that passes every test and is still ten times slower in production. We wanted a hands-off release system whose most important feature is refusing, and explaining why.

What it does

You file a GitLab issue with the label lastmile::autopilot and walk away. LastMile:

  1. hands the issue to GitLab's Developer flow, which writes a merge request with tests;
  2. checks that every commit on it came from the Developer flow's account, then marks it ready;
  3. asks the intake agent for a risk dossier and, when a scanner fires, asks the security-triage agent for a verdict with a reason;
  4. runs a trusted gate on protected main. The gate re-runs the tests on the merge result as a sandbox user, applies the policy file and merges (pinned to the evaluated commit), or parks the change with reasons;
  5. builds the image, signs it with keyless cosign, attaches a CycloneDX SBOM and tags a semver release;
  6. deploys a new Cloud Run revision at 0% traffic, smoke-tests it, sends 20% of synthetic traffic to it as a canary, and asks the canary-judge agent to promote or roll back. Hard latency and error limits override the agent;
  7. on a rollback, reverts the merge and opens an incident;
  8. closes the issue only after a verified promote, posts a "Shipped" note, and has the governor agent audit the whole run. The governor files issues against LastMile itself when it finds a gap.

The headline run. Issue #36 → !34 → v3.0.0 on Cloud Run, 11 Oct 2026. A small docs fix: /docs listed /healthz, a path the hosting platform reserves, so the Developer flow hid the route from the API docs (it still answers locally) and added 3 tests. The version is a major bump only because LastMile's deterministic API diff counts the hidden route as a removed operation and overrides the agent's rating. Filed at 05:58:41 UTC, merged at 06:14:39, promoted at 06:27:54 (canary p95 11.725 ms against 12.436 ms, 172 canary requests, no errors), issue closed at 06:28:39 (its only close event, after the promote), governor compliant at 06:29:38. That is 29 minutes from issue to production with zero human touches after filing the issue. The trigger (filing the issue) is the request and is not counted. Every automated step on the issue and the merge request carries a note naming its CI job.

It says no. In !27 a refactor of the daily report passed every test, and the gate merged it. On Cloud Run the canary measured p95 140 ms, then 196 ms, against 11 ms. The hard limit and the judge agent both said roll back. Traffic went back to the stable revision, the merge was reverted (5cafd83), and incident #25 was opened. The intake agent had flagged the N+1 query as a concern; the policy treats performance as something to measure, not guess.

It also refuses before merging. On 11 Oct, issue #35 asked for an outstanding-balance report; the Developer flow wrote !33, and the deterministic gate parked it at 05:32:39 UTC because it changed a money path without a default-off feature flag. The intake agent had rated it medium; the policy's floor for that path is high, and the floor wins. Money-path parks are never auto-repaired, so nothing shipped and production was not changed.

The trust model. A merge request never judges itself:

  • !21 edited the policy so that policy changes could ship automatically. The gate judged it with main's policy and parked it.
  • !11 committed a cloud access key. The triage agent called it a documentation placeholder, but secrets are never auto-dismissed, so it did not ship.
  • An agent verdict counts only if it comes from that flow's own service account (checked by numeric id), carries the gate's one-time nonce and is posted after the request.
  • Agents can raise the risk class, never lower it. Agent commits outside tests/** park the change.

Results. All 13 seeded benchmark changes, replayed live with no hints to the agents (neutral branch names, title-only descriptions): 13/13 expected outcomes, 6/6 unsafe changes stopped, 0/7 safe changes blocked, median 6 minutes from merge request to decision (benchmark/RESULTS.md). Other examples: the intake agent wrote 13 missing tests and committed them on !6; the OpenAPI diff forced a major version on !14; the triage agent accepted an MD5 ETag as a false positive, with its reason recorded in the release, on !7.

Stages covered (9). Since v2.6.0, every release note carries a stage table built from that release's own jobs (v3.0.0):

Stage What LastMile does
Plan issue → Developer flow → merge request; report back on the issue
Create intake agent writes missing tests; bounded auto-repair (one hand-back)
Verify tests on the merge result, flaky-test check across hash seeds, OpenAPI breaking-change diff
Secure GitLab SAST, Secret Detection, Dependency Scanning, plus the triage agent
Package container image, keyless cosign signature, CycloneDX SBOM
Release semver from evidence, release notes, accepted-risk record
Configure per-environment config, default-off feature flags, pinned Cloud Run limits
Monitor Cloud Run canary with traffic split, judge agent plus hard limits, rollback, revert, incident; scheduled production check
Govern evidence pack, governor agent audit, self-filed governance issues

The demo application is kirana-ledger, a small ledger and GST invoicing API for a fictional neighbourhood grocery (kirana) store.

How we built it

  • GitLab Duo Agent Platform: four custom flows, published in the AI Catalog: intake, security triage, canary judge and governor (.gitlab/duo/flows/). GitLab's foundational Developer flow writes the code for issues. The flows only post typed JSON verdict notes; they never hold merge, approve or dismiss tools (enforced by a flow linter, lastmile/flowlint.py).
  • GitLab CI/CD: an untrusted merge-request pipeline (tests and scanners, no token) hands off to a trusted pipeline on protected main, which runs main's LastMile code and policy (lastmile/trusted.py). The policy lives in .gitlab/lastmile/policy.yml and also switches the autonomy level (hands_off, supervised, assisted).
  • Google Cloud: Cloud Run revisions with tags and traffic splitting, Artifact Registry, and keyless Workload Identity Federation from GitLab OIDC, limited to protected refs. The runtime service account has no roles. Setup is in infra/gcp/; the deploy target is lastmile/targets/cloudrun.py.
  • Supply chain: cosign keyless signing (Sigstore, GitLab OIDC) and a CycloneDX SBOM from syft (about 2,800 components).
  • Code: Python 3.12, FastAPI for the demo app, 219 tests (pytest), plus an offline harness that replays the seeded changes with scripted agent answers (python -m lastmile bench).
  • AI assistance (disclosed): much of the code and documentation was written with an AI coding assistant (Claude), directed, reviewed and run by the entrant. The agents inside LastMile are GitLab Duo flows. The video's voice-over is synthetic (a local text-to-speech model), and its background music was generated from code for this video.

Challenges we ran into

  • Making "hands-off" trustworthy. The first gate ran inside the merge-request pipeline, so an MR could in principle change the code that judged it. We moved every decision to a trusted pipeline on protected main, bound verdicts to a nonce and a service account, and kept the token out of every job that runs merged code. Benchmark 14 is a red-team test that checks this.
  • Closing the issue too early. On issue #24 GitLab closed the issue at merge, before the canary, because the Developer flow wrote a closing keyword. The gate now rewrites closing keywords and reopens the issue if GitLab still closes it, and the govern stage audits each issue's close history. Re-checks of #24 and #32 marked those runs non-compliant and filed #28/#29 and #33/#34, which were then fixed and closed.
  • A semantic merge conflict (incident #18): an agent-written MR was green on its own branch but broke main. The release stage refused to ship it, and the gate now judges the merge result, not the MR head.
  • The hackathon role cannot create a bot token, so automated actions appear under the entrant's username. Every one of them now posts a note naming its CI job.

Accomplishments that we're proud of

  • Repeated issue-to-production runs with zero human touches after filing the issue: #23 (about 24 min, before the move to Cloud Run), then on Cloud Run #24 (29 min), #30 (32 min), #31 (28 min), #32 (29 min) and #36 (29 min). LastMile's own audit later marked #24 and #32 non-compliant because GitLab closed the issue before the promote (see Challenges); #30, #31 and #36 are clean.
  • A live rollback on real Cloud Run revisions (!27), with an automatic revert and an incident.
  • 13/13 hint-free benchmark outcomes, with 0 safe changes blocked.
  • The governor agent found real gaps in LastMile's own pipeline (for example #9: the gate fired before the pipeline was green), and each one was fixed.

What we learned

  • Agents are most useful as evidence-gatherers. Letting them decide alone is the risky part, so LastMile lets them raise risk but never lower it, and lets hard limits override them.
  • Green tests are not proof. Measuring the release on real infrastructure caught what no test or agent review stopped.
  • Honest records matter more than a clean demo: we kept the early-close and semantic-conflict incidents visible and audited our own runs.

What's next for LastMile

  • A real managed database and real traffic in place of the simulated 2 ms latency and synthetic load.
  • A dedicated bot identity, once the platform allows one, so automated actions no longer use the entrant's account.
  • A pipeline execution policy, so that direct pushes to main are covered as well as merge requests.
  • A packaged "adopt it in your project" template (flows, policy, CI jobs and AGENTS.md), with the cost per release measured.

Honest limitations

  • Synthetic traffic only: the canary load comes from LastMile's load generator in CI. There are no real users.
  • The database is in memory; each query waits a fixed, simulated 2 ms (KIRANA_DB_LATENCY_MS) to stand in for a network round trip.
  • Automated steps run through the project automation token, which is the entrant's account. Every such step posts a note that links its CI job; agent verdicts come from the flows' own service accounts.
  • The bounded auto-repair path and the production-check rollback are built and unit-tested but have not yet been triggered live.

Built With

  • cosign
  • cyclonedx
  • docker
  • fastapi
  • gitlab-ci
  • gitlab-dependency-scanning
  • gitlab-duo-agent-platform
  • gitlab-sast
  • gitlab-secret-detection
  • google-artifact-registry
  • google-cloud-run
  • pytest
  • python
  • sigstore
  • sqlite
  • syft
  • workload-identity-federation
Share this project:

Updates

Submission history