cd /news/artificial-intelligence/veta-ai-agent-that-qa-tests-android-… Β· home β€Ί topics β€Ί artificial-intelligence β€Ί article
[ARTICLE Β· art-62170] src=github.com β†— pub= topic=artificial-intelligence verified=true sentiment=↑ positive

Veta: AI agent that QA-tests Android apps

Veta, an AI agent that QA-tests Android apps using a swarm of autonomous sub-agents, runs 100% of its AI inference on AMD GPUs via Fireworks AI or self-hosted vLLM on ROCm. The system replaces scripted UI tests with AI-driven visual, functional, and accessibility testing, scaling from 10 to 100 concurrent sessions on Kubernetes.

read17 min views1 publishedJul 16, 2026
Veta: AI agent that QA-tests Android apps
Image: source

Autonomous AI agent swarm for visual, functional, and accessibility testing of Android apps and mobile web β€” 100% of AI inference runs on AMD GPUs (Fireworks AI on AMD Instinct / self-hosted vLLM on ROCm), with containerized Android instances running locally.

Overview β€’ Features β€’ Architecture β€’ How it works β€’ API β€’ Getting started β€’ Project structure β€’ Tech stack

Built for theNo benchmarks, no constraints, just build: give an AI model eyes and hands, and let it QA your Android apps.[AMD Developer Hackathon ACT II]β€” Unicorn Track.

Veta replaces brittle, scripted UI tests with an AI-driven QA agent that watches an Android screen, decides what to do next based on a plain-English task description, and executes actions via ADB. Each run produces a pass/fail verdict, a complete action trace, and video/log artifacts β€” the same output a human QA tester would produce, at machine speed.

Sessions run in parallel across a fleet of containerized Android instances (redroid), managed by a scheduler with a warm pool and autoscaling. A web dashboard lets humans launch sessions, watch live screen mirrors, and review results.

Input type Description
APK
Upload an Android APK, install it on a clean Android instance, and run a task against the native app
Web domain
Provide a URL to test inside a sandboxed mobile browser, with optional login credentials
Context file
Attach .txt , .md , .docx , or .pdf checklist alongside the task β€” contents are folded into the agent's working prompt at runtime

The system starts at ~10 concurrent sessions on a single host and scales to ~100 concurrent sessions on Kubernetes.

Every single model call β€” planning, execution, verification, reporting β€” runs on AMD GPUs. Zero inference happens on non-AMD hardware by default.

Path What runs on AMD Hardware
Fireworks AI (default)
Planner, executor, verifier, reporter AMD Instinct MI250/MI300X
Self-hosted vLLM + ROCm
Same pipeline, your hardware Any AMD GPU via ROCm

The agent has no fallback to non-AMD inference providers. Gemini, OpenRouter, and Anthropic are listed as optional alternatives in the settings UI β€” none are enabled or used by default. Out of the box, 100% of AI compute is AMD.

Sub-agent architectureβ€” Planner creates a checklist before execution. Executor drives per-step decisions. Verifier reviews the verdict before finalizing. Reporter writes a structured post-run analysis. Each is independently promptable and swappable.Fixed action vocabularyβ€”tap

,long_press

,swipe

,scroll

,drag

,type

,type_multi

,key

,back

,dismiss_keyboard

,wait

,assert

,done

,request_manual_control

,note

. The model never emits raw shell commands.Ground-truth screen trackingβ€” Per-stepscreen_changed

flag from hierarchy hash comparison, plus Android activity name monitoring viadumpsys

. The model knows "which screen am I on" independently of visual appearance.Toast interceptionβ€” Post-action polling of the accessibility hierarchy catches system Toasts (~2-3.5s window) with code-level certainty, not visual guesswork.Loop detectionβ€” Tracks repeated identical actions with noscreen_changed

effect; auto-insertsback

after 3 repeats to break stuck states.Verification gateβ€” Before accepting adone

verdict, a second model pass (verifier) checks every planned checkpoint against the action history. Can send the session back for more exploration if evidence is thin.Post-run analysisβ€” Reporter sub-agent generates a structured write-up: executive summary, checkpoint breakdown, technical explanation, severity rating, remediation recommendations. Available via API and PDF/CSV export.Containerized Android fleetβ€” Redroid containers with per-container binder device slots, health checks, warm pool management, and clean-state teardown between sessions.Real-time streamingβ€” WebSocket for log entries, step records, status changes, and live device screen mirror during a running session. Degrades to polling fallback if WebSocket fails.Step replay playerβ€” Focus view and filmstrip view with touch marker animation, screenshot browsing, and auto-play.** Export**β€” PDF report (jsPDF with professional layout, verdict, metadata, checkpoint breakdown, steps table, logs table) and CSV log export.Session controlsβ€” End (stop + cancel) and restart running sessions from the UI.** Device identity profiles**β€” Pixel 7 Pro, Pixel 6, Samsung S24, and more β€” spoofed via ro.build properties for realistic testing.** 100% AMD-powered AI**β€” Fireworks AI on AMD Instinct (default) or self-hosted vLLM on AMD ROCm. Everycall_model_json()

in the agent pipeline hits AMD GPUs.

                      +------------------+
      People / CI --> |  New Session     |  APK / URL + task description
                      |  Input Form      |--------------------------------+
                      +------------------+                                |
                                                                          v
                      +------------------+                      +-------------------+
                      |  Fleet Dashboard |  <--------------->   |  Session          |
                      |  (live overview) |                      |  Scheduler        |
                      +------------------+                      +--------+----------+
                                                                          |
                      +-----------------------------------------------------+------------------------------------------+
                      v                          v                          v                                          v
               redroid container           redroid container           redroid container                    ... up to ~100
               + agent sidecar             + agent sidecar             + agent sidecar
               (observe->decide->act)      (observe->decide->act)      (observe->decide->act)
                      |                          |                          |
                      +---------> Object storage (screenshots, step JPEGs, logs)
                      +---------> Results DB (Postgres β€” sessions, steps, log entries, analyses, devices, settings, users)
Component Responsibility
New Session form
Accept APK upload or domain URL, task description, device/browser profile, optional context file
Fleet Dashboard
Live overview of every session with status bar, search, filter, sort, pagination
Session Detail
Live screen mirror + structured log stream + replay player + AI analysis panel
Session Scheduler
Dispatch loop (2s tick) + fleet loop (10s tick): queued sessions -> reserve/boot device -> dispatch agent
Android container (redroid)
One isolated Android 11 instance per session via Docker, with per-container binder device slots
Agent sidecar
Observe -> decide -> act loop. Sub-agents: planner, executor, verifier, reporter
Device Manager
Container lifecycle: provision, deploy, health-check, teardown, warm pool maintenance
Postgres DB
Source of truth for session metadata, steps, log entries, device state, session analyses, settings, users
File storage
Per-step replay screenshots (downscaled JPEG), uploaded APKs, stored on local filesystem

Each session runs a single sidecar process that cycles through five steps:

Observeβ€” Capture a screenshot (adb screencap

via adbutils) and the UI element hierarchy (uiautomator dump

parsed into a flat element list with bounds, text, content-desc, and class).Decideβ€” Send image + hierarchy + task description + action history to the AI model. The model returns one structured action from the fixed vocabulary.Actβ€” Translate the action into ADB calls:input tap

,input swipe

,input text

,input keyevent

, etc.Settleβ€” Poll for system Toasts in the ~1s post-action window, then capture a fresh screenshot for the next step.** Ground truth check**β€” Per-step hierarchy hash detects whether the screen actually changed; activity name tracking detects navigation independently of visual appearance.

The model must explicitly emit assert

(expected vs. actual comparison) and done

(pass/fail verdict with reasoning) β€” it cannot wander indefinitely.

Task + screenshot
    |
    v
+------------------+
|  Planner         |  Creates 3-6 concrete checkpoints from task + screenshot (+ APK manifest activities)
|  (once)          |  Degrades gracefully to empty list -> executor/verifier fall back to free-form task
+--------+---------+
         | plan checklist
         v
+------------------+
|  Executor        |  Per-step: screenshot + hierarchy + history -> one action
|  (per step)      |  Retries on bad generation (3 attempts). Loop detection. Activity tracking.
+--------+---------+
         | "done" with proposed verdict
         v
+------------------+
|  Verifier        |  Checkpoint-by-checkpoint review against action history + final screenshot
|  (once)          |  Can set should_continue=true -> executor keeps going. Falls back to executor's verdict.
+--------+---------+
         | final verdict
         v
+------------------+
|  Reporter        |  Post-verdict write-up: summary, breakdown, technical explanation, severity, recommendations
|  (once)          |  Persisted to DB. Serves Analysis tab + PDF export. Degrades to checkpoint-derived fallback.
+------------------+
  • User submits an APK (or URL), task description, and optional context file through the New Session form.
  • API creates a queued

session row with sequential ID (e.g.S9403

). - Session Scheduler's dispatch loop (every 2s) picks up queued sessions up to the parallelism limit.

  • Scheduler reserves an idle device or cold-provisions one (binds a port + binder slot, boots redroid container, waits for adb + sys.boot_completed

). - Session transitions booting

->initializing

->running

. The agent sidecar installs the APK (if applicable), runs the planner, then begins the observe-decide-act loop. - When the executor emits done

, the verifier reviews the evidence. If it passes, the session transitions toanalyzing

(device is released immediately), the reporter generates the write-up, then the session settles intopassed

orfailed

. - Artifacts (step screenshots, action log, analysis) are persisted. The container is torn down. The fleet loop (every 10s) re-deploys the device for the warm pool.

  • The Fleet Dashboard and Session Detail reflect the final verdict and analysis in real time.
Status Meaning
queued
Waiting for an available device slot
booting
Android container is starting up
initializing
Installing APK / preparing agent
running
Agent loop is actively executing the task
awaiting_questions
Review mode: waiting for user answers to clarifying questions
awaiting_approval
Review mode: waiting for user to approve the generated test plan
analyzing
Verdict reached; reporter generating write-up
passed
Task completed successfully
failed
Task completed but assertions failed
stopped
Manually stopped by a user
error
Infrastructure or system error
timed_out
Agent exceeded max steps (50)
  • Each redroid container runs Android 11 (1080x2400, 480dpi) with nav bar + soft keyboard disabled via policy_control

andpm disable-user

. - Devices are identified by D-{hex}

ID, assigned a host port (5555-5655 range) and abinder slot(binder/hwbinder/vndbinder triplet) for the kernel's binder driver β€” a hard cap set atmodprobe

time on the host. - Warm pool: idle, healthy devices maintained up to a user-configurable target. Devices in error

state are reaped after 30s backoff. - Health checks run every 10s: container running + sys.boot_completed

. - Device identity profiles (Pixel 7 Pro / Pixel 6 / Samsung S24) are applied via ro.build

property overrides before any app touches the device.

The backend is a Python FastAPI server at /api

. Interactive docs at http://localhost:8000/docs

.

Method Path Purpose
POST
/api/sessions
Create a new session (multipart form: APK file or URL + task + device profile)
GET
/api/sessions
List sessions (cursor-paginated, filterable by status/device/build/text search)
GET
/api/sessions/stats
Dashboard stats (running/queued/passed/failed counts + success rate)
GET
/api/sessions/{id}
Get single session details
PATCH
/api/sessions/{id}/control
Control session (action: end or restart )
POST
/api/sessions/{id}/answers
Submit answers to review clarifying questions
POST
/api/sessions/{id}/approve
Approve review-mode test plan and queue session
POST
/api/sessions/{id}/regenerate-plan
Regenerate test plan from existing answers
POST
/api/sessions/{id}/back-to-questions
Go back to clarifying questions from plan review
GET
/api/sessions/{id}/logs
Get log entries with cursor (since )
POST
/api/sessions/{id}/logs
Create a log entry
GET
/api/sessions/{id}/steps
Get replay steps with cursor (since_step )
GET
/api/sessions/{id}/steps/{n}/screenshot
Get step screenshot (JPEG)
GET
/api/sessions/{id}/analysis
Get session analysis write-up
POST
/api/sessions/{id}/analysis/regenerate
Regenerate analysis for a finished session
WS
/api/sessions/{id}/events
WebSocket β€” real-time log/step/status deltas
WS
/api/sessions/{id}/stream
WebSocket β€” live screen frame stream (PNG)
Method Path Purpose
GET
/api/devices
List all devices
POST
/api/devices
Provision a new device (boots redroid container)
GET
/api/devices/capacity
Binder slot usage (total/used/available)
POST
/api/devices/{id}/start
Start an offline device
POST
/api/devices/{id}/stop
Stop a running device
POST
/api/devices/{id}/restart
Restart a device (re-deploy container)
POST
/api/devices/{id}/maintenance
Toggle maintenance mode
POST
/api/devices/{id}/warm
Adjust warm instance count (delta -8 to +8)
DELETE
/api/devices/{id}
Remove device (tear down container, free port/slot)
Method Path Purpose
POST
/api/auth/login
Login (email + password)
POST
/api/auth/signup
Register new user
POST
/api/auth/logout
Logout
Method Path Purpose
GET
/api/settings
Get app settings (workspace, region, notifications, parallelism, API key, warm pool size, max devices)
PUT
/api/settings
Update app settings
  • Docker Engine and Docker Compose (for Postgres and redroid containers)
  • Python 3.11+
  • Node.js 20+
  • An AI provider API key (Fireworks AI on AMD Instinct recommended, or any OpenAI-compatible provider)

Development (hot reload):

./dev.sh

Production (built frontend, no reload):

./start.sh

Both scripts start Postgres (if not running), run migrations, and launch the full stack. Ctrl-C

stops everything.

cd backend
cp .env.example .env

Edit .env

to set your AI_API_KEY

and any other settings.

docker compose up -d

Or manually:

docker run -d --name veta-pg \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=veta \
  -p 5432:5432 \
  postgres:16-alpine
cd backend
pip install -e .
alembic upgrade head
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

The API starts at http://localhost:8000

. Docs at http://localhost:8000/docs

.

On first boot, the server auto-seeds admin user (

admin

/admin

) and default settings.

cd frontend
npm install
npm run dev

The dashboard starts at http://localhost:8080

. Login with any email/password.

Set

VITE_API_URL

to change the backend address (defaulthttp://localhost:8000

).

sudo modprobe binder_linux devices="binder,hwbinder,vndbinder"


adb devices

export REDROID_IMAGE=redroid/redroid:11.0.0-latest
Variable Default Description
DATABASE_URL
postgresql+asyncpg://postgres:postgres@localhost:5432/veta
Postgres connection string
REDROID_IMAGE
redroid/redroid:11.0.0-latest
Android container image
ADB_HOST
localhost
ADB host address
REDROID_PORT_START
5555
Start of port range for device ADB
REDROID_PORT_END
5655
End of port range (up to ~100 devices)
BINDER_SLOTS
auto-detected from /dev
Override for binder slot count
AI_API_KEY
-- AI provider API key (also configurable via Settings UI)
AI_PROVIDER
fireworks
AI provider: fireworks , veta-ai , google-gemini , openrouter , deepseek , anthropic
AI_MODEL
provider default Model name override
MODEL_ENDPOINT
provider default API base URL override
veta/
  Docs/                        Design documents, wireframes, logo
  server/                      vLLM inference server (AMD ROCm)
    veta_inference_server.ipynb  Notebook: serve vision models on AMD GPUs via vLLM
    bin/                         cloudflared binary for tunnel

  backend/                     FastAPI backend API
    app/
      main.py                  FastAPI entrypoint + lifespan (auto-seed, stale cleanup, dispatch + fleet loops)
      config.py                pydantic-settings (.env / env vars)
      database.py              Async SQLAlchemy engine + session factory
      dispatcher.py            Session dispatch loop + fleet health loop
      device_manager.py        Redroid container lifecycle, port/binder allocation, warm pool, health checks
      device_events.py         SSE device state change notifications
      apk_utils.py             APK metadata extraction (aapt + apkutils)
      screenshot_store.py      Per-step screenshot downscale + JPEG storage
      models/                  SQLAlchemy ORM models (session, step, log_entry, device, settings, session_analysis, user)
      schemas/                 Pydantic request/response schemas (camelCase JSON)
      routers/                 REST + WebSocket endpoint handlers (sessions, steps, logs, devices, auth, settings, stream)
      services/
        session_analysis.py    Build + store session analysis (bridges reporter sub-agent to DB)
    adapters/                  Port implementations (ApkInspectorImpl, etc.)
    alembic/                   Database migrations
    scripts/
      seed.py                  Seed admin user + default settings
    uploads/                   Uploaded APKs + step screenshot storage

  agent/                       AI agent sidecar
    agents/
      base.py                  Shared retry logic (generate_with_retries)
      planner.py               Pre-execution checkpoint builder
      executor.py              Per-step action decision
      verifier.py              Post-decision verification
      reporter.py              Post-verdict write-up
    core/
      adb.py                   adbutils device handle
      device.py                Screenshots, action execution, marker extraction, activity tracking
      hierarchy.py             XML hierarchy parsing, element signature hashing
      model_client.py          Multi-provider AI client (Gemini SDK + OpenAI-compat) with retry + JSON extraction
      apk.py                   APK metadata extraction, install/launch on device
      cancellation.py          Thread-safe session cancellation
      thinking_hooks.py        Real-time thinking indicator callbacks
      modes.py                 Mode configs (review/run) + allowed action groups
      skill_text.py            Loads agent playbook into system prompt
    ports/                     Protocol interfaces for dependency injection
      apk_inspector.py         ApkInspector protocol (get_all_activities, get_apk_details)
    skills/                    Agent playbook (loaded into system context)
      SKILL.md                 Master playbook β€” decision loop, confidence/targeting rules, verdict discipline
      actions.md               Full action vocabulary with semantics for each action type
      elements.md              UI element taxonomy β€” how to recognize and interact with different UI components
      scenarios.md             Step-by-step playbooks for authentication, permissions, onboarding, forms, search
      debugging.md             Recovery strategies for stuck states, loop detection, unexpected screens
    memory.py                  SessionMemory β€” action history, loop detection, activity tracking, screen stack
    orchestrator.py            Session state machine wiring all sub-agents together (booting -> ... -> passed/failed)
    phases.py                  Phase-based lifecycle (SetupPhase, ReviewPhase, RunPhase)
    runner.py                  Compatibility entrypoint (delegates to orchestrator)

  frontend/                    TanStack Start web dashboard
    src/
      routes/                  File-based routing
        __root.tsx             Root layout + sidebar + auth
        index.tsx              Fleet Dashboard (stat bar, search, filter, sort, pagination, session cards)
        session.$id.tsx        Session Detail (live mirror, log stream, replay player, analysis panel, export, filmstrip)
        new-session.tsx        New Session form (APK/URL toggle, file drag-drop, context attachment, device profile)
        devices.tsx            Device Farm (card grid, provision dialog, warm pool controls, binder tracking)
        history.tsx            Run History (table view, sort, filter)
        settings.tsx           Settings (workspace, execution, API key, notifications, integrations)
        login.tsx / signup.tsx Auth pages
      components/
        session-card.tsx       Fleet dashboard session card
        session-analysis-panel.tsx  Analysis tab UI
        ui/                    shadcn/ui primitives (50+ components)
      lib/
        api.ts                 API client (fetch + WebSocket wrappers, all endpoints)
        auth.ts                Session storage auth
        session-report.ts      PDF + CSV export (jsPDF with professional layout)
    public/                    Static assets (favicon, logo)

Located in agent/agents/

. Each is independently promptable and can be retried or swapped without affecting the others.

Agent File Purpose
planner
planner.py
Builds 3-6 concrete checkpoints from task + first screenshot (+ APK manifest). Runs once before execution.
executor
executor.py
Per-step decision: screenshot + hierarchy + history -> one action. Retries on bad generation (3 attempts).
verifier
verifier.py
Checkpoint-by-checkpoint review against action history. Can send session back for more exploration.
reporter
reporter.py
Post-verdict structured write-up. Falls back to checkpoint-derived summary on model failure.

Located in agent/core/

.

Module Purpose
device
Screenshots, action execution dispatch, activity tracking, marker extraction
hierarchy
UiAutomator hierarchy dump + parsing + element signature hashing
model_client
Multi-provider HTTP client (Gemini SDK + OpenAI-compat, Fireworks on AMD Instinct), JSON extraction, retry logic
apk
APK install, package discovery, app launch on device
adb
Shared adbutils device handle
cancellation
Thread-safe session cancellation registry
modes
Mode configs (review/run) + action group definitions
skill_text
Agent playbook loaded into the executor's system prompt
Provider SDK Backend hardware Models Status
Fireworks AI
openai
AMD Instinct MI250/MI300X
qwen3.7-plus , firefunction-v2 , Gemma 4
βœ… Default (100% AMD)
Veta AI (self-hosted)
openai
AMD ROCm (vLLM)
Any HF vision model (Qwen2.5-VL, Gemma, Llama) βœ… 100% AMD
Google Gemini
google-genai
Google TPU gemma-4-26b-a4b-it
⬜ Optional
OpenRouter
openai
Varies openai/gpt-4o-mini , any routed model
⬜ Optional
Deepseek
openai
Varies deepseek-v4-flash
⬜ Optional
Anthropic
openai
Varies Anthropic models via OpenAI-compat endpoint ⬜ Optional

100% of inference runs on AMD out of the box.The two AMD-backed providers (Fireworks + self-hosted ROCm) handle every agent call. Gemini, OpenRouter, Deepseek, and Anthropic are optional alternatives β€” none are enabled by default.

The agent playbook (agent/skills/

) is loaded into the executor's system context:

File Purpose
SKILL.md
Master playbook β€” decision loop, confidence/targeting rules, verdict discipline
actions.md
Full action vocabulary with semantics for each action type
elements.md
UI element taxonomy β€” recognizing and interacting with buttons, sliders, toggles, dropdowns, dialogs
scenarios.md
Step-by-step playbooks for authentication, permissions, onboarding, forms, search
debugging.md
Recovery strategies for stuck states, loop detection, unexpected screens
Layer Technology
Backend
Python 3.11+, FastAPI, SQLAlchemy (async), Alembic, PostgreSQL
Agent loop
Python, httpx, Pillow, google-genai SDK, openai SDK
Frontend
React 19, TanStack Start, TanStack Router, Tailwind CSS v4, shadcn/ui
AI inference (cloud, default)
Fireworks AI β€” AMD Instinct MI250/MI300X (100% AMD)
AI inference (self-hosted)
vLLM + AMD ROCm (100% AMD)
Android runtime
Docker, redroid (Android 11), ADB, adbutils, uiautomator
Infrastructure
Docker, Docker Compose (dev), Kubernetes (target)
Real-time
WebSocket (live frame stream + events bus), SSE fallback
Export
jsPDF + jspdf-autotable (PDF), CSV
Approach Cost per session (~50 steps) When to use
Fireworks API (AMD Instinct)
~$0.02-0.05 Development, low-volume QA
Self-hosted vLLM on ROCm
GPU electricity only Production, high-volume CI/CD
Gemini CachedContent
~$0.01 (95% cached) Optional fallback / multi-provider
── more in #artificial-intelligence 4 stories Β· sorted by recency
── more on @veta 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/veta-ai-agent-that-q…] indexed:0 read:17min 2026-07-16 Β· β€”