Try it in 60 seconds

  • Live production site and its Promise Ledger: https://davidj-builds.gitlab.io/kept/ledger.html
  • Issue #2 in the repo shows the whole loop: the Promise Writer's Promise Card, the Reviewer's coverage table on MR !1, the Scribe's production receipt, the Responder reopening the issue with the commit that broke it, and the Scribe closing it again after a clean redeploy.
  • Build > Pipelines shows the lifecycle stages (gate, tests, preview promises, SAST, secret detection, package, staging, manual promotion, production verification, release) and the scheduled watch history.

Inspiration

Code generation is solved; knowing that what you asked for actually shipped, and is still true, is not. Today an issue closes when a merge request lands. Nobody checks its acceptance criteria against the live product, and nobody notices when a later change quietly breaks one. As agents write more of the code, that gap between "merged" and "kept" is where AI debt collects. We wanted the issue itself to keep score.

What it does

Kept turns every acceptance criterion in a GitLab issue into a promise: a small, deterministic HTTP check stored as code next to the change. Four GitLab Duo Agent Platform flows then carry that promise through all nine lifecycle stages and back:

  • Plan / Create. When an issue is opened, the Promise Writer flow turns each criterion into a promise, says plainly which ones can't be checked (with a suggested rewrite), implements the change, and opens a merge request that carries .kept/promises/<issue>.json. It posts a Promise Card on the issue, one line per criterion.
  • Verify / Secure. The pipeline runs 100+ unit tests on Python 3.9 and 3.12, runs every promise against a preview build (each criterion shows up as a test in the MR), and runs SAST and secret detection. A promise gate refuses checks that point outside the project's own sites. The Reviewer flow posts one table: criterion, promise, covered by this diff or not.
  • Package / Release. After a person merges, the built site and a manifest hashing every file and every promise go to the package registry. Staging deploys, promises are checked, a person clicks promote, production deploys, promises are checked again, and a GitLab Release is cut whose notes are the receipts.
  • Govern. The Scribe flow lifts the production receipt from the job log, posts it on the issue, and only then closes it.
  • Monitor. A pipeline schedule re-checks every promise ever kept. When one breaks, the Responder flow reopens the original issue with the evidence, the last deployment that kept it, the commit that most likely broke it, and a fix merge request.
  • Configure. Environments, allowed origins, policy and agent behaviour all live in the repo (.kept/config.json, .gitlab/duo/flows/*.yml, AGENTS.md); a script registers the flows and their triggers from the YAML.

The Promise Ledger page shows every promise, who wrote it (a human or which flow), whether it holds right now, and how many watch runs back that up.

Autonomy level: Supervised. Agents write the promises, the code, the review, the receipts, the incident report and the fix. A person approves two things: the merge and the production promotion.

How we built it

  • Duo Agent Platform: four custom flows (flow registry v1 YAML in .gitlab/duo/flows/), registered in the AI Catalog through the GraphQL API, enabled in the project, and wired to different triggers: work item created, merge request created, pipeline passed on the default branch, pipeline failed, plus mentions and reviewer assignment. Each flow runs as its own composite-identity service account and reads AGENTS.md for project rules.
  • Code checks, agents act. The kept engine is pure Python standard library (it has to run inside the flow runtime, which ships Python 3.9 and no pip): nine check types, a tiny HTML element matcher, JUnit output, receipts, a ledger with per-promise uptime and incident detection. No model runs in the watch loop. CI jobs hand structured results to the flows through marked blocks in job logs, so agents copy evidence instead of re-deriving it.
  • Pipeline: stages named for the lifecycle (plan, verify, secure, package, staging, production, release, monitor), GitLab SAST and Secret Detection templates, JUnit reports, the generic package registry (for the release package and for the watch history), environments, a manual promotion gate, downstream pipelines in the Pages-host projects (triggered with the CI job token), release-cli, and a pipeline schedule.

Challenges we ran into

  • Pages in the hackathon group are visible to members only, and participants can't change that, so staging and production are served from two small public Pages-host projects that the Kept pipeline triggers with the exact commit to build.
  • The flow runtime has no outbound internet and no pip, which pushed a clean split: CI jobs touch the live site, flows act through GitLab.
  • We built during a GitLab.com CI incident (14 Oct): trigger: bridge jobs sat in "pending" for over an hour while plain jobs ran, so deploys now call the pipeline-trigger API with the job token and follow the downstream pipeline; flows were started through the REST API while event triggers were delayed.
  • Kept caught a bug in Kept: our merge request said "Implements #2", which is one of GitLab's closing words, so GitLab closed the issue at merge, before production had kept anything. The gate now fails any merge request whose description would close an issue early, and the flows write "Related to #N".
  • The Reviewer flow caught a weak promise of ours: it checked a label, and without JavaScript the figure was a dash. The build now renders the real figures into the static HTML, so the promise checks the number.
  • Agents need structured evidence they can't misread: receipts travel between jobs and flows as marked blocks the Scribe copies verbatim.

Accomplishments that we're proud of

  • One artifact, the promise, threads through all nine stages, and the loop closes back into planning: issues reopen themselves with the commit that broke them.
  • Kept is honest about what it can't check: subjective criteria are listed as "not checkable" with a suggested rewrite, never faked. When a preview failed for lack of watch data, the Responder refused to fake an uptime number to make the check pass.
  • It kept running without us. Between the demo on 14 Oct and 26 Oct the scheduled watch ran 51 times on its own: 48 green, one the staged drift break shown in the video, and two that failed on GitLab runner network errors (repository fetch, artifact upload), not on a promise.
  • 140 automated tests, including end-to-end runs of the real CLI against a real HTTP server.

What we learned

Agents are most useful at the edges where judgement is needed (turning prose into checks, diagnosing a regression), and most trustworthy when the thing they report is computed by plain code.

What's next for Kept

Promises for APIs and CLIs, not only web pages; promise templates for common criteria; a merge request widget for promise coverage; and organisation-wide promise uptime as a delivery metric next to cycle time.

Built with

Python (standard library), GitLab Duo Agent Platform (custom flows, AI Catalog, triggers), GitLab CI/CD, GitLab Pages, GitLab Package Registry, GitLab Releases, GitLab SAST and Secret Detection, HTML/CSS/JavaScript.

AI disclosure

Built by David J with AI assistance for code and writing. The demo video narration is text-to-speech. The Kept flows themselves use GitLab Duo's model through the Duo Agent Platform.

Built With

Share this project:

Updates

Submission history