Try it live

Inspiration

Coding agents can now write a merge request in minutes. Everything after that is still a person's job: reviewing it, deciding whether it is safe to ship today, watching the rollout, and cleaning up when it breaks. The hard question is not whether agents can ship, but how much they should be allowed to.

Site reliability engineering already answers that for humans with the error budget: when production is healthy you move fast, and when users have been hurt recently you slow down and fix things. Spillway gives that rule to agents. A dam's spillway releases water at a rate set by how full the reservoir is; Spillway releases change at a rate set by how much error budget is left.

What it does

A human writes an issue. From then on, no human acts.

  1. Plan. A planner agent (a GitLab Duo flow started by the issue) reads the issue, the code, the autonomy policy and the live reliability status. It proceeds, asks one question, or defers the work if the current budget tier does not allow it.
  2. Create. A builder agent writes the change and its tests, runs them, and opens a merge request.
  3. Verify, Secure, Package. The MR pipeline runs lint, type checks and tests with coverage, then SAST, secret detection, dependency scanning and IaC scanning, then builds and container-scans the image.
  4. Ship. A deterministic governor checks every gate: protected paths, scanner reports (findings the change introduces), a reviewer agent's verdict bound to the exact commit, and the live error budget from Cloud Monitoring. It merges exactly the reviewed commit, defers, sends the MR back to the agent with reasons (at most twice), or hands it to a human.
  5. Release. The budget tier decides the canary shape: 10 % → 100 % when healthy, 5 % → 25 % → 100 % with longer bakes as the budget is spent, 5 % → 100 % with a 15-minute bake when frozen. The release is a GitLab child pipeline generated from that decision, so the pipeline graph shows what policy chose.
  6. Monitor. At each look a canary judge compares the new Cloud Run revision with the stable one on Cloud Run's own request metrics: a one-sided Fisher exact test, corrected for every planned look, plus a practical burn-rate threshold. An inconclusive canary is never promoted.
  7. Recover. On failure, traffic returns to stable at once, a GitLab incident is opened with the evidence (SLI table, p-values, the failing revision's own log lines), and releases are blocked. A responder agent finds the cause, writes a regression test and a fix; the fix goes through the same gates and its own canary, and the governor closes the incident when it is proven in production.
  8. Govern. Every ten minutes the governor recomputes multi-window burn rates. A fast burn opens an incident and freezes autonomy: only incident fixes ship. Deferred features resume by themselves when the budget recovers. Every decision is written to a hash-chained ledger, shown live on a public control room.

The autonomy policy is a YAML file in the repository. Changing it changes what the agents may do, and the agents are not allowed to change it.

What happened when we ran it (all on the public project)

  • One issue, shipped hands-off. Issue #7 → planner and builder → MR !1. The governor blocked it (the container scan found high CVEs); the agent reworked it on its own, upgrading the base image and refusing to suppress any CVE; the reviewer approved the exact head commit; the governor merged; the canary passed at 10 % (look 3 of 7) and the governor released links-20261026-1432-d130054e.
  • A planted bug, caught and fixed with no human action (staged and disclosed). A human pushed a change that broke redirects for links older than a minute; every test and scanner still passed. At its first look the canary saw 47 of 69 requests fail against 0 of 1,328 on stable (burn rate 681×, p = 5×10⁻⁷¹). Traffic went back to stable, incident #8 opened, and the responder found the exact faulty lines and opened MR !2 (a revert plus a regression test).
  • The error budget took over. The canary had burned real budget: the fast-burn alert fired, autonomy went healthy → frozen, and a fast-burn incident (#9) opened. A new feature issue (#10) was deferred by the planner, quoting the frozen tier's rule. The fix shipped behind the frozen tier's slower canary (5 %, 15-minute bake) as links-20261026-1802-663ea935, and the governor closed incident #8 itself.
  • Deferred work resumed by itself. When the budget recovered past 20 % the governor took deferred::budget off #10 and restarted delivery. MR !4 was reviewed, merged and released behind the cautious canary (5 % → 25 % → 100 %, 8-minute bakes) as links-20261026-2047-4f297056.
  • Then three more, overnight. Issues #11 (noindex on redirects), #12 (refuse links back to the shortener) and #14 (a robots.txt) each went issue → MR → review → merge → cautious canary → release with no human action: MRs !5, !6, !7. Six agent changes in production in total, every canary step passed.
  • Everything is in the ledger: 55 hash-chained records, chain intact.

How we built it

  • GitLab Duo Agent Platform: three custom flows. delivery is planner → builder; reviewer is read-only apart from one note; responder is investigator → fixer. A tool option the model cannot override forces every MR into main. Humans start delivery by creating an issue (work-item trigger); every later agent step is started by trusted CI through the Flows API.
  • The governor (Python 3.12, pydantic, SciPy): a pure, unit-tested core (budgets, burn alerts, canary statistics, policy decision, release plan, ledger) with thin adapters for GitLab, the Flows API, Cloud Monitoring, Cloud Run, Cloud Logging, Cloud Storage and Secret Manager. Its tick is a stateless reconciler: everything it has done is recorded as markers on the MR, so any run can be repeated safely.
  • GitLab CI/CD: verify, secure (SAST, Secret Detection, Dependency Scanning v2, Container Scanning, IaC), package (one BuildKit build pushed to the GitLab registry and Artifact Registry, so the scanned image is the released image by digest), configure (Terraform plan and apply), a generated release child pipeline serialised by resource_group, and a scheduled ship tick.
  • Google Cloud: Cloud Run (the links URL shortener the agents change, an always-on synthetic traffic generator, and the control room), Firestore, Artifact Registry, Cloud Monitoring (SLIs, plus SLO objects and a canary dashboard generated from the same policy file), Cloud Logging, Cloud Storage (the ledger, under a retention policy), Secret Manager and Workload Identity Federation. There are no service account keys and no CI/CD variables: CI jobs get short-lived credentials from their GitLab OIDC token, and Google's IAM accepts that token only from this project's protected main.
  • Terraform for all of it, with SLOs read from the same policy/slo.yaml the governor enforces.
  • Quality: 253 automated tests across four Python projects (property-based tests for the policy decision), strict type checking, and static validation of the flow definitions in CI.

Trust boundaries

  • Deterministic core, LLMs at the edges. Statistics, the policy decision and every production action are plain tested code. LLM output can only make a decision stricter: a reviewer can block a merge but never approve one policy rejects, and the planner's risk estimate can only raise the computed risk.
  • Agents never hold production credentials. No pipeline an agent can edit or trigger reaches Google Cloud or the governor's token.
  • Agents cannot change their own rules. An agent MR touching the pipeline, policy, governor, infrastructure, flows, or the traffic generator and dashboard that measure it, is refused.
  • Identity is checked, not assumed. A review counts only from the reviewer's account and only for the current commit; only the responder can declare a change an incident fix; a missing scanner report blocks like a failing one.

Challenges we ran into

  • The platform differed from the docs. GitLab's catalog rejected max_cycles and response schemas; the flow image has no pip and its sandbox blocks GitHub (so uv is installed from a hash-pinned PyPI wheel); a work-item trigger passes only the issue number. Each became a fix and a CI check.
  • Composite identity. GitLab records the human who started a flow as an MR's author, so the governor identifies a flow's work by its commits, which carry the flow's service account.
  • Base-image CVEs blocked every change. The first agent MR was blocked by 44 high CVEs already in production. The governor now blocks only findings a change introduces, as GitLab's MR widget does.
  • Our own design had a hole. An early version let the MR pipeline wake the privileged ship pipeline to merge faster. An MR's pipeline configuration is the agent's to edit, and a trigger can pass variables, so we removed it: merges wait for the next scheduled tick.
  • One cause, two incidents. The planted bug opened a rollback incident and then, by burning budget, a fast-burn incident; each started a responder, and we closed the duplicate fix by hand. Fixed after the run: a burn during an open rollback incident is now linked to it instead of starting a second responder.
  • Statistics with little traffic. Peeking every two minutes inflates false alarms and a quiet canary can look fine by luck, so the judge corrects for every planned look, requires a minimum sample, and fails an inconclusive canary.

Accomplishments that we're proud of

  • A real regression was caught at its first canary look and rolled back automatically, and the fix was diagnosed, tested, reviewed, released and the incident closed by agents and the governor.
  • The pipeline graph changes shape with the error budget: the project's Releases page has all three shapes, healthy (10 %), frozen (5 %, 15-minute bake) and cautious (5 % → 25 %).
  • The agents froze themselves when users had been hurt, and deferred feature work without being told.

What we learned

  • The useful question is not whether an agent can ship, but how much autonomy the system has earned right now, and that can be measured.
  • Keep LLMs where judgement helps (planning, coding, reviewing, diagnosing) and out of where determinism matters (statistics, policy, production actions).
  • Least privilege for agents is mostly about pipelines: who can define them, who can trigger them, and what they can pass along.

What's next for Spillway

  • Error budgets per service and per team.
  • More SLIs (correctness probes, saturation) and percentile-based latency per step.
  • A Duo agent that proposes policy changes for a human to approve, so humans govern the rules, not changes.

Disclosure

  • Staged: the planted bug (a human-authored commit, labelled as such) to show the canary and the responder. The fast burn and freeze that followed were real consequences of it.
  • Human actions during the run: creating issues #7 and #10 to #14, pushing the planted bug, closing duplicate MR !3, re-filing #13 as #14 (its delivery session posted a plan, then failed on a Duo service error, "Failed to acquire lock on workflow"), and closing incident #5 (a canary that starved during setup, before the traffic generator was deployed). The governor acts with the entrant's own GitLab token, so its notes appear under that account.
  • The SLO window is 24 hours instead of 28 days so the budget moves within a demo; the maths does not depend on the window.

Built With

Share this project:

Updates

Submission history