> ## Documentation Index
> Fetch the complete documentation index at: https://yukta.infenia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Add remote workers

> Run selected processor nodes on other machines in named worker pools, secure the worker connection with a token and mutual TLS, and check that a placed node runs there.

<dl />

**Use this when** a step is heavy, must run next to a private resource (a database, a GPU host, an internal network) or should not compete with the engine for CPU and memory. The server that admits and tracks runs is the control plane. It still sees every message of a placed node, so placement moves the work, not the data.

**Prerequisites:**

* A Plus build (AGPL-3.0-only) with the workers module: `-Pyukta.workers=true`. The release binaries and the Docker image do not include it. See [Licensing and editions](https://yukta.infenia.com/reference/licensing.md).
* The same build on the control plane and every worker, and the plugin types a worker will run built into that worker.
* A long random token.

> [!NOTE]
>
> A native binary fixes its role when you build it. Build it with `-Pyukta.workers.native=control-plane` or `-Pyukta.workers.native=worker`. With the archive (jar) build, the settings choose the role.

![The control plane opens one channel per attempt to each worker, and workers register and renew a lease with it.](https://yukta.infenia.com/images/diagrams/remote-workers.light.svg)

Workers connect to the control plane; it decides where a placed node runs.

## Steps

**1. Build with workers**

```bash theme={"system"}
./gradlew :boot:bootJar -Pyukta.workers=true
```

Without the switch, a workflow that places a node fails to prepare with an error that names it.

**2. Configure the control plane**

The control plane is the server that has `yukta.workers.token` and no `yukta.workers.control-plane`:

```properties theme={"system"}
yukta.workers.token=<long random token>
spring.rsocket.server.port=7000
```

This opens a second port, 7000, for workers. Workers connect out to it, so a worker needs no inbound port.

**3. Secure the connection**

Without TLS the token, node configurations and payloads cross the network in clear text. A control plane with no SSL bundle, or a worker whose control plane is not on its own machine and has no bundle, refuses to start unless you set `yukta.workers.allow-insecure=true`. Use that only on a network you trust.

For mutual TLS, give the control plane a certificate and require a client certificate:

```properties theme={"system"}
spring.rsocket.server.ssl.bundle=server
spring.rsocket.server.ssl.client-auth=need
spring.ssl.bundle.jks.server.keystore.location=file:/etc/yukta/server.p12
spring.ssl.bundle.jks.server.keystore.password=<password>
spring.ssl.bundle.jks.server.keystore.type=PKCS12
spring.ssl.bundle.jks.server.truststore.location=file:/etc/yukta/client-trust.p12
spring.ssl.bundle.jks.server.truststore.password=<password>
spring.ssl.bundle.jks.server.truststore.type=PKCS12
```

Give each worker its own certificate in a bundle that the worker names with `yukta.workers.ssl-bundle` (next step). The worker checks the control plane's host name against its certificate.

**4. Start a worker**

A worker is a full Yukta process. Its settings:

```properties theme={"system"}
yukta.workers.control-plane=cp1.internal:7000
yukta.workers.token=<long random token>
yukta.workers.id=worker-a
yukta.workers.pools=private-net
yukta.workers.plugin-types=HTTP_REQUEST,SQL_QUERY
yukta.workers.capacity=8
yukta.workers.ssl-bundle=client
spring.ssl.bundle.jks.client.keystore.location=file:/etc/yukta/worker.p12
spring.ssl.bundle.jks.client.keystore.password=<password>
spring.ssl.bundle.jks.client.keystore.type=PKCS12
spring.ssl.bundle.jks.client.truststore.location=file:/etc/yukta/server-trust.p12
spring.ssl.bundle.jks.client.truststore.password=<password>
spring.ssl.bundle.jks.client.truststore.type=PKCS12
```

`yukta.workers.id` is required. A worker with an empty `yukta.workers.plugin-types` runs nothing, so list every type it may run. When you run several workers on one machine, give each its own `server.port`. The defaults for pools (`default`), capacity (`4`) and the other settings are in [Configuration reference](https://yukta.infenia.com/reference/configuration.md).

**5. Place a node**

Add `placement` to the node's `engine` object. A placement without `pool` uses the pool `default`. This node calls an internal service on a worker in `private-net`, with up to three attempts:

```json theme={"system"}
{
  "nodeId": "fetch-inventory",
  "type": "HTTP_REQUEST",
  "config": {
    "url": "http://inventory.internal/api/stock",
    "method": "GET",
    "engine": {
      "placement": { "pool": "private-net" },
      "retry": { "maxAttempts": 3, "backoffMs": 500 },
      "onError": "ROUTE"
    }
  }
}
```

Only processors can be placed. `SESSION_MEMORY`, `KV_STORE`, `WAIT_FOR_SIGNAL` and `SUB_WORKFLOW` keep state on the control plane and refuse a placement. Triggers and terminals always run locally.

## Verify it worked

List the workers the control plane knows:

```bash theme={"system"}
curl -fsS -H "Authorization: Bearer $YUKTA_TOKEN" "$YUKTA_SERVER_URL/api/workers"
```

Each entry shows the worker's id, pools, plugin types, capacity, used slots, whether it is draining, and when its lease ends. Start the workflow with the placed node and read the node's result. The gauges `yukta.workers` (connected workers) and `yukta.worker.slots` (free slots) show capacity on the metrics endpoint.

If no worker qualifies, the attempt fails at once with "No connected worker in pool 'private-net' can run HTTP\_REQUEST right now". The error is retryable, so the node's `retry` policy applies. A worker qualifies when it is connected, not draining, serves the pool, lists the plugin type and has a free slot.

## Variations that matter

* **A secret per worker.** Give the control plane `yukta.workers.tokens.<worker-id>=<secret>` for each worker (write `yukta.workers.tokens[my.id]` when the id has dots). As soon as one is set, only the listed workers are admitted, and the shared token stops working. To remove one compromised worker, delete its entry and restart the control plane.
* **Several control planes (high availability).** Set `yukta.workers.control-plane` to every server, comma separated. A standby refuses the worker with "this server is not the leader". See [Run highly available](https://yukta.infenia.com/guides/operate/run-highly-available.md).
* **Rolling restart of workers.** A worker that stops tells the control plane it is draining and waits up to `yukta.workers.drain-timeout` (30 seconds by default) for running attempts. Anything still running fails and the engine retries it on another worker. A worker that loses the control plane reconnects with a pause that grows from 1 to 30 seconds.
* **Upgrade together.** A worker that registers with another protocol version is refused and gets no jobs. Upgrade the control plane and the workers to the same release.
* **Make placed steps safe to repeat.** Delivery is at least once. If a worker dies after an effect and before the attempt completes, the effect can happen twice. Use the message id as an idempotency key.
* **Configure workers like the server.** A plugin runs inside the worker process, so the worker's own allow-lists apply (commands, hosts, variables, secrets). See [Secure the server](https://yukta.infenia.com/guides/operate/secure-the-server.md).

What works differently on a worker: run budgets and rate limits (for example for `LLM_CALL`) are counted inside each worker, not for the whole run. Live log lines written on the worker do not reach the run's log stream. Messages cross as plain JSON values.

## Related

* [Run highly available](https://yukta.infenia.com/guides/operate/run-highly-available.md)
* [Secure the server](https://yukta.infenia.com/guides/operate/secure-the-server.md)
* [Handle failures](https://yukta.infenia.com/guides/build/handle-failures.md): retry and error routing for a placed node
* [Node options reference](https://yukta.infenia.com/reference/node-options.md): the `engine` object
* [Configuration reference](https://yukta.infenia.com/reference/configuration.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.