Inspiration
Mobile QA is ping pong:
- a tester sends a screenshot that says "this looks off";
- the developer asks which build, which device, which steps;
- the fix lands, and nobody tells the tester which build has it.
About 17% of bug reports end as "can't reproduce". Then release day brings a Play rejection nobody checked for. Capture SDKs stop at the ticket and AI fixers stop at the merge request. Nobody puts the fixed build back in the hands of the person who found the bug.
What it does
1. The QA loop, from the tester's phone back to the tester's phone.
- Capture. The tester long-presses what looks wrong in the QA build. The Android SDK boxes the element on a redacted screenshot and adds the device, the exact build, the screen's deep link and recent logs.
- File. The Bugoro Hub files a GitLab issue as the tester (their own OAuth token) and assigns our
qa-fixDuo flow. A human filed it, so the Assign trigger starts the flow. - Fix. Triage reads the report as data and answers one word: fix, ask, duplicate, already fixed, or hand off. On fix, the fixer makes the smallest UI change and opens an MR with "What to retest".
- Build and screenshot. CI builds the QA APK into GitLab's package registry. Our emulator farm opens the reported screen on the main build and the fix build: Before and After.
- Return trip. The tester's app shows "Fix ready: Install build #N". They install it, check it, and tap Verify, which signs off on the issue in their name ("Verified on build #N") and turns on auto-merge, pinned to the commit they tested.
2. Store check before release. The store-check CI component builds the release bundle and runs Android lint, Greenlight and 13 Bugoro Play rules (restricted permissions traced to the library that merged them in, privacy policy URL, placeholder text, target SDK, 16 KB pages and more). It fails release pipelines on critical findings and feeds GitLab's Code Quality widget. The release-readiness flow explains the findings and opens a separate MR with only the safe fixes.
3. Size guard. The size-guard component measures what a phone actually downloads (bundletool) on every MR and tracks the trend on the Hub's public dashboard. The size-fix flow deletes resources it can prove unused and turns on R8.
How we built it
- One MIT monorepo: a Kotlin Android SDK (Compose, OkHttp, WorkManager) with a no-op release artifact; a Compose sample app with seeded UI bugs; the Hub (Rails 8.1, SQLite, Solid Queue, Hotwire, Tailwind); a FastAPI emulator farm on KVM.
- GitLab Duo Agent Platform: three custom flows (
qa-fix,release-readiness,size-fix), published Public in the AI Catalog with their own service accounts and triggers (Assign, Mention). They route on one exact word, and anything else falls to handoff. - Cost limits per run: a tool budget per agent, capped paging, the smaller model for the four notes-only agents, no repository clone (API tools only), at most 2 fix attempts and 2 questions per report, and a daily start budget in the Hub. By our estimate a typical fix run is about 10 credits, and no single session can run past about 30.
- GitLab CI/CD: four components (
qa-build,device-shots,store-check,size-guard) on GitLab's hosted runners. Jobs reach our private emulator farm only through the Hub's relay, authenticated with GitLab CI ID tokens (OIDC) plus a farm token. - Measured on the live project (Oct 26): about 3 minutes from assigning the flow to the fix MR, and about 10 minutes to emulator Before/After screenshots of the fix build on GitLab's hosted runners. Release Readiness took about 2.5 minutes from a mention to its review note and a separate fix MR with only the safe changes.
- Hosting: the Hub runs in Coolify; the emulator farm runs in Docker on our own KVM server, with CPU, memory and process caps, on a shared production host.
- Trust by construction:
- issues are filed as the real tester, because bots can't fire triggers;
- only testers with the Developer role start flows; anyone else's report waits for a developer;
- the Hub locks each new issue's discussion to project members, so strangers can't feed text to the agent;
- the Hub re-checks every agent MR's diff against a path allowlist before it allows auto-merge;
- a pipeline repair is asked for only on a real script failure, and the agent reads the job log first.
- Tests: 1,126 Hub RSpec examples, 1,250 CI-script and flow pytest tests (the flows are validated against GitLab's own flow schema and prompt filter), 130 farm tests, 266 Kotlin tests, and the deploy scripts' tests. All green on GitLab's hosted runners.
Challenges we ran into
- Composite identity. Inside a flow, GitLab's get_current_user returns the person who started the run, and the flow's MRs and label changes are attributed to that person; only its notes carry the flow's own account. So the flows identify themselves from GitLab's own "started session" note, and the Hub recognizes the agent's MR and decisions from the flow's notes, which nobody else can write.
- No runner rights. The hackathon role can't register a runner, so CI moved to GitLab's hosted runners, and the Hub relays the three farm calls with CI ID-token auth.
- Bots, service accounts and flows can't fire triggers. So the Hub files every issue with the tester's own token, and the return trip runs deterministically in the Hub and CI.
- Credits. Billing is per LLM call, so we budgeted tool calls per agent, capped paging and moved notes-only agents to a smaller model.
- A shared production server. Every Bugoro container is capped and bound to localhost, and a preflight checks the host before each deploy.
Accomplishments that we're proud of
- The loop closes on the device: the person who found the bug confirms the fix on the fixed build, and that tap is the merge gate.
- store-check traces a restricted permission to the library manifest that merged it in.
- Agents that know when to stop: ask, duplicate, already fixed and hand off are first-class outcomes.
What we learned
- Split deterministic work from agentic work: flows decide and edit, CI builds and measures, the Hub keeps the state.
- On a platform where only humans fire triggers, design the human moments on purpose.
- Restraint earns trust: an agent that hands off with a precise note beats one that guesses.
What's next for Bugoro
- An iOS SDK, and push notifications for "Fix ready".
- Crash on launch checks in store check through the farm.
- GitLab's knowledge graph to map a tapped element straight to the code.
Log in or sign up for Devpost to join the conversation.