Inspiration
Odobook is a voice car logbook. If you drive for work in Australia, the tax office (ATO) wants a 12-week logbook: every trip, odometer readings, and a specific purpose. Odobook lets a driver say "start a work trip to Torquay" to Alexa+ and records it properly. Its records are tax evidence, so a bad release can quietly make a driver's whole logbook invalid, and they won't find out until an audit.
I built it last week, as a Victorian primary teacher working after school. On GitHub, tests ran when I remembered, deploys went out from my laptop, and nothing watched production. That's fine for a weekend project, but not for something people hand to their accountant. I don't have time to babysit releases, so I wanted the agents to do everything after the code is written, and to bring me in only for the decisions that need a human.
What it does
When a change arrives, GitLab Duo agents take it from issue to production. I make three calls along the way: yes to the plan, merge, and ship.
| Stage | What happens | Who |
|---|---|---|
| Plan | Labels each new issue, checks for duplicates, writes acceptance criteria and flags any ATO impact. It marks small, testable changes agent-ready and anything risky needs-human. |
Triage flow |
| Create | Builds the merge request when I move an issue to In progress. | GitLab's Developer flow |
| Verify | Typecheck and unit tests. A failing pipeline gets an incident, or a tested fix pushed to the merge request. | CI, Incident Commander |
| Secure | SAST, secret detection and dependency scanning. A security specialist then triages every finding against Odobook's own OAuth and MCP rules (SEC-1 to SEC-10). | CI, Gatekeeper |
| Govern | An ATO compliance guard: 12 checks that a logbook is still valid ATO and IRS evidence (84-day period, odometer-based business use, refusing "business" as a purpose, an amendments audit trail, no tax advice). A compliance specialist explains any failure in plain English. | CI, Gatekeeper |
| Package | A versioned Worker bundle in the package registry, plus release notes built from the commits. | CI |
| Release | Deploys to staging, then runs the full OAuth and 11-tool end-to-end test, a real-browser account-linking test and a latency gate. It then asks me for one approval with the evidence, the risk and a rollback plan. After I promote, a child pipeline deploys production, checks it and tags a release, and the agent writes release notes for drivers. | CI, Release Captain |
| Configure | Separate staging and production Workers and databases, pinned next to their data. The agents, their skills and their rules (AGENTS.md) are all code in the repo. |
wrangler.jsonc, .gitlab/duo/ |
| Monitor | An hourly read-only check of production against the 500 ms Alexa+ budget. If it fails, the Incident Commander opens an incident. | Schedule, Incident Commander |
Supervised, in practice. The agents can't merge, deploy to production or touch production data. They plan, review, fix, test and stage on their own, and they tell me when it's my turn.
It ran for real: three loops in one day
- Latency. The staging check failed because trip writes from the US (where Alexa+ calls from) took up to 900 ms. The Incident Commander opened #1 with the impact and evidence. I moved it to In progress, and the Developer flow opened !2 to pin the Workers next to the database. The Gatekeeper reviewed it, and I merged. Writes dropped to 250–350 ms.
- The agents caught a bug in their own pipeline. After the first release, the Release Captain pointed out that the release name didn't match the version that was built (its note on !4). A trigger variable was expanding in the child pipeline instead of the parent. Triage planned the fix and flagged it
needs-human, because it touches the release path (#3). The Developer flow fixed it and added a guard that stops a deploy if the version didn't resolve (!5). The next release, v0.2.41, came out right. - Security findings. The Gatekeeper sorted 15 scanner findings into real-but-unreachable and false positives, and suggested a dependency update. Triage planned it (#4). The Developer flow checked that the newest
wranglerstill pins the vulnerableundici, so it used an override for that package and lockfile updates for the other two (!6). Dependency findings dropped from 14 to 1, and the one left is a withdrawn advisory. It shipped as v0.2.52.
Before and after
| Before (GitHub) | After (GitLab) | |
|---|---|---|
| Tests | Run by hand, when I remembered | Every merge request: typecheck, unit tests, 12 ATO checks, three scanners |
| Review | Me, tired, at 10 pm | A compliance specialist and a security specialist, with a verdict on every merge request |
| Deploy | wrangler deploy from my laptop |
Staging automatically, production after one approval, from a child pipeline with a tagged release |
| Proof it works | "It worked on my machine" | End-to-end, browser and latency checks on staging; smoke checks on production |
| Production | Nothing watching | Hourly health check; an incident opened automatically |
| Release notes | None | Written for drivers in plain English by the Release Captain |
How we built it
Four custom flows in the AI Catalog, defined as YAML in .gitlab/duo/flows/ and published by scripts/sync-flows.mjs:
- Triage: a single agent, triggered on new issues.
- Gatekeeper: a supervisor with two subagents (
compliance_reviewer,security_reviewer). It posts one verdict per merge request and is triggered when a merge request is opened or marked ready. - Release Captain: triggered by pipeline events. It decides which moment it's in: the main pipeline waiting on the manual promote job (approval), or the production child pipeline succeeding (shipped). It reads the evidence from job logs, and it ignores scheduled and merge request pipelines.
- Incident Commander: a dispatcher with
incident_responderandpipeline_fixer. It only looks at the failed jobs of the pipeline it was called for, stops if a job is already being retried, posts a retry note instead of an incident for infrastructure errors, and reuses an incident instead of duplicating it.- GitLab's Developer flow does the Create stage, triggered when a triaged issue moves to In progress.
- Skills in the repo hold the domain rules the agents check against:
ato-compliance-review(rules ATO-1 to ATO-10, IRS-1, SCOPE),oauth-mcp-security-review(SEC-1 to SEC-10),odobook-releaseandodobook-incident.AGENTS.mdsets the hard lines: never weaken the compliance guard, production is a human decision, never deploy the Alexa+ demo Worker, no tax advice. - The flow runner (
.gitlab/duo/agent-config.yml) runs on Node 22, cachesnode_modules, and has a network policy that allows only npm and the two Odobook hosts. - CI/CD (
.gitlab-ci.yml), in stages: verify, test, govern, package, staging, validate, production and monitor.
- GitLab's SAST, secret detection and dependency scanning templates, with JUnit reports.
- A generic package for each version.
- The production child pipeline (
.gitlab/ci/production.yml) sits behind a manual trigger job. The main pipeline still reports Passed, so the Release Captain hears about it, and production stays a one-click human decision. - The
releasekeyword tags the release and attaches the bundle. - A pipeline schedule runs the hourly monitor.
- The app: a Cloudflare Worker in TypeScript, acting as both an MCP server (11 tools, Streamable HTTP, MCP 2025-11-25) and an OAuth 2.1 authorization server (PKCE S256, RFC 8707/9728/8414, pre-registered clients), with D1 and KV. Staging and production are separate Workers with separate databases, both pinned to Sydney next to D1.
- Built with Claude Code as a pair programmer, and with the GitLab Duo agents doing the post-code work.
Challenges we ran into
- Flows only start on human actions. A merge request opened by an agent doesn't start the Gatekeeper until a person marks it ready. I turned that into the design: "mark ready" and "In progress" are the moments a human agrees to let the agents carry on.
- A blocked pipeline never reports Passed. With a manual production job that had to be clicked (
allow_failure: false), the main pipeline sat at "blocked", so the Release Captain never heard staging had passed. Moving production into a child pipeline behind an optional manual trigger solved it. - The agents found a real latency problem I couldn't see from Australia. From Melbourne every call took under 160 ms, but from the US runner, writes took up to 900 ms, because Smart Placement ran the Worker in the US while the database is in Sydney. Pinning the Workers next to D1 fixed it. A p95 gate (under 500 ms, none over 1 s) then stopped one-in-twenty network noise from blocking releases, while still catching a real regression.
- Keeping agents on the right evidence. Early on, the Incident Commander opened a second incident about a failure that was already fixed. Its prompt now limits it to the pipeline it was called for, checks for retries and reuses an open incident. The agents also couldn't read binary artifacts, so the reports are printed into job logs.
- A variable that expanded in the wrong pipeline.
RELEASE_VERSION: $VERSIONreached the child pipeline unexpanded, so the child filled in its own pipeline number. The Release Captain spotted the mismatch, and the agents fixed it.
Accomplishments that we're proud of
- All nine stages are covered (Plan, Create, Verify, Secure, Govern, Package, Release, Configure, Monitor), each by real automation that ran on real changes.
- Three full loops in one day, from issue to production, each with a human approving only the plan, the merge and the release.
- The agents caught and fixed a bug in their own release pipeline, and the fix includes a guard so that kind of bug can't ship quietly again.
- The compliance guard is domain-specific. It checks what makes a logbook valid tax evidence, not just whether the code compiles.
- All 13 real dependency findings fixed, with written reasons for the two false positives left.
What we learned
- Put the rules where agents can read them. Skills with rule IDs (ATO-5, SEC-3) made the reviews specific and checkable, instead of "looks fine to me".
- The human checkpoints are worth designing on purpose. "In progress", "mark ready" and "promote" are natural places for a person to say yes, and everything between them can be the agents' job.
- An agent's honesty is a feature. The best notes were the ones that said what wasn't tested ("the real child pipeline has not been tested"), or that the merge request had already been merged before the review.
- Measure from where your users are. The latency problem only showed from the US, and the US is where Alexa+ calls from.
What's next for Odobook: agents carry every release
- Let the Incident Commander roll back automatically when the hourly check fails twice, then ask a human afterwards.
- A scheduled Renovate-style flow that opens dependency update merge requests on its own.
- Use the same release path for the Odobook iOS app.
- Submit Odobook as a real Alexa+ add-on when the toolkit opens to developers.
Built With
- cloudflare-d1
- cloudflare-kv
- cloudflare-workers
- gitlab
- gitlab-ci
- gitlab-duo
- gitlab-duo-agent-platform
- model-context-protocol
- node.js
- oauth2
- playwright
- typescript
Log in or sign up for Devpost to join the conversation.