> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://docs.ampdb.io/api/mcp` to find what you need.

# Workers

A **worker** is the part of Amp that runs [jobs](/managing-amp/jobs): it extracts chains, runs derived datasets'
SQL, collects garbage, and exports tables. [Solo mode](/running-amp/solo-mode) runs one, named `worker`, in the same
process as everything else. In [distributed mode](/running-amp/distributed-mode), each worker is an `ampd worker`
process, and you run as many as you need (see [Worker](/running-amp/worker)).

This page explains how workers get their jobs, and how to see the workers Amp knows and whether they're alive. When a
worker misbehaves, see [Troubleshooting Workers](/managing-amp/workers/troubleshooting).

## Prerequisites

* Amp running, with its Admin API reachable. [Solo mode](/running-amp/solo-mode) serves it at
  `http://localhost:1610`, where `ampctl` looks by default. For another Amp, set its URL, and a token if it needs one:

  ```bash
  export AMP_ADMIN_URL=https://amp.example.com:1610
  export AMP_AUTH_TOKEN=your-token
  ```

* `ampctl` installed (see [Installation](/running-amp/installation)).

## How workers get jobs

* **Node IDs.** Each worker has a node ID, which you give `ampd worker` with `--node-id`, like `worker-01`. It starts
  with a letter, followed by letters, digits, `_`, `-`, and `.`. Only one worker can use an ID at a time: a second
  `ampd worker` with the same ID refuses to start.
* **Heartbeats.** A running worker records a heartbeat in the [metadata database](/running-amp/metadata-database) every
  second. A worker whose last heartbeat is less than 5 seconds old is **active**.
* **Scheduling.** When a job is created, the controller places it on an active worker: the one its `--worker-id` names,
  or one of those its pattern matches, like `'worker-*'`, or else the
  [default worker](/running-amp/configuration-reference#controllerdefault_worker_id), or a random one. Job creation
  fails when no active worker fits.
* **Assignment.** A job stays with its worker for good: its retries and its periodic runs run there too. A worker picks
  up its jobs as soon as they're scheduled, and checks for any it missed every minute. When it restarts, it resumes
  the jobs it was running.

## List workers

:::code-group
```bash [ampctl]
ampctl worker list
```

```bash [Admin API]
curl http://localhost:1610/workers
```
:::

`ampctl` shows each worker's node ID and last heartbeat, newest worker first:

```
Workers:
  worker-03 (last heartbeat: 2026-10-30T14:30:00.512378+00:00)
  worker-02 (last heartbeat: 2026-10-30T14:30:00.208119+00:00)
  worker-01 (last heartbeat: 2026-10-30T14:21:47.901244+00:00)
```

Here, `worker-01` hasn't sent a heartbeat for 8 minutes: it's stopped, or it can't reach the metadata database. A
worker stays in the list after it stops, with its last heartbeat, and there's no way to remove it. `ampctl worker ls`
does the same.

The Admin API returns `{"workers": [...]}`, each with these fields:

| Field          | Type   | Description                                                                          |
| -------------- | ------ | ------------------------------------------------------------------------------------ |
| `node_id`      | string | The worker's node ID.                                                                |
| `heartbeat_at` | string | When it last sent a heartbeat, in RFC 3339, like `2026-10-30T14:30:00.512378+00:00`. |

## Inspect a worker

:::code-group
```bash [ampctl]
ampctl worker inspect worker-01
```

```bash [Admin API]
curl http://localhost:1610/workers/worker-01
```
:::

```
Node ID: worker-01
Created: 2026-10-02T09:12:44.204511+00:00
Registered: 2026-10-30T13:58:02.731090+00:00
Heartbeat: 2026-10-30T14:21:47.901244+00:00

Worker Info:
  Version: v0.0.42
  Commit: f27102fd59608258dc58a6dbbaac880f124364ae
  Commit Timestamp: 2026-09-24T19:45:31.000000000+02:00
  Build Date: 2026-09-24
```

`ampctl worker get` does the same. Inspecting a node ID Amp doesn't know fails with `worker not found: worker-09`, or
`404` with `WORKER_NOT_FOUND`.

| Field                   | Type   | Description                                                                                                                                                                                                                                    |
| ----------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `node_id`               | string | The worker's node ID.                                                                                                                                                                                                                          |
| `created_at`            | string | When a worker with this ID first started. It never changes.                                                                                                                                                                                    |
| `registered_at`         | string | When it last started. Later than `created_at` once it has restarted.                                                                                                                                                                           |
| `heartbeat_at`          | string | When it last sent a heartbeat.                                                                                                                                                                                                                 |
| `info.version`          | string | The `ampd` version it runs, as `git describe` prints it: the release, then, if it was built from a later commit, how many commits later and which, like `v0.0.42-101-g555b7a3`. A `-dirty` suffix means it was built with uncommitted changes. |
| `info.commit_sha`       | string | The commit `ampd` was built from.                                                                                                                                                                                                              |
| `info.commit_timestamp` | string | When that commit was made.                                                                                                                                                                                                                     |
| `info.build_date`       | string | When `ampd` was built.                                                                                                                                                                                                                         |

Each `info` field is `unknown` when the worker didn't report it. Timestamps are in RFC 3339. `created_at`,
`registered_at`, and `heartbeat_at` are in UTC; `info.commit_timestamp` keeps the commit's own offset.

To list every worker's version, for example to check that an upgrade reached them all:

```bash
ampctl worker list --json | jq -r '.workers[].node_id' | while read -r node_id; do
  ampctl worker inspect "$node_id" --json | jq -r '"\(.info.version)  \(.node_id)"'
done | sort
```

```
v0.0.41  worker-01
v0.0.42  worker-02
v0.0.42  worker-03
```

## Choose where jobs run

To run a job on a given worker, pass its node ID to `--worker-id` when you create the job, or when you
[deploy a dataset](/managing-amp/datasets#deploy-options). A glob pattern, like `--worker-id 'eu-*'`, picks one of the
active workers it matches, at random. To send every job that doesn't name one to the same worker, set
[`controller.default_worker_id`](/running-amp/configuration-reference#controllerdefault_worker_id).

To spread jobs over workers with different hardware or locations, give them node IDs you can match, like
`eu-west-1a-worker` and `us-east-1b-worker`.
