-
-
Pipeline #2946876156 on main, before the deploy. deploy_production is started by a person; it has since run and passed on 14 Oct.
-
The promo plan for the top pick: partners Qloo says its fans over-index on, with the evidence beside each one.
-
The app the pipeline wraps: a live shortlist for a 300-capacity room in Asheville, with the Qloo evidence for each pick.
-
The venue form. The lamps on the truss light one by one as the agent finishes each step.
-
The GitLab Duo agent session for that review.
-
The Stagehand grounding guard flow reviewing a merge request and posting its verdict.
Inspiration
Stagehand already existed as an app. I wrote it for the Qloo hackathon: a venue booker enters a city and gets a shortlist of touring artists, each with Qloo taste data behind it, and then a promo plan for the artist they pick. What it lacked was everything after the code. Merging a change meant me running tests by hand, and nothing checked licences, scanned dependencies, built an image or kept a record of what shipped. I took the app to GitLab to build that part.
What it does
The app is the same one described on the Qloo entry. The new work on GitLab is the automation around it.
Every merge request and every push to main runs a pipeline of 13 jobs on a merge request and 14 on main, where the manual production deploy is added. Lint, typecheck and unit tests run first. A licence gate checks every dependency against an allow-list. A test integrity check looks for deleted or weakened tests in the diff. Four scanners run: Semgrep SAST, secret detection, dependency scanning and container scanning. The pipeline builds a container image with rootless BuildKit, pushes it to the project registry, starts that image as a service and runs a show check against it. A last job writes a compliance evidence record and a carbon estimate for the run.
Next to the pipeline sit the Duo Agent Platform files: four flows and one chat agent. A grounding guard reviews merge requests that touch the agent or Qloo code and looks for changes that weaken the rule that every artist shown to a user came from a Qloo call in the same run. Two issue intake flows plan an issue and open a merge request, one with approval stops and one without. A pipeline responder reads a pipeline event and fixes a failed merge request pipeline, summarises a release, or opens an incident issue. A Duo configuration file and merge request review instructions are in the repository too.
How I built it
The pipeline is one .gitlab-ci.yml with helper scripts under ci/. Jobs are wired with needs, so the stages only group them for display. A change that touches only documentation skips the build and the scanners, and secret detection still runs. The image build uses BuildKit without root, so it runs on GitLab's hosted runners. The licence gate, the test integrity check, the show check, the failure summary and the evidence record are small Node scripts with their own tests, and the pipeline runs them with the app's tests.
The flows are YAML files for the Duo Agent Platform. I wrote each one from GitLab's documentation, and each file lists which field and tool names the docs confirm and which parts are best effort. Flows that only read have no file or shell tools. AUTONOMY_LEVEL is one pipeline variable with three values. The default is supervised, so deploy_production is a manual job that a person starts. Setting it to hands_off makes that job run by itself on main. The deploy job pushes to Vercel, and a show check then runs the shortlist check against the public site.
I started from the GitHub repository, imported it as a branch, opened two merge requests (the import, then the automation on top of it) and merged them. Later changes from GitHub came in through further merge requests.
Challenges I ran into
Much of the Agent Platform is new, and its documentation does not answer every question. I kept a research file with each fact and its source, and a list of what I could not confirm. The dependency scanner is documented as an Ultimate feature, yet it ran and returned findings. The first pipelines showed that the node_modules cache stayed empty because nothing filled it until the lockfile changed, so the dependency job now also runs when the pipeline file changes. The first production deploy from GitLab also deployed fine but failed its own show check, because the check used the per-deploy vercel.app address, which Vercel protects with a login. Merge request !7 pointed the check at the public alias.
Accomplishments that I'm proud of
Pipeline #2947354926 on main (commit 2a79e48c) passed all 14 jobs and 220 tests. It includes deploy_production, which I started by hand on 14 October at the supervised level, and show_check_production, which passed against https://stagehand-venues.vercel.app. The pipeline graph in the gallery is an earlier main run from before that deploy. The scanners found real issues, which I list under limits, and the evidence record means a reviewer can see what each run checked.
What I learned
A pipeline that does not wait on a manual deploy is easier to trust, because green means checked and not shipped. I also learned to write down which claims I verified. Most of my research notes are about what the docs say, and very little of that is the same as what I have seen run.
Limits (read this part)
- One Duo flow has run, once. On 14 October 2026 at 13:25 UTC I created the grounding guard as a private custom flow in the project (AI > Flows > New flow, the YAML from flows/stagehand-grounding-guard.yml), enabled it with its Mention, Assign and Assign reviewer triggers, and mentioned its service account on merge request !6, a one-line comment change in src/lib/agent/context.ts. Session 9455549 started, finished in about two minutes and posted a GROUNDING-GUARD: PASS note that names the head commit and the file it read. That is one run on a harmless change. I have not seen it catch a real weakening of the validators, and it has not run on the Merge request Created event.
- The other three flows (both issue intake flows and the pipeline responder) and the chat agent are in the repository but have not been created in GitLab or run.
- The production deploy needs a person to start it. It has run once from GitLab and passed, and its first run failed the show check for the reason under Challenges. I have not run it on a schedule or at hands_off.
- Only the supervised level has a pipeline behind it. The hands_off and assisted settings exist as variables and flow files and are untested.
- The scanners reported findings, mostly in development dependencies (tinypool through vitest, postcss, braces). The fixes are major version upgrades that I have not made. The Semgrep results I read point at constant URLs and canvas particles, and I have not dismissed them in the report yet.
- Audience overlap in the app often comes back empty, and small cities may have no Qloo locality. Details are on the Qloo entry.
What's next
Run the other three flows once each on a real merge request and write down what happens. Make the dependency upgrades. Then measure a carbon baseline with a scheduled pipeline, so the estimate has something to compare against.
Built With
- docker
- gitlab
- nextjs
- typescript
- vercel
- vitest

Log in or sign up for Devpost to join the conversation.