The local agent — browse guides

The local agent (vera-agent)

Run tests on your own machine against localhost with the vera-agent CLI: install, commands, configuration, environment targeting and troubleshooting.

Run your Vera tests on your own machine while the cloud stays the control plane. vera-agent is a small runner you install locally: it leases queued runs over an authenticated HTTPS API, drives Playwright against your app, uploads the video/trace/screenshots, and reports results back. Vera keeps everything you already use — the dashboard, projects, run history, AI generation and self-heal — but the browser executes next to your app instead of in the cloud.

Why this exists:

  • Test http://localhost with zero tunnels. The browser runs where your app runs, so private/loopback hosts are reachable directly — no ngrok, no exposing a preview URL.
  • Your machine, your setup. Setup command fixtures (seed scripts, fixture users) run on the agent path because it's your own machine; the same actions stay disabled on shared cloud workers.
  • No database credentials on the agent — ever. The agent never holds DATABASE_URL. Every persistence touch (run rows, progress frames, artifact keys) is server-mediated over the token-authenticated /api/agent/v1 API. The agent is org-trusted but platform-untrusted.

1. Quickstart

The package is published: npx vera-agent <command> works with no checkout, and npm install -g vera-agent gives you the binary permanently. Every command below is written vera-agent on purpose — the package also installs a short vera alias, but npx vera fetches an unrelated package squatted on that name in 2022, so the alias is safe only once vera-agent is installed, never in an npx one-liner.

  1. Mint an enrollment token. In the dashboard, open Settings → Agents and create an agent. The vera_agent_… token is shown once — copy it now. (This is not the same as a vera_… CI Token — see Configuration.)

  2. Enroll this machine:

    npx vera-agent login --url https://app.vera.dev --token vera_agent_xxx

    This validates the token against the server (a /hello handshake) and writes ~/.vera/agent.json with owner-only permissions (chmod 600).

  3. Start leasing runs:

    npx vera-agent start

    The first run auto-installs Playwright Chromium if it's missing. Leave it running; press Ctrl+C to drain and exit.

  4. Point an environment at the agent. On a project environment, set the run target to agent (see Environment targeting). Now hit Run in the dashboard: the run is queued, your agent claims it, and the steps stream live into the run view just like a cloud run.

Want a one-off check right now instead of a standing worker? vera-agent verify runs a test — or a step list that was never saved — in this process against your own localhost, no queue involved. Linking the repo itself (and the coding-agent wiring around it) is vera-agent init.

2. Commands

Execution commands (start, run, watch, verify, status, doctor) read config from ~/.vera/agent.json with environment overrides; init, check and affected are the odd ones out and use .vera/config.json + ~/.vera/cli.json (see Configuration). check --run --local is the one command that needs both: the CLI token to read the map, the enrollment to execute.

CommandWhat it does
init --url <apiUrl> --token <vera_…>Link this repo to a Vera project: writes .vera/config.json (commit it) + the token in ~/.vera/cli.json, registers the vera MCP server, and adds the agent-instructions block to AGENTS.md/CLAUDE.md. Idempotent. Flags: --project <id>, --mcp-scope local|project, --no-mcp, --no-agents-md.
login --url <apiUrl> --token <vera_agent_…>Enroll this machine. Validates against /hello, then writes ~/.vera/agent.json (chmod 600). Omit the flags in a terminal to be prompted interactively.
startLease and execute runs continuously until Ctrl+C (drain). This is the normal mode.
run --test <testId>One-shot: lease and execute a single queued run for that test, then exit with a status code. For scripts/CI-style invocation.
run --drain [--timeout <s>]Lease-execute until the queue is empty or the timeout elapses, then exit by aggregate result. The CI-rider batch mode — see CI-rider.
verify --test <testId>Run a saved test here, now, in this process — no queue, no lease wait. Exit 0 pass / 1 fail / 2 no run.
verify --project <id> --steps <file|->The same for an ad-hoc step list that was never saved. Also --env, --role, --label, --json.
watch <dir> [--tag <t>|--suite <id>]Watch a local dir and re-run tests on save. Defaults to the watch tag. Streams to the dashboard. Ctrl+C to stop.
check [--base <ref>] [--run [--local]] [--strict]Map your git diff onto the tests that cover it, and optionally run exactly that set. --run --local executes here, in this process, so it reaches localhost. Full guide: change impact.
affected <file>The reverse lookup: which tests cover this one file, and which rules it touches.
statusPrint enrollment, connectivity (a live /hello round-trip), and Playwright readiness. Never mutates anything.
doctorDiagnose Node / enrollment / Playwright / the native-mobile toolchain, each line with a fix-it.
install-browsersInstall Playwright Chromium (npx playwright install chromium).

Also available: --help / help, --version / version.

init

vera-agent init --url https://app.vera.dev --token vera_xxxxx

Points the repo you run it in at a Vera project and wires up the coding-agent surface around it. It resolves the project from the repo's origin (GitHub linkage first, then a name match; ambiguous ⇒ it asks for --project <id>, no match ⇒ it links out to the dashboard, because a token cannot create projects), then writes:

  • .vera/config.json{apiUrl, projectId, repoPathPrefix}. Commit it; it deliberately holds no credential.
  • ~/.vera/cli.json — the vera_… token, chmod 600, keyed by apiUrl, so a second repo pointed at the same deployment needs only --url.

…and, unless told otherwise, registers the vera MCP server (--mcp-scope local writes your private ~/.claude.json; --mcp-scope project merges a ${VERA_TOKEN}-based entry into the repo's committed .mcp.json; --no-mcp skips it) and adds a sentinel-guarded instructions block to the repo's AGENTS.md or CLAUDE.md (--no-agents-md skips it). Re-running changes nothing and says so.

The token is a CI Token (vera_…, Settings → CI Tokens) — not the vera_agent_… enrollment token login takes. On cloud it needs the Pro api-access feature; a free workspace's token works on MCP but init will report the token as not accepted. Full walkthrough: Getting started.

login

vera-agent login --url https://app.vera.dev --token vera_agent_xxx
  • --url — your Vera API root (no trailing slash needed).
  • --token — the vera_agent_… enrollment token from Settings → Agents.

If the agent is older than the server's minAgentVersion, login still saves your credentials but warns loudly (enrollment is valid; the agent just needs an upgrade). A bad token or unreachable URL fails without writing anything.

start

Leases work in a 25-second long-poll loop and executes each claimed run with a heartbeat and live progress relay. On SIGINT/SIGTERM it drains: stops leasing, finishes the in-flight run, closes browsers, then exits (a 60-second hard deadline guarantees exit even if a run hangs).

run --test <testId>

Leases until the target test's queued job is claimed, executes it, and exits 0 on pass / 1 on fail. If it happens to claim a different test's job first it executes that one too (leases aren't returnable), then keeps looking. Gives up with a non-zero exit after ~60 seconds of no leasable work.

run --drain [--timeout <s>]

The batch shape for CI: leases and executes queued runs continuously, then exits on its own — when the queue goes quiet (--idle-exits consecutive no-work polls, default 2 ⇒ ~50s) or the --timeout <seconds> wall clock elapses (0/absent = no cap; a run in flight always finishes first). Exit code is the aggregate: 0 all-passed or nothing-queued, 1 any failed, 2 a fatal agent error (auth/handshake). Prints a per-run summary line and a final aggregate line. See the CI-rider guide for the GitHub Action and cost math.

verify --test <id> / verify --project <id> --steps <file|->

The developer-in-front-of-the-app shape: one run, in this process, against your dev server, finishing in seconds. No queue, no lease, no waiting for a worker to pick it up — which is what makes it usable in an edit → check → edit loop, and what makes http://localhost:3000 testable from a cloud control plane.

# an ad-hoc check straight from stdin
echo '[{"action":"goto","url":"/orders"},
       {"action":"assert","selector":"#total","assertType":"text","value":"42 orders"}]' \
  | vera-agent verify --project prj_123 --steps - --role admin

# or replay something already saved
vera-agent verify --test tst_456 --json
  • --steps takes a JSON file, or - for stdin, holding either a bare array or {"steps": [...]} (the shape the run_scenario MCP tool takes). Missing step ids are minted for you — they're run-local identifiers, so hand-writing nanoids is not your job.
  • --steps requires --project (a loose step list has no test to inherit it from). Provide exactly one of --test or --steps.
  • --env <name> / --role <roleId> pick the environment profile and the captured auth role; --label <text> names the run in the feed.
  • --json prints one machine-readable object on stdout and moves every human line to stderr, so it pipes into jq or an agent.
  • Exit codes match run --drain: 0 passed, 1 failed, 2 the run never happened (bad flags, no enrollment, no Chromium).

It is still a real run: the control plane creates the row, receives the step results and artifacts, and counts the invocation — so a local check and a cloud check share one history rather than splitting in two. What runs locally is the browser, not the bookkeeping.

verify needs the enrollment credential (~/.vera/agent.json, written by login), not init's token. If you have only run init, it exits 2 with Cannot verify — agent handshake failed.

watch <dir> [--tag <tag> | --suite <id>] [--debounce <ms>]

The inner-loop mode: watch a local source directory and, on each save, re-run a set of tests against the app running on your machine — results stream to the dashboard in real time (they are ordinary agent runs on the same run_events SSE bus).

# Re-run every test tagged `watch` on save (the default):
vera-agent watch ./src

# Re-run a specific tag, or a suite, with a custom debounce:
vera-agent watch ./src --tag smoke
vera-agent watch . --suite suite_9f2 --debounce 800

How it works on each debounced save-burst:

  1. Debounce — file-change events are coalesced over a quiet window (--debounce, default 500ms), so a save-all across many files fires one trigger, not dozens.
  2. Trigger — the agent calls POST /api/agent/v1/watch-trigger with exactly one of {tag} / {suiteId}. The server resolves the matching tests and enqueues one run per test, pinned to this agent (payload pinnedAgentId), returning {runIds, testCount}. Only this agent can claim those jobs.
  3. Drain — the agent leases and executes its own pinned jobs through the normal loop, printing a compact per-run line, then returns to watching (the process does not exit). Bursts arriving mid-drain are coalesced into one follow-up pass.

Resolution scope (the agent carries no project context, so):

  • --tag matches org-wide — every test in your org carrying that (normalized) tag, across all projects. A watch tag is a deliberate opt-in, so spanning projects is intentional; tag the handful of tests you want your save-loop to run.
  • --suite re-runs the suite's membership (static testIds ∪ its dynamic filters — tags / recently-failed / flaky), exactly like a normal suite run.

Each trigger enqueues at most 50 tests (the tail is dropped) so a broad tag can't flood the queue. Ctrl+C stops the watcher, lets the in-flight run finish, closes browsers, and exits (60-second hard deadline).

Ignored paths: node_modules, .git, dist, and any dotfile/dotdir are never watched. Platform note: recursive watching uses native fs.watch(..., { recursive: true }) on macOS/Windows; on Linux (where recursive mode is unsupported) the agent falls back to per-directory watchers armed across the tree and re-armed when new subdirectories appear.

check / affected

The diff-shaped question rather than the file-shaped one: which tests cover what I just changed? Both read the repo link (.vera/config.json + the vera_… CLI token), ask the server's impact map, and print the tests with the evidence that selected them.

vera-agent check                  # read-only: map and report
vera-agent check --run --local    # …and run exactly that set, HERE
vera-agent affected src/lib/pricing.ts

The local-agent-specific part is --run --local. It executes the impacted tests in this process through the same path as verify — so they reach http://localhost:3000 — which means it needs the enrollment credential on top of the CLI token. Without one it exits 1 and tells you to login; it never falls back to draining the queue, because an empty queue would aggregate to a green that proves nothing. Plain --run (no --local) runs cloud-side instead, regardless of the project's run target.

Everything else — the three joins, what the map cannot see, --strict, the exit codes, the CI shape — is in change impact (vera check).

status

vera-agent 0.4.0

Enrollment:
  config file: /Users/you/.vera/agent.json
  api url:     https://app.vera.dev
  token:       …x9f2
  name:        my-laptop

Connectivity:
  reachable:   yes (protocol v1)
  version:     0.4.0 ≥ min 0.1.0 — OK

Playwright:
  chromium:    installed (/Users/you/Library/Caches/ms-playwright)

doctor

Diagnoses the machine's readiness with a check table and actionable fix-its: Node (≥ 20), enrollment + server reachability, Playwright browsers, and the native-mobile toolchainadb + attached devices, the emulator binary + AVDs, appium + installed drivers (uiautomator2 / xcuitest), and on macOS Xcode + available simulators. Each line reads OK / WARN / FAIL with a one-line fix-it (brew install --cask android-platform-tools, npm i -g appium, appium driver install uiautomator2, …).

vera-agent 0.4.0 — doctor

[OK  ] Node          v20.x
[OK  ] Enrollment    https://app.vera.dev (token …x9f2)
[OK  ] Server        reachable — protocol v1, min agent 0.1.0
[WARN] Playwright    Chromium not found
                     ↳ vera-agent install-browsers
[OK  ] Mobile caps   android=yes, ios=no
[OK  ] adb           on PATH — 1 usable device(s) attached
[FAIL] Appium        not on PATH
                     ↳ npm i -g appium && appium driver install uiautomator2

Summary: 4 OK, 1 warnings, 1 failures.

Exit code: 0 when all required checks pass. Mobile checks are WARN-only on a desktop-only agent (a missing emulator never fails doctor); they become FAIL (exit 1) only when this machine advertises that capability — so a mobile-capable agent that can't actually run mobile (e.g. Appium or the platform driver is missing) is caught in CI.

install-browsers

Wraps npx playwright install chromium. start/run also auto-prompt to install on first use (and install unattended in a non-interactive shell), so you rarely call this directly.

3. Configuration

Two credentials, three files

The most common setup mistake is assuming one token does everything. It does not:

FileWritten byHoldsUsed by
~/.vera/agent.jsonloginvera_agent_… enrollment token (Settings → Agents), chmod 600start, run, watch, verify, status, doctor
~/.vera/cli.jsoninitvera_… CI Token (Settings → CI Tokens), keyed by apiUrl, chmod 600init (and the MCP server it registers)
.vera/config.jsoninit{apiUrl, projectId, repoPathPrefix}no credential, commit itthe repo, your coding agent, CI

Neither token file is ever committed; .vera/config.json is meant to be. If you plan to both link the repo and execute runs on this machine, run init and login — they are separate steps writing separate credentials.

Environment overrides

Config lives in ~/.vera/agent.json (written by login, chmod 600). Environment variables override the file per field:

Env varFile keyMeaning
VERA_API_URLapiUrlVera API root.
VERA_AGENT_TOKENtokenEnrollment token (vera_agent_…).
VERA_AGENT_NAMEnameFriendly name reported to the dashboard.
QA_MAX_BROWSERS(env only)Browser-pool cap. Default 4. Never stored in the file.

Precedence (highest first): environment variable → ~/.vera/agent.json. Env values win per-field, so you can enroll once and override just the URL or name for a given invocation. The token is a bearer credential — keep the file 600 and prefer VERA_AGENT_TOKEN (e.g. from a secret store) in CI.

The agent executes one leased run at a time regardless of QA_MAX_BROWSERS (the cap governs parallelism within a run). Videos and traces are staged under ~/.vera/data before upload.

4. Environment targeting

Routing is per project environment (per plan §10 H4). A ProjectEnvironment carries:

  • runTarget?: 'cloud' | 'agent' — where runs against this environment execute.
  • an optional agentId pin — restrict this environment to one specific enrolled agent (otherwise any of the org's online agents can claim it).

There's also a project-level default run target that an environment inherits when it doesn't set its own. When an environment resolves to agent, the tests-run path pre-inserts a queued run row plus an agent_jobs row and returns; the run then waits for an agent to lease it. Cloud-targeted environments keep the existing in-process / queue path unchanged. Either way the run records who executed it in runs.executor ({kind:'agent', agentId, agentName} for agent runs), and cloud and agent runs render identically in the run view.

This means one project can have, side by side, a staging environment that runs in the cloud and a local environment that runs on your machine — same tests, different execution target.

5. Native mobile runs on the agent

An agent can drive a native app (.apk/.ipa) on a local emulator, USB device, or iOS simulator — the local-first counterpart of a device cloud.

  • Capability advertising. On every start/run handshake the agent detects and advertises { mobileAndroid, mobileIos } (Android when adb reports a device or the emulator binary is present; iOS on macOS with the simulator toolchain). The control plane routes a mobile job only to an agent that advertises the job's platform — a desktop-only agent never claims a mobile run.

  • App download. When a mobile test's config.mobile.appArtifactId resolves to an uploaded app, the lease carries the app descriptor; the agent downloads the binary over the authenticated agent API and caches it under ~/.vera/apps/<appId>.<ext> (re-download only when missing). The server validates the download key against the leased run's project — an agent can never fetch arbitrary org artifacts.

  • Local Appium. On a mobile lease the agent ensures a reachable Appium 2 server: it probes http://127.0.0.1:4723, reuses one you already have running, or spawns appium if it's on PATH (stopped when the loop drains). If no server is reachable and appium isn't installed, the run fails with an actionable message — the agent never silently installs anything. Install it yourself:

    npm i -g appium
    appium driver install uiautomator2   # Android
    appium driver install xcuitest        # iOS (macOS)
  • Local-Appium only. Agent mobile runs target a local Appium; device clouds (BrowserStack / LambdaTest) are the server-side path and never run on an agent.

  • Verify first. Run vera-agent doctor to confirm adb/emulator/Appium (and Xcode/simulators on macOS) before dispatching a mobile run.

  • iOS specifics. iOS runs against simulators (free Apple ID; the first session builds WebDriverAgent — slow, one-time). The agent resolves the test's deviceProfile to a simulator, boots it if needed, and pins the session to it. iOS requires your Mac or your device-cloud account — see the dedicated iOS native testing guide.

6. Lifecycle facts worth knowing

  • Lease + heartbeat. A claimed run's lease lives 60 seconds without a heartbeat; the agent heartbeats every ~20s while executing. If your agent crashes or loses its network, the lease expires within a minute.
  • Requeue once, then "agent lost". The scheduler sweeps expired leases: the first expiry requeues the job (so another agent — or your restarted agent — can pick up the same run row and finish it); a second expiry gives up and fails the run with agent lost.
  • Offline / queued timeout. If a run is queued and no agent claims it within 10 minutes, the scheduler fails the run with: "No agent picked up this run within 10 minutes — start vera-agent on your machine." A requeued job gets a fresh 10-minute window from the requeue, not from when it was first created. (If you see this, your agent isn't running or isn't enrolled in the same org / pinned to that environment.)
  • Drain is graceful. Ctrl+C finishes the current run before exiting, so you don't strand a half-done run in running.

7. Limitations in this phase

  • Visual regression and accessibility findings are not reported from agent runs. screenshot steps still capture and upload, but there's no baseline diff, and a11y findings are dropped. Run those on the cloud target for now.
  • Cross-project chains are cloud-only. Chains never execute on an agent in this phase.
  • Mobile is local-Appium only. Agent mobile runs drive a local Appium server; device clouds are the server-side path.

8. Troubleshooting

SymptomCause & fix
401 Unauthorized on every requestThe token is invalid or was revoked in Settings → Agents. Mint a new one and vera-agent login again.
Agent refuses to run: "the server requires at least X"Your agent version is below the server's minAgentVersion. Upgrade it: npm install -g vera-agent@latest (or just use npx vera-agent@latest).
init: "Vera token not accepted…"Three indistinguishable causes, by design: the vera_… token is revoked, it has expired, or the workspace's plan no longer includes api-access (Pro), which /api/v1 requires. Check the plan as well as the token — a free cloud workspace's token is valid for MCP but not for init.
verify: "Cannot verify — agent handshake failed"verify runs on the enrollment credential, not init's. Run vera-agent login --url … --token vera_agent_… (Settings → Agents) on this machine first.
check --run --local: "needs an enrolled agent on this machine"Same cause, checked up front: check reads the map with the vera_… CLI token but executes with the vera_agent_… enrollment. Run login, or drop --local to run cloud-side.
check --run --local: "enrolled with X, but the impact map came from Y"This machine is enrolled against a different deployment, so the impacted test ids do not exist there. Re-enrol against the URL in .vera/config.json.
"Playwright Chromium is not installed"Run vera-agent install-browsers (or let start auto-install). vera-agent status shows the detected browser cache path.
Run stuck queued, then fails after 10 minNo agent claimed it. Check vera-agent status shows reachable: yes, that the agent is enrolled in the same org, and — if the environment pins an agentId — that this agent is the pinned one. The dashboard's Agents panel shows an online LED per agent.
Could not reach … / timeoutsNetwork/URL problem. Verify --url (or VERA_API_URL) and that the server is reachable from this machine.
Agent shows Offline in the dashboard shortly after stoppingExpected — presence is a 60s heartbeat; it dims once the agent stops leasing/heartbeating.
A mobile run stays queued and never claimsThis agent isn't advertising that platform. Run vera-agent doctor — Android needs adb + a device or the emulator binary; iOS needs macOS + simulators. Restart the agent so the fresh capabilities are advertised on /hello.
Mobile run fails: "No Appium server is running …"Install and start Appium: npm i -g appium && appium driver install uiautomator2 (or xcuitest). The agent probes/spawns 127.0.0.1:4723; vera-agent doctor shows what's missing.

See also

  • Getting startedvera-agent init: linking a repo to a project, the three config files, and the verify → keep → triage loop.
  • Change impact (vera check) — the three joins behind check / affected, what they cannot see, and the --run --local contract.
  • MCP server — the tool surface init registers, and the manual setup for clients other than Claude Code.
  • CI-rider — run queued Vera tests on your own CI runners via vera-agent run --drain + a composite GitHub Action (scheduled iOS/web regression, with cost math).
  • CI integration — token-driven runs from CI (the cloud path).
  • iOS native testing — the honest iOS story: simulators on a Mac (free Apple ID + WebDriverAgent), real devices, and the device-cloud/CI alternatives.