Skip to content

About

Policy-gated auto-merge, scheduled health monitor, GitLab Incident + automatic rollback, and journey cards for any GitLab project. One CI include, GitLab Free, optional Duo agents.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Autopilot — one issue in, production out

Code generation is solved. Autopilot takes over everything after the code. You create one GitLab issue with the label autopilot. Agents plan it, build it, review it against a written policy, let the scanners judge it, fix what the scanners find, merge it, package it, release it, promote it from staging to production, watch it in production, roll it back if it breaks, and write the story back on your issue. Nine DevSecOps stages, zero humans in the loop.

CI Release Python 3.11+ License: MIT

Use it in your own project

One include adds the policy gate, auto-merge, the scheduled monitor with incident + automatic rollback, and the journey card to any GitLab project. It runs on GitLab Free with hosted runners; GitLab Duo flows are optional.

include:
  - remote: https://raw.githubusercontent.com/vigohan95/gitlab-autopilot/v0.2.0/templates/Autopilot.gitlab-ci.yml

Then copy templates/AUTOPILOT.md to your project root, add a masked CI/CD variable AUTOPILOT_TOKEN (project access token, scope api), and label a merge request autopilot. docs/QUICKSTART.md walks through it in five minutes, monitor and rollback included. The same scripts are on PyPI: pip install gitlab-autopilot gives you autopilot-policy, autopilot-automerge, autopilot-incident, autopilot-monitor and friends (--help on each). The rest of this page is the story of the project that runs them for real: the judged hackathon project, its live drills, and the policy they enforce.

Live drills, 2026-10-14 and 2026-10-16 — failures included (judged project; the full timeline is in docs/DRILL-2026-10-14.md). Six live journeys ran in the judged project (rows 0–0f of the judges' table), including a live automatic rollback. We ran two failure scenarios on purpose so that the Reviewer, Security Fixer and Incident Responder flows had real work to do, filed two issues after the fixes, and two days later released v0.1.3 and broke production again to run the fixed rollback path live:

  • Security loop. A human opened MR !2 pinning fastapi==0.125.0. F2 answered HUMAN REQUIRED (§2.2, §3.3). pip-audit failed pipeline 2945456466 (starlette, 10 known vulnerabilities). F3 opened fix MR !3 (fastapi==0.143.0) and F2 approved it. A human merged !3 into the feature branch (§3.2), then auto-merge merged !2. The vulnerable pin never reached main.

  • Incident loop. The monitor schedule, pointed at an always-503 URL, failed. The incident job opened Incident #3. F4 posted the RCA about 80 s later. It named the real cause, a misconfigured probe target, and said a rollback would not help.

  • Run 4, hands-off, after the fixes. A human filed issue #4 at 07:29Z, and MR !4 merged at 07:39Z with no human touch. GitLab Duo Code Review had left a valid finding on the tests two minutes earlier, and the gate merged past it.

  • Run 5, the follow-up, two human interventions. Issue #5 (07:53Z) asked for that missing test. F1 opened MR !5 (test-only, +30 lines), F2 approved it, and Code Review found nothing left to comment on. Then a container-registry HTTP 500 failed build-image, and the job sat failed until a human retried it 75 minutes later. Next, auto-merge held the MR on a false positive (finding 7). After the fix and a human rebase, !5 merged at 09:28Z and reached production at 09:34Z. This run was not zero-touch.

  • Run 6, a live automatic rollback (2026-10-16). We released v0.1.3 (main pipeline 2954320350: release at 08:46Z, production deployed from the release commit at 08:48Z), then pointed the monitor at the always-503 URL again. The scheduled monitor failed at 08:52:44Z. The incident job opened Incident #6, resolved the deployed release as v0.1.3 and the target as v0.1.2, created the branch rollback/v0.1.2/incident-6 and started the rollback pipeline 2954357168 with no variables. Its deploy-production succeeded at 08:54:52Z: 2 min 8 s from monitor failure to production rolled back, zero human touches after the schedule was played. F4 posted the RCA at 08:54:46Z; F1 started on the incident and skipped it (no branch, no MR).

  • What broke, and the fix commit for each (details and regression tests in the drill log):

    1. The §3.3 "new findings" baseline was empty for a fix MR into a feature branch, so the gate held a correct fix (abee55e).
    2. A policy-check label could override the reviewer agent's HUMAN REQUIRED verdict (838637f).
    3. The automatic rollback was refused. It needed pipeline variables, which our Developer role may not set. It now runs on a rollback/<tag>/incident-<iid> branch with no variables (5f2d251). It ran live on 2026-10-16 (run 6): Incident #6 → rollback pipeline 2954357168 → production rolled back 2 min 8 s after the monitor failed.
    4. The "monitor-only" schedule also ran a dependency scanner; the scanners now run on MR and main pipelines only (914c354).
    5. The rollback target could be the release already running. It is now the release below the deployed one, or a human if nothing names the deployed release (82614ff). Ran live on 2026-10-16: deployed v0.1.3, target v0.1.2.
    6. Code Review findings were advisory (run 4). New AUTOPILOT §3.7: unresolved review threads hold the merge (6339d18).
    7. Two fixes interacted (run 5). With fix 4 in place, the newest green main pipeline was a scanner-free schedule run, so fix 1's baseline had zero findings and pre-existing ones looked new. The baseline now skips pipelines that never run the scanners (f3b096e).
    8. A transient registry 500 was not retried (run 5). Registry-bound jobs now retry GitLab's infrastructure failure classes, and every docker login/build/push goes through a retry wrapper that retries only registry 5xx, "unexpected status" and TLS or network timeouts (3c85877). This is tested, but it has not yet met a live registry error.

    Two observations. First, the reviewer verdict is not an MR approval here. review-approve's approve call answers 401 in this project (likely because the token's user is recorded as the MR author, and authors may not approve), so §3.2 is decided by F2's newest signed verdict note. We had claimed otherwise; that claim is now corrected everywhere. Second, the incident also woke F1, because every new work item fires its trigger. F1 changed nothing. It now skips incidents as its first step, which takes effect once the registered flow is re-pasted in AI > Flows.

Play it yourself — one click each, nothing to install.

  • File an issue: new issue from the autopilot template (any signed-in GitLab.com user). The Planner flow answers with a numbered plan citing AUTOPILOT.md in about 2 minutes, the Developer step opens the MR, CI reviews, gates, merges, releases and writes the journey card and the narrative back on your issue (about 15 minutes wall clock; the 2026-10-10 run below is what to expect).
  • Replay the three scenes without Duo — manual jobs in the demo stage of the latest main pipeline (today: pipeline 2941380630; playing a job needs the Developer role on the project, reading the logs does not): demo:mr — the hands-off merge: issue → one-line version-bump MR → policy-check → auto-merge → release-cli release → journey card, ~12 min. demo:security then demo:security-fix — the security gate: the MR pins fastapi==0.125.0, pip-audit goes red on starlette PYSEC-2026-248/249 and the MR cannot merge; the fix restores the pin citing §4.1 §4.2 and merges, ~14 min. demo:chaos-on then demo:chaos-off — self-healing: production is broken, the scheduled monitor goes red, the incident job opens a GitLab Incident and starts the rollback, ~5 min. DEMO.md has the step-by-step timings.
  • Receipts without signing in: the pages job publishes a one-page table of every journey (Deploy > Pages has the live URL) — lead time, human actions after filing, CI footprint, each number linking to the GitLab note it came from (scripts/receipts_page.py).
  • Judging freeze: after 2026-10-27 13:00 UTC (AUTOPILOT §1.3, enforced by scripts/policy.py on merge requests and by the Planner flow on issues) a new autopilot issue or MR gets one polite "frozen for judging" note and nothing else happens.

It ran for real on 2026-10-10 (every link is a public page): one human filed issue #2 with the label autopilot. The Work item created trigger started the Planner + Developer flow (session 9286698), which posted a plan citing AUTOPILOT §2.1 §2.2 §3.4, committed 4 files (code + tests) and opened MR !1. The review-handoff job started GitLab Duo Code Review (session 9286720): "found nothing to comment on". policy-check approved. The auto-merge gate refused twice, honestly (1: it counted all 44 pre-existing High/Critical base-image findings, the policy says new; 2: the finding key still carried the per-commit image tag), then passed after the gate was fixed and the MR rebased: MR !1 merged at 14:10Z (de42064a), closing #2. Main pipeline 2933589447 posted the journey card; the Pipeline passed trigger started the Closer flow (pipeline 2933596153), which wrote the narrative on the issue at 14:18Z. One human action; agents planned, wrote and narrated; CI kept the gate. Row 0 of the judges' table below has the details.

Built for the GitLab Life After Code hackathon (Path A · Start Fresh · Hands-off). Everything in this repository was created after 2026-10-05. License: MIT.

What runs today, verifiably, in the history project: the deterministic layer — a 12-stage GitLab CI pipeline with the policy engine (AUTOPILOT.md enforced by scripts/policy.py), scanners, registries, release-cli releases, staging → production on Render, a scheduled monitor that opens incidents and triggers rollbacks. Three merge requests (!1–!3) were taken from "opened" to "merged and released" by that layer with no human click after the issue; the actors GitLab recorded are a project bot (scripts/demo.py) and CI jobs, and the notes say so.

What the GitLab Duo Agent Platform adds, in the hackathon project: the reasoning layer — 5 custom flows, 4 custom agents, 5 triggers and a read-only MCP server, defined in .gitlab/duo/ and registered in the showcase project's Agent Platform (plan/create by the Planner + Developer flow, policy review by the Reviewer flow, security fixes, incident RCA, the narrative journey card). The four agents, five flows and five triggers are registered and enabled in the hackathon project, and all five custom flows have run for real:

  • 2026-10-10 (row 0 of the judges' table): F1 Planner + Developer (session 9286698), Code Review (session 9286720) and F5 Closer (workflow pipeline 2933596153).
  • 2026-10-14 (rows 0b–0e): F2 Reviewer (session 9426174, HUMAN REQUIRED on !2; session 9426628, APPROVED on !3), F3 Security Fixer (opened fix MR !3) and F4 Incident Responder (the RCA on Incident #3); F1 and F2 again on issue #4 / MR !4, hands-off (run 4), and on issue #5 / MR !5 (run 5, two human interventions; row 0e).
  • 2026-10-16 (row 0f): F4 Incident Responder again (the RCA on Incident #6, 08:54:46Z, 92 s after the incident opened), while the incident job's automatic rollback ran to completion; F1 (session 9589385) started on the incident and skipped it as its first step, no branch and no MR.

The Merge request created trigger does not fire for a flow-created MR, because triggers need a human actor. The review-handoff CI job therefore starts F2 through the API. F2's newest signed Autopilot review: APPROVED note is the §3.2 decision that auto-merge reads. It does not become a GitLab MR approval: review-approve asks for one with the CI token, but in the judged project the approve call answers 401 and approved_by stays empty — likely because AUTOPILOT_TOKEN is the human member's personal access token, GitLab records that human as the author of flow-created MRs, and "Prevent approval by merge request creator" is on by default (Premium/Ultimate). See docs/DRILL-2026-10-14.md, run 4.

Hackathon project (judged, Duo Agent Platform): https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43190989/showcase — flows, agents, triggers, AI > Sessions, and the mirrored pipeline. History project (same code, where the pipeline operated for real): https://gitlab.com/hanwg666/gitlab-autopilot — merge requests !1–!3, releases v0.1.0–v0.1.2, environments on Render, incidents and rollbacks. Demo video: https://www.youtube.com/watch?v=4gf7paNMP-Q (added at submission). Both projects receive every commit through one GitHub Actions mirror (.github/workflows/mirror-to-gitlab.yml).

The application under Autopilot: pulse

pulse is a deliberately small but real service: a FastAPI site-health monitor. Give it URLs, it probes them on a schedule, stores results in SQLite, and serves a JSON API, an HTML status page and Prometheus /metrics. It has tests, pinned dependencies, a container image, a wheel, and two deployable environments — so every lifecycle stage has something genuine to act on. It also probes other sites while GitLab monitors it, which is the kind of loop this hackathon is about.

GET /livez (liveness)  GET /healthz (readiness)  GET /version  GET /metrics  GET /  (status page)
GET /api/sites POST /api/sites DELETE /api/sites/{id}
POST /api/check  GET /api/checks?site_id=&limit=  GET /api/summary

Architecture

flowchart TD
    H([Human: creates issue<br/>label autopilot]) -->|trigger T1: work item created| F1

    subgraph DUO[GitLab Duo Agent Platform]
        F1[F1 Planner + Developer flow<br/>plan · create]
        F2[F2 Reviewer flow<br/>cites AUTOPILOT.md]
        F3[F3 Security Fixer flow]
        F4[F4 Incident Responder flow<br/>+ MCP pulse-readonly]
        F5[F5 Closer / Release Scribe flow]
        CR[Code Review<br/>foundational flow]
        FP[Fix CI/CD Pipeline<br/>foundational flow]
    end

    F1 -->|opens MR| MR[(Merge request)]
    MR -->|trigger T2: MR created| F2
    MR --> CR
    MR --> P

    subgraph P[".gitlab-ci.yml — merge request pipeline"]
        direction LR
        T[test · lint<br/>verify] --> S[SAST · secrets · deps<br/>pip-audit · secure] --> B[build-image · wheel<br/>package] --> G[policy-check · auto-merge<br/>govern]
    end

    S -->|trigger T3: pipeline failed| F3
    F3 -->|fix MR| MR
    T -->|other failures| FP
    G -->|AUTOPILOT §3 satisfied| M[(merged to main)]

    subgraph MP[".gitlab-ci.yml — main pipeline"]
        direction LR
        R[release-cli<br/>release] --> DS[deploy staging<br/>configure] --> VS[verify staging] --> DP[deploy production] --> JC[journey-card<br/>audit-trail · govern]
    end
    M --> MP
    MP -->|trigger T5: pipeline passed| F5
    F5 -->|narrative + close| H

    SCH([schedule: every 2 h]) --> MON[monitor job<br/>monitor]
    MON -->|fail| INC[incident job:<br/>opens Incident, triggers rollback]
    INC -->|rollback branch: last release, no variables| MP
    MON -->|trigger T4: pipeline failed| F4
    F4 -->|RCA on the Incident| INC
Loading

Two layers, on purpose:

  • Deterministic layer (CI): everything that must be exact — scanners, builds, the policy gate, the merge call, the release, the deployments, the monitor, the incident, the rollback trigger — is a CI job with a script in scripts/. It runs even when no agent is listening.
  • Reasoning layer (Duo): everything that needs judgement — planning, coding, policy review, choosing a security fix, root-cause analysis, writing the story — is a flow. Flows start from GitLab triggers; CI hands off to them when a trigger cannot fire (triggers need a human actor).

The nine stages, and where to see each one

stage what Autopilot does evidence
plan Planner agent reads the issue, posts a numbered plan citing AUTOPILOT.md the issue's notes; AI > Sessions
create Developer step branches, codes, tests, opens the MR with Closes #n the MR; its commits
verify test (pytest + coverage + JUnit), lint (ruff → Code Quality report), verify-staging MR widget: tests, coverage, code quality
package build-image → Container Registry; build-wheel → PyPI Package Registry Deploy > Container Registry, Package Registry
secure SAST + Secret Detection (GitLab templates, run on every MR and on main) and pip-audit (fails the pipeline on any known vulnerability — this is the scanner that blocked MR !3). Dependency Scanning and Container Scanning are included from the GitLab templates but self-skip on the Free tier (they show as allowed-to-fail/skipped; Ultimate only). Failures → Security Fixer flow (Duo) or a scripted fix MR MR security widget; red pip-audit job on MR !3; fix MRs labelled security-fix
release release-cli cuts vX.Y.Z with generated notes and asset links when the version changes Deploy > Releases
configure Environments staging → production on free Render web services (deploy by commit), resource_groups, rollback job Operate > Environments (deployment history)
monitor scheduled monitor job probes /healthz and /version; red → incident job opens a GitLab Incident and triggers rollback; Incident Responder flow writes the RCA Monitor > Incidents; the rollback pipeline on branch rollback/<tag>/incident-<iid>
govern policy-check enforces AUTOPILOT.md on every MR; auto-merge cites the rules; audit-trail artifact; the reviewer agent's signed verdict (§3.2) and no unresolved review threads (§3.7); GitLab audit events; journey card on the issue with lead time (issue created → production deployed), human actions after filing and the CI footprint (scripts/sci.py, an estimate documented in docs/SCI.md, also the sci.json artifact) MR notes; Secure > Audit events; the journey card

Judges: how to verify in 5 minutes

Everything below is a page in this project; nothing needs to be run locally.

minute open what you should see
0 issue #2 · MR !1 · main pipeline 2933589447 in the hackathon project (AI > Sessions/Flows/Agents need a signed-in member) A real, complete Duo Agent Platform run (2026-10-10): issue #2 "Add per-site latency percentiles (p50/p95)" filed by a human → Work item created trigger → f1-planner-developer (session 9286698): plan note citing AUTOPILOT §2.1 §2.2 §3.4, commit by f1-planner-developer (4 files, code + tests), MR !1. The review-handoff job started GitLab Duo Code Review (session 9286720): "found nothing to comment on". (The Merge request created trigger did not start f2 for a flow-created MR — triggers need a human actor — which is why the CI hand-off exists.) policy-check approved. auto-merge refused twice, honestly: first it counted all 44 pre-existing High/Critical base-image findings (policy says new; fixed), then the finding key still carried the per-commit image tag (fixed); both refusals are notes on the MR. After a rebase the gate passed — "§3.3 no new High/Critical findings (44 pre-existing, tracked on main)" — and MR !1 merged at 14:10Z (de42064a), closing #2. Main pipeline 2933589447 (Ultimate scanners, dry-run deploys) posted the journey card; the Pipeline passed trigger started f5-closer (workflow pipeline 2933596153), which posted "Autopilot journey — narrative" on #2 at 14:18Z. One human action; agents planned, wrote and narrated; CI kept the gate.
0b MR !2 · MR !3 · pipeline 2945456466 · docs/DRILL-2026-10-14.md Security loop, live (2026-10-14). A human opened !2 pinning fastapi==0.125.0 with the autopilot label. F2 (session 9426174) answered HUMAN REQUIRED, citing §2.2 and §3.3. pip-audit went red (starlette 0.50.0, 10 known vulnerabilities) and auto-merge never ran. F3 (trigger: pipeline failed) read the log and opened fix MR !3 (fastapi==0.143.0, labels autopilot + security-fix); F2 approved it (session 9426628). The gate then held the correct fix: its §3.3 baseline was empty for a feature branch (finding 1, fixed in abee55e). A human merged !3 (§3.2), and auto-merge merged !2 into main at 06:56Z with an empty net diff. Finding 2 (838637f): a policy-check label could override F2's HUMAN REQUIRED verdict. The verdict is now authoritative.
0c Incident #3 · scheduled pipeline 2945603793 · RCA note Incident loop, live (2026-10-14). PULSE_PRODUCTION_URL pointed at an always-503 endpoint for a zero-cost outage. The scheduled monitor reported DOWN after 5 attempts (07:02:30Z). The incident job opened Incident #3 (07:03:04Z). F4 posted the RCA at 07:04:25Z (Impact, Timeline, Root cause, Remediation): it identified a misconfigured probe target, not a PULSE fault, said a rollback would not help, and added rca::done. The automatic rollback was refused: "Insufficient permissions to set pipeline variables" (finding 3). It now runs on a rollback/<tag>/incident-<iid> branch with no variables (5f2d251), and it ran live on 2026-10-16 (row 0f). The incident also started F1, which made no change; F1 now skips incidents first.
0d issue #4 · MR !4 · main pipeline 2945742313 · docs/DRILL-2026-10-14.md (run 4) Run 4, hands-off, after the fixes (2026-10-14). A human filed issue #4 "Add 24h uptime percentage per site to /api/summary and the status page" at 07:29:28Z. F1 posted the plan (07:31) and opened MR !4 (07:33; pulse/app.py, pulse/store.py and their tests). F2: APPROVED (07:35). policy-check allowed; auto-merge cited "§3.2 reviewer agent verdict: APPROVED; §3.3 no new High/Critical findings (44 pre-existing…)". MR !4 merged at 07:39:18Z (e9ca9d94) and closed #4; deploy-production finished at 07:47:46Z. Lead time: 9 min 50 s to merge, 18 min 18 s to production. Human touches after filing: 0. (Our own push cancelled the main pipeline at 07:40:38Z and it was retried at 07:42:00Z, which accounts for about 1.5 min; sync_main.sh now refuses to push while a main pipeline runs.) Two honest caveats: GitLab Duo Code Review left a valid finding on tests/test_app.py and the gate merged past it — now AUTOPILOT §3.7 holds the merge while review threads are unresolved; and the review-approve call answered 401, so no MR approval exists — §3.2 was decided by F2's signed verdict note.
0e issue #5 · MR !5 · main pipeline 2946146803 · docs/DRILL-2026-10-14.md (run 5) Run 5, the follow-up to run 4's Code Review finding (2026-10-14). A human filed issue #5 at 07:53:38Z. F1 posted the plan (07:55) and opened MR !5 at 07:56:31Z: tests/test_app.py only, +30 lines. F2: APPROVED (07:59). GitLab Duo Code Review "found nothing to comment on" (08:00), so the follow-up settled its own earlier finding. Human touches after filing: 2. (1) build-image failed at 07:59:50Z on a container-registry "unexpected status … 500 Internal Server Error" (MR pipeline 2945797046). Nothing retried it, so a human retried the job at 09:15:17Z. Fixed: infrastructure retries plus a registry retry wrapper (finding 8, 3c85877). (2) At 09:18:38Z auto-merge held the MR on "8 NEW High/Critical findings". This was a false positive: there were 0 new findings against main push pipeline 2945783422. The baseline had been the scanner-free 08:08 schedule run (finding 7, f3b096e). An MR pipeline runs the gate from the MR's own commit, so a human rebased !5 at 09:21:54Z. The fixed gate passed. MR !5 merged at 09:28:02Z (09a701a) and closed #5. deploy-production finished at 09:34:08Z. Lead time: 1 h 34 min 24 s to merge and 1 h 40 min 30 s to production. Of that, 75 min was the failed job waiting for a human. This run was not zero-touch.
0f Incident #6 · scheduled pipeline 2954349065 · rollback pipeline 2954357168 · release v0.1.3 · docs/DRILL-2026-10-14.md (run 6) Run 6, release v0.1.3 and a live automatic rollback (2026-10-16). Commit 9c8280d1 (pulse 0.1.3, two new /metrics gauges) reached main at 08:42Z; main pipeline 2954320350 created release v0.1.3 at 08:46:47Z and deploy-production (dry run) deployed that commit at 08:48:41Z. Then PULSE_PRODUCTION_URL was pointed at the always-503 endpoint and the schedule played. The monitor failed at 08:52:44Z. The incident job opened Incident #6 at 08:53:14Z, read production's last deployment (main at 9c8280d1 = release v0.1.3), chose v0.1.2 as the target, created rollback/v0.1.2/incident-6 and started pipeline 2954357168 on it with no pipeline variables (08:53:19Z). That pipeline ran deploy-staging → verify-staging → deploy-production (success 08:54:52Z) → rollback-note → audit-trail, no scanners, no build; production's newest deployment is now the rollback/v0.1.2/incident-6 ref. F4 posted the RCA at 08:54:46Z (rca::done, incident left open per §6.3). F1 started on the incident (session 9589385) and skipped it: no branch, no MR. Lead time: 2 min 8 s from monitor failure to production rolled back (1 min 38 s from the incident). Human touches after playing the schedule: 0. Findings 3 and 5 from 10-14 both ran live here.
1 Pipelines → the latest main pipeline 12 stages left to right: version → test lint → semgrep-sast secret_detection pip-audit → build-image → container_scanning build-wheel → release → deploy-staging → verify-staging → deploy-production → journey-card audit-trail → demo:* (manual). Every job is green; click any for its log.
2 Releases · Container Registry · Package Registry v0.1.0, v0.1.1 cut by release-cli with generated notes and asset links; the image tagged by commit SHA, latest and vX.Y.Z; the pulse-monitor wheel per version.
3 Environments staging and production with deployment history (dry-run until RENDER_API_KEY is present, then live onrender.com URLs).
3 Merge requests (all) each autopilot MR carries the policy-check note (rules §1.1 §2.1 §2.2 §3.4 §3.5 with pass/fail), the auto-merge note ("Merging when this pipeline succeeds", §3.2 §3.3), and the labels autopilot::approved / autopilot::needs-human. The security-fix MR shows the red pip-audit run and the fixing commit.
4 Issues · Incident #3 · monitor pipeline 2933042883 issue #1 closed with its journey card (the first card over-credited Duo flows; the corrected card is the later note). Incident #3 timeline, honestly: demo:chaos-on degraded /healthz on the live production service (07:10Z) → scheduled monitor red after 5 attempts → incident job opened #3, cleared the chaos switch on the service (production healthy again at 07:14Z) and triggered the rollback to v0.1.1. The first rollback attempt (pipeline 2933058599) failed because the platform health check did not exist in the old release — fixed by health-checking /version — and the retried rollback pipeline 2933092058 deployed v0.1.1 to staging and production (/version → 0.1.1) and posted the rollback note on #3; the next push to main rolled production forward to 0.1.2.
4 AUTOPILOT.md · scripts/policy.py the policy in prose and in the YAML block the CI job enforces; every agent/script note ends with autopilot: rules=[…] pointing back here.
5 .gitlab/duo/ · AI > Flows / Agents / Triggers / Sessions the 5 flow definitions, 4 agents, trigger matrix, mcp.json, agent-config.yml; the same objects in the UI and their sessions once the group has Duo Agent Platform enabled.
+1 the manual demo:* jobs on the latest main pipeline play demo:mr (hands-off merge + release), demo:security / demo:security-fix (security gate), demo:chaos-on / demo:chaos-off (incident + rollback) yourself — they only need the project's AUTOPILOT_TOKEN variable; DEMO.md has the timings.

Audit trail: Secure > Audit events and the audit/pipeline.json artifact of every pipeline.

Policy as code: AUTOPILOT.md

AUTOPILOT.md declares the autonomy boundary in prose and in a machine-readable YAML block that scripts/policy.py enforces in CI. Agents and scripts cite rule numbers (autopilot: rules=[§2.1, §3.4]) in every note, so a reader can see why the agent was allowed to merge, roll back, or stop.

  • §1 scope (only autopilot issues, judging freeze) · §2 protected paths and dependency bumps · §3 merge gate (green, approved by the F2 reviewer's signed verdict note — not a GitLab MR approval, see §3.2 —, no new High/Critical, ≤400 lines, no unresolved review threads §3.7) · §4 what a security fix may change · §5 release and promotion order · §6 self-healing (never roll forward) · §7 chaos switch for the demo · §8 retry budget · §9 secrets and audit.

GitLab Duo Agent Platform pieces

piece files notes
5 custom flows (flow registry v1, environment: ambient) .gitlab/duo/flows/ F1 planner+developer, F2 reviewer, F3 security fixer, F4 incident responder, F5 closer. Registered and enabled in the hackathon project; all five have run for real: F1 and F5 on 2026-10-10 (judges' table, row 0), F2, F3 and F4 in the 2026-10-14 drill (rows 0b, 0c), F4 again on the live rollback of 2026-10-16 (row 0f). For flow-created MRs, review-handoff starts F2; its signed verdict note decides §3.2 (review-approve's approve call answers 401 in the showcase, so no MR approval is recorded)
4 custom agents .gitlab/duo/agents/ autopilot-reviewer, security-fixer, incident-responder, release-scribe (for Chat and the UI; their prompts are embedded in the flows because custom flows cannot call custom agents)
5 triggers .gitlab/duo/triggers.md work item created, MR created, pipeline failed ×2, pipeline passed
3 foundational flows — Developer, Code Review (with mr-review-instructions.yaml), Fix CI/CD Pipeline
MCP server (read-only) mcp_server/pulse_mcp.py, .gitlab/duo/mcp.json pulse_health, pulse_summary, pulse_recent_checks, pulse_metrics, gitlab_releases; stdio and streamable-HTTP. Usable from the IDE / Duo CLI; GitLab.com's AI Catalog only offers curated MCP servers, so UI flows cannot attach it
repository context AGENTS.md, .gitlab/duo/agent-config.yml read by Developer and by the custom flows via context:inputs.user_rule

Repository layout

  • templates/ — Autopilot.gitlab-ci.yml (the one-line include) and AUTOPILOT.md (starter policy) for other projects.
  • scripts/ is also the gitlab_autopilot package on PyPI (pyproject.toml maps it); each script runs both ways.
pulse/              the service (FastAPI, SQLite, probes)
tests/              pytest (no network; httpx.MockTransport)
scripts/            CI helpers: policy.py, automerge.py, monitor.py, incident.py,
                    deploy.py, deploy_render.py, release_notes.py, journey_card.py, sci.py, codequality.py,
                    rollback_ref.py, start_flow.py, review_approve.py, receipts_page.py, check_ci_schema.py,
                    gitlab_api.py
docs/               SCI.md (CI footprint method + sources), RUNBOOK-M1-M2.md (security + incident scenarios),
                    DRILL-2026-10-14.md (what the live drill ran, found and fixed)
mcp_server/         read-only MCP server for the Incident Responder
.gitlab/duo/        flows, agents, triggers, mcp.json, agent-config.yml, review instructions
.gitlab-ci.yml      the eleven-stage pipeline
AUTOPILOT.md        the autonomy policy (prose + YAML)
AGENTS.md           conventions for agents
Dockerfile          non-root image, HEALTHCHECK, PULSE_PORT (7860 local, 10000 on Render)

Reproduce in your own group

Everything is free-tier; no card is needed anywhere.

  1. Create a public project from this repository in a group that has GitLab Duo Agent Platform turned on (hackathon onboarding group, Premium/Ultimate trial, or Free with GitLab Credits). Keep LICENSE at the root.

  2. CI/CD variables (Settings > CI/CD > Variables):

    variable value flags
    AUTOPILOT_TOKEN project access token, scope api, role Maintainer masked, not protected (MR pipelines run on unprotected branches)
    AUTOPILOT_F2_CONSUMER_ID numeric id of the f2-reviewer flow's consumer in this project (scripts/start_flow.py docstring has the GraphQL query); lets review-handoff start F2 on flow-created MRs —
    RENDER_API_KEY Render API key (Account Settings > API Keys); the two free services come from render.yaml (New > Blueprint) masked, protected — and protect rollback/* too (push: Developers + Maintainers), or the rollback pipeline, which runs on rollback/<tag>/incident-<iid>, cannot see it
    PULSE_STAGING_URL / PULSE_PRODUCTION_URL the onrender.com URLs printed by the first deploy (used by the scheduled monitor) —
    PULSE_CHAOS healthz — only on environment production, only during the demo (AUTOPILOT §7) environment-scoped

    Without RENDER_API_KEY the deploy jobs dry-run and still record environments; without AUTOPILOT_TOKEN the scripts that write to GitLab print what they would do.

  3. Runners: GitLab hosted runners (default). The flow jobs need the gitlab--duo tag, which hosted runners provide.

  4. Duo settings (top-level group > Settings > GitLab Duo): turn on GitLab Duo, Allow flow execution, Allow foundational flows (check Developer, Code Review, Fix CI/CD Pipeline), Allow external MCP tools. Project: Settings > General > GitLab Duo: turn on the same toggles and generate / confirm the agent configuration file.

  5. Agents: AI > Agents > New agent, four times, from .gitlab/duo/agents/*.yaml. Enable each in the project.

  6. Flows: AI > Flows > New flow, five times, paste .gitlab/duo/flows/f*.yaml into Configuration, publish, enable in the project (GitLab creates the service account). Settings > Repository > Push rules: allow the service account; Protected branches: allow it to push to autopilot/* branches.

  7. Triggers: AI > Triggers, five triggers as in .gitlab/duo/triggers.md.

  8. Schedule: Build > Pipeline schedules > New: cron 0 */2 * * * (every 2 hours), branch main, description "monitor". It was every 15 minutes until 2026-10-13; docs/SCI.md has the measured cost of both cadences.

  9. Approvals (govern), optional: §3.2 is decided by the F2 agent's signed APPROVED verdict note, not by an MR approval. review-approve also asks GitLab to record an approval with AUTOPILOT_TOKEN; that only succeeds when the token's user is allowed to approve the MR. Use a project access token (not the personal token of the human who files issues — GitLab records that human as the author of flow-created MRs, and "Prevent approval by merge request creator" is on by default). In the judged showcase our Developer role cannot create one, so the call answers 401 there and no approval is recorded.

  10. Run it: New issue > template autopilot > describe a small change. Watch AI > Sessions, the MR, the pipeline, Operate > Environments, and the issue's journey card.

  11. Self-healing demo: set PULSE_CHAOS=healthz on production, run the schedule manually; watch Monitor > Incidents and the rollback pipeline; then remove the variable.

Demo without Duo

DEMO.md is the runbook: scripts/demo.py (or the manual demo:* jobs on the main pipeline) plays the three scenes — hands-off merge + release, security gate, chaos → incident → rollback — through the API with only AUTOPILOT_TOKEN.1

Local development

pip install -r requirements-dev.txt
python -m pytest -q                              # 153 tests, no network
ruff check . && ruff format --check .
PULSE_DB=:memory: PULSE_INTERVAL=0 PULSE_SITES=gitlab=https://gitlab.com/ python -m pulse
python scripts/check_ci_schema.py                # validates .gitlab-ci.yml against GitLab's schema
python scripts/policy.py --files pulse/app.py --labels autopilot --lines 42
python -m mcp_server.pulse_mcp --transport stdio # MCP server

Contributor notes — pushing to main. GitHub is the mirror source for both GitLab projects. Push only with bash scripts/sync_main.sh && git push --force-with-lease origin main, then check the mirror with gh run list --workflow mirror-to-gitlab.yml --limit 1. sync_main.sh refuses to run while a showcase main pipeline is created, pending or running, because a push to main auto-cancels it: on 2026-10-14 our own runbook push cancelled run 4's journey pipeline, which had to be retried. --allow-running (or SYNC_ALLOW_RUNNING=1) overrides the guard when you accept that cancel.

Why it is built this way (for the judges)

  • Hands-off means a policy, not a prompt. The agents are allowed to merge because AUTOPILOT.md says when, and the same rules are checked by code.
  • Security is a loop, not a report. Scanner failures route to an agent whose only job is a minimal fix MR that goes back through the same gate.
  • Monitoring closes on itself. The monitor opens the incident, the rollback is deterministic, the RCA is written by an agent that can only read.
  • Every stage leaves evidence in a GitLab page a judge can open.

Footnotes

  1. Since 2026-10-10 the history project's monitor schedule is paused: the personal namespace's Free-tier CI quota (400 min/month) was consumed by the quarter-hourly monitor (which also dragged semgrep-sast and audit-trail along on every run — about 149 runner-minutes a day; now every 2 h and monitor-only, about 6.4, see docs/SCI.md §4 — until 2026-10-14 the showcase's schedule also ran gemnasium-python-dependency_scanning, about 15.9 a day, which the live drill exposed and which is now excluded) and the demo scenes. The mirror pushes there with ci.skip; new pipelines run in the judged (showcase) project, and the history project's pipelines, releases, environments and incidents stay as evidence. ↩

About

Policy-gated auto-merge, scheduled health monitor, GitLab Incident + automatic rollback, and journey cards for any GitLab project. One CI include, GitLab Free, optional Duo agents.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages