# Workers and execution

Worker connection, temporary workspaces, generated Playwright config, logs, steps, and failure handling.

Workers are the execution capacity behind BernOps runs. The backend owns scheduling and persistence; workers own the local browser process and Playwright execution.

## Worker connection

A worker connects to the backend WebSocket endpoint at `${ORCHESTRATOR_URL}/ws/worker` with its enrolled `WORKER_ID`, display name, and individual workspace-bound credential. There is no pool-wide secret, and a personal coding-agent host is not worker capacity.

For a new worker, use [Connect a worker](/docs/workers-and-pool#worker-setup): install the approved private bootstrap, then run `bernops setup` and choose **3 (automation worker)** or **4 (both)**. The default menu choice is auth-only and installs no worker. Setup handles shared platform login, verifies a signed immutable SDK-capable worker release and real SDK/browser readiness, then requests separate workspace approval. No public download, source checkout build, manual credential creation, or preliminary `bernops auth login` is required. Platform login alone never enrolls a host or worker, and its OAuth session is not passed to the container. Setup requires a confirmed Ready or Busy worker connection, not just a running container or successful image check.

Target selection follows saved identity, then the journal, then the bootstrap default unless explicit. Local development uses `./dev.sh install-cli` to activate the local-target bootstrap, followed by bare `bernops setup`; explicit `--target local` is also available. Native Secret Service is the default for local/cloud platform credentials; file storage is an explicit unencrypted choice for a new identity. Use `--no-browser` for remote approvals. The [low-level `bernops worker setup` alternative](/docs/workers-and-pool#headless-and-advanced) is different: cloud default, no platform CLI authentication, and an operator-controlled source/image path rather than unified signed-release setup.

The backend tracks connected workers and claims an eligible worker in the run's workspace when capacity is available. If no matching worker or scope capacity is available, the run remains `pending` until the dispatcher can assign it. Inspect inventory from the worker cog or **Workspace > Workers**, and use the [local management commands](/docs/workers-and-pool#worker-management) for status, start, stop, restart, and supervisor logs.

## What the worker receives

For Playwright runs, the backend sends a `RunTestsPayload` containing:

| Field | Purpose |
| --- | --- |
| `test_files` | File names and source code to write into a temp directory. |
| `base_url` | Final base URL used by the generated Playwright config. |
| `env_vars` | Project runtime variables plus forced execution variables. |
| `secret_env_keys` | Variable names whose values should be redacted. |
| `project_id` | Project identifier exposed as `BERNOPS_PROJECT_ID`. |

## Temporary workspace

The worker creates a temp directory, writes every test file using a sanitized basename, links `node_modules`, and generates `playwright.config.js`. The generated config sets:

- `workers: 1`
- `retries: 0`
- `trace: 'on'`
- `screenshot: 'on'`
- `video: 'retain-on-failure'`
- Docker-safe Chromium launch arguments

The worker then starts `npx playwright test --config=<temp config>` from the worker root.

## Environment construction

The child process environment is intentionally filtered. The worker keeps a small allowlist from its own environment, adds allowed payload variables, and forces BernOps variables like `AB_LOG_URL`, `BERNOPS_RUN_ID`, and `BERNOPS_PROJECT_ID`.

Payload variable names must pass the worker allowlist. Reserved names and prefixes are blocked so project variables cannot override process-critical values.

## Logs, steps, and traces

The private [Automation SDK](/docs/automation-sdk) can send acknowledged structured logs, nested steps, metrics, progress, and attachments through the authenticated worker-local bridge while the worker remains lifecycle owner.

New unified worker setup obtains an SDK-capable signed release with the exact private SDK already bundled and requires `native_bridge_v1`. Do not install or mount the SDK separately in a saved job. Existing legacy workers are not silently upgraded; image replacement alone does not expand their approved credential capabilities. See [setup recovery](/docs/workers-and-pool#setup-recovery) for `worker_sdk_upgrade_required` and separately approved replacement workers.

BernOps installs a custom Playwright reporter. Reporter output is emitted with a structured prefix, parsed by the worker, redacted, and sent back to the backend as execution steps.

Plain stdout and stderr are also streamed as execution logs. After the process exits, the worker parses `results.json`, mines trace files, and includes useful failure context.

**Why `test.step` matters**

The reporter sees Playwright test steps. Good step names become readable run timelines, which makes debugging easier than reading raw logs only.


## Failure handling

The worker reports a failed run when Playwright exits non-zero, when result parsing fails, or when the execution setup throws. The error message is redacted before it is sent back.

If no worker is connected, the run remains `pending` in the backend before a payload reaches any worker. Backend-side failures still move the run to `error`.

Terminal-result replay uses an in-memory outbox; it can recover across a connection interruption while the worker process survives, not after process loss. Worker enrollment persists separately. Scheduling still requires one active gateway; see [current pool limitations](/docs/workers-and-pool#current-limitations).
