qaitai docs

Architecture

How a run flows from the control plane to runners, browsers and back.

 browser ──▶ web (Next.js control plane) ──▶ Postgres (row-level security)
              │  create run: validate target, budgets, insert run + workers

              ├─ cloud runs ──▶ Hatchet ──▶ platform runner / one short-lived machine per worker

              └─ self-hosted runs wait for the workspace's runners

                    │ HTTPS only: enroll · heartbeat · jobs/next · claim · events · complete
                  remote runner (your network: QAITAI_URL + QAITAI_TOKEN, nothing else)

 every runner: agent loop ──▶ model API · browser over CDP (Steel / Chromium) · app under test

Components

WhereWhat it does
Control planeapps/webNext.js app: UI, API, auth (GitHub, Google, magic link, email + password, organizations), GitHub App. The only component holding the GitHub App key.
Runnerapps/runnerExecutes runs. Platform mode: a Hatchet worker with direct, RLS-bound database access inside the instance. Remote mode: HTTPS to the control plane only. Job mode (qaitai Cloud): one cloud job on an ephemeral machine.
Agentpackages/agentThe agent loop, tools, egress guard, model and browser providers.
Jobspackages/jobsRunner ↔ control plane protocol (zod schemas) and the shared job lifecycle.
Databasepackages/dbDrizzle schema and migrations, row-level security helpers, envelope encryption.
QueueHatchetDurable tasks, fair scheduling across workspaces, cron.
BrowsersSteel or ChromiumDriven with Playwright over CDP. Steel also provides the live view.

Flow of a run

  1. Dispatch. The control plane checks the target: a private or localhost host requires the self-hosted runtime, and a public host on the cloud runtime must be a verified domain. Then the plan's concurrency limit, then one transaction inserts the run, one row per worker and an audit entry. Each worker gets a different focus (happy path, invalid input, navigation edge cases, mobile and keyboard, boundary values).
  2. Execute. Both runner modes share one executor. The job spec carries the prompt, target, limits, egress policy, the secrets the prompt references and the model credentials. A platform runner builds it from the database; a remote runner receives it when it claims the job.
  3. Agent loop. A tool-calling agent with a small, fixed tool set: observe (URL, accessibility snapshot, downscaled screenshot), navigate, click / type / select_option (by ARIA role and accessible name), fill_secret, press, report_finding and finish. Old observations are pruned in batches so the cached prompt prefix stays stable; input tokens are about 95% of a run's cost. Every step and finding is written as it happens, which is what the live run view streams.
  4. Finish. The worker records usage (agent-minutes weighted by model and key mode). A passing suite run stores its recorded actions for deterministic replay. When the last worker finishes, the run is finalized and the GitHub check is posted.

Limits on every run

max_steps (default 60) and max_minutes (default 20) per worker, a 45-minute task timeout with no automatic retries (agent runs aren't idempotent), fair scheduling across workspaces, and plan concurrency caps at dispatch.

The full internal write-up, with file references, is docs/architecture.md.

On this page