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 testComponents
| Where | What it does | |
|---|---|---|
| Control plane | apps/web | Next.js app: UI, API, auth (GitHub, Google, magic link, email + password, organizations), GitHub App. The only component holding the GitHub App key. |
| Runner | apps/runner | Executes 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. |
| Agent | packages/agent | The agent loop, tools, egress guard, model and browser providers. |
| Jobs | packages/jobs | Runner ↔ control plane protocol (zod schemas) and the shared job lifecycle. |
| Database | packages/db | Drizzle schema and migrations, row-level security helpers, envelope encryption. |
| Queue | Hatchet | Durable tasks, fair scheduling across workspaces, cron. |
| Browsers | Steel or Chromium | Driven with Playwright over CDP. Steel also provides the live view. |
Flow of a run
- Dispatch. The control plane checks the target: a private or
localhosthost 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). - 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.
- 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_findingandfinish. 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. - 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.