# Setup

A worker connects your own machine to workbenches.dev as a self-hosted runner. Runs started on workbenches.dev, from the API or `mcp.workbenches.dev`, can execute on it instead of in a hosted Daytona sandbox. workbenches.dev stays the orchestrator: the worker reports the same normalized events a cloud run does, so the run page, `wait`, commands, outcomes, and pull requests work as they do for a cloud run.

Worker time is free. A worker run records no sandbox time and is not checked against your plan's sandbox hours.

A worker runs more than a sandbox can:

- **Any runner.** OpenCode, Claude Code, and Pi all run on a worker.
- **Local-only Workbenches,** on its `local` runtime, with the machine's own tools, such as Xcode on a Mac.
- **Members' own sign-ins,** such as a Claude or ChatGPT subscription, instead of only the organization's OpenRouter key. See [Member sign-ins](/docs/workers/operations#member-sign-ins).

It does not offer [live previews](/docs/cloud/previews) yet.

A worker needs a workbenches.dev account. Nothing else in `wb` depends on it. `wb worker` supports macOS and Linux; on Windows `wb worker start` fails with `wb worker is not supported on Windows yet`.

Read the [trust model](/docs/workers/trust) before sharing a worker: every run executes as the machine's account, with no sandbox.

## Start a worker

Everything is done from a shell on the host, usually over SSH. No desktop session is needed.

```sh
wb login                                 # or wb login --org acme
wb worker start --name mini
```

Without a login, the first `start` fails with `Sign in first with wb login`.

`start` on a machine that is not yet a worker registers it, with your own `wb login` for the default organization or the one `--org` names. You must be a member of that organization, and you become the worker's owner. It stores a worker key in `$WORKBENCH_HOME/worker/worker.json` with mode `0600`. That key can only connect as this worker; your login key never reaches it. The worker keeps using the API it registered with.

`start` then installs and starts a background service and returns:

- macOS: a launchd agent in your user's background domain (`user/<uid>`),
- Linux: a systemd user service with lingering enabled,

so the worker keeps running after the SSH session ends. Once it connects it prints:

```text
Worker mini is online for acme. 1 slot, runtimes docker, local. Follow it with: wb worker logs -f
```

or that it started and is still connecting.

### Options

| Option              | Effect                                                                                   |
| ------------------- | ---------------------------------------------------------------------------------------- |
| `--name <name>`     | The worker's name, to register this machine. On a registered machine it may be left out. |
| `--org <slug>`      | The organization to register with. Defaults to your default organization.                |
| `--runtimes <list>` | Narrow the runtimes it offers, such as `--runtimes docker` to turn local runs off.       |
| `--slots <n>`       | How many runs it takes at once. Defaults to 1.                                           |

`--runtimes` and `--slots` are remembered for the next `start`. On a registered machine, a different name fails with `This machine is already the worker mini. Run wb worker remove first.`

The service runs with the `PATH` of the shell that started it, so Docker and runner CLIs are found the same way they are in that shell. A plain `ssh host wb worker start` gets a non-login shell with a short `PATH`; start it from an interactive login shell, such as `zsh -lic 'wb worker start'`, so it finds what you installed.

## Runtimes

A worker offers every runtime the machine can run. It checks both each time it starts.

| Runtime  | Offered                                       | What a run gets                                                                                                                                             |
| -------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `docker` | When Docker is reachable as the worker starts | One container per run from the Workbench's own image, sized by its `requirements`. The host needs only `wb` and Docker.                                     |
| `local`  | Always                                        | The run executes on the host as the account that started the worker, with this machine's tools and Docker, and the starting member's directory as its home. |

Nobody picks a runtime when starting a run. workbenches.dev places a run on the first runtime its Workbench declares that the worker offers: `local` runs on the host, and an entry with an image (`docker`, `e2b`, or `daytona`) runs in Docker.

`start` fails when the machine can run none of the allowed runtimes.

## Send runs to it

A worker is personal at first: only runs you start go to it. To let every member of the organization use it, share it from its page on workbenches.dev, or with the API (`PATCH /v1/workers/:id` with `{ "shared": true }`). You can make it personal again the same way.

To run on it, pass `worker` with its slug to the `run` MCP tool, or `worker` in `POST /v1/runs`. The `workers` MCP tool and `GET /v1/workers` list the organization's workers with their slugs.

A run is refused, and the reason shown, when:

| Status                  | Reason                                                                                                                                                                                                 |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 404                     | The organization has no worker with that slug.                                                                                                                                                         |
| 403                     | The worker is personal to another member: `The worker <name> is personal to <owner name>.` A run started by a key with no member is refused the same way.                                              |
| 409 `worker_offline`    | The worker is not connected.                                                                                                                                                                           |
| 422 `worker_unsuitable` | It offers no runtime the Workbench declares, its machine does not meet the Workbench's `requirements` (`os`, `arch`, `cpu`, `memory_gb`, `gpu`), or the package is over the 64 MiB a worker downloads. |

The worker itself also rejects a run whose runtime it does not offer, whose Workbench does not declare that runtime, whose requirements it cannot meet, or that arrives with every slot busy. A run waits for a free slot.

A worker reports the GPUs it finds, with each name and, for NVIDIA, its memory, every time it connects. A local run that needs a GPU passes on that discovery alone, since a worker run has no `--allow-unchecked-gpu`.

On a personal worker, `send`, `steer`, `queue`, and `answer` commands are refused for anyone but its owner. Any member may cancel.

## Credentials for runs

Before members run on a worker, each connects their own runner sign-ins there with `wb connect --worker`. Without one, a run uses the provider keys workbenches.dev sends with it. See [Member sign-ins](/docs/workers/operations#member-sign-ins).
