Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Workers

A worker reservation gives a remote caller its own short-lived machine to run chats on. You ask the hub for a worker from a pool the operator defined, a fresh aya-helper boots and enrolls, you drive chats on it through the Remote API, and when you release it the machine is destroyed and every credential it held stops working.

It is off unless the operator turns it on (see Run your own). With it off, the routes below answer 404 {"error":"workers unavailable"}. The full reference, with every field and status, is docs/api/workers.md.

Examples use hub.example.test for the hub and issuer.example.test for its identity provider.

What it is

  • A pool is something the operator defines: which harnesses a worker runs, who may reserve from it, how many can run at once, how long they may live, and how machines are created.
  • A reservation is your claim on one worker from one pool. It has a state (queued, provisioning, enrolled, active, releasing, destroyed, and so on) and a time-to-live.
  • A worker is an aya-helper on the machine behind it. It has its own identity on the hub, created when it enrolls and disabled when the reservation ends.
  • Only you can use your reservation. Its chats run on a reserved root, res-<reservation_id>.<harness>, which no one else can address and which the browser UI never reaches.

Get a token

Reserving uses the same kind of token as the rest of the Remote API, with a new scope. Ask for workers:reserve, plus chat:read and chat:drive to run and read chats:

TOKEN=$(curl -s -X POST "$TOKEN_ENDPOINT" \
  -d grant_type=client_credentials \
  -d client_id="$CLIENT_ID" \
  -d client_secret="$CLIENT_SECRET" \
  -d resource="$RESOURCE" \
  -d scope="workers:reserve chat:read chat:drive" | jq -r .access_token)

Your client’s sub must be on the operator’s REMOTE_ALLOWED_SUBJECTS list and on the pool’s access list. See Authentication for the scopes. GET /api/remote/v1/auth-info still lists only chat:read and chat:drive.

Reserve a worker

HUB=https://hub.example.test
RSV=$(curl -s -X POST "$HUB/api/remote/v1/reservations" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"pool":"dev","request_id":"build-001","kind":"job"}' | jq -r .reservation_id)

kind is job or checkout. request_id makes the call safe to retry: the same id with the same input returns the same reservation, and different input is a 409. You may also give ttl_secs (it can only shorten the pool’s maximum), a subset of the pool’s harnesses, labels the worker must carry, and a source the pool’s schema accepts.

Poll GET /api/remote/v1/reservations/$RSV and read state. It moves through queued and provisioning to enrolled, and to active once the worker has sent its first heartbeat. 429 means a pool limit or budget was hit and carries retry_after_secs. A failed or rejected reservation never becomes usable; reserve again.

Chat on it

Start a chat on the reservation with reservation_id instead of root_id (name exactly one of the two):

curl -s -X POST "$HUB/api/remote/v1/chats" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"request_id":"chat-001","provider":"claude","reservation_id":"'"$RSV"'","text":"Run the tests."}'
# 202 {"turn_id":7,"replayed":false,"warning":null}

Then poll /turns/{id}/events exactly as in Remote API. A reserved root takes neither tier nor working_directory, and provider must be one of the reservation’s harnesses. If the reservation belongs to someone else you get 403 not your reservation, and once it has ended you get 410 reservation ended.

You can also bring an existing session along with POST /api/remote/v1/sessions/{pk}/move (needs workers:reserve and chat:drive). seed is native to continue the session itself on the worker (the worker must support cross-host resume), or summary to start a new chat seeded from the transcript.

Release it

curl -s -X DELETE "$HUB/api/remote/v1/reservations/$RSV" \
  -H "Authorization: Bearer $TOKEN"

Releasing is safe to repeat. The hub revokes the worker’s tokens in the same step, ends its pending turns and destroys the machine. From then on the worker’s own calls answer 401, and a new chat on the reservation answers 410.

How a worker enrolls

You do not do this part; the operator’s pool setup does. It is here so you can tell what is happening while a reservation is provisioning.

  1. The pool’s backend creates or assigns a machine. A static pool hands out a fixed set of registered machines; a webhook pool asks the operator’s own provisioning service to create one.
  2. The machine receives a single-use enrollment token and presents it to the hub together with an Ed25519 public key it generated in memory, and proof (an attestation) that it is the machine the pool expected. The private key never leaves the machine and no shared secret is stored on it.
  3. The hub checks the proof and the pool’s requirements, creates the worker identity, binds the reserved roots to it, and moves the reservation to enrolled. A replayed token, or one for a reservation in the wrong state, gets one constant refusal.
  4. The worker signs short assertions with its key to obtain short-lived access tokens from the hub’s own token endpoint, then heartbeats, which makes the reservation active. It keeps a wake long-poll open so it learns about your chat at once instead of waiting out its idle poll.

If the hub is unreachable for too long, or the reservation ends, the helper stops and kills every process it started. It is not restartable: the enrollment token is single-use.

Pointing a worker’s Claude at the AYA gateway

An option for the operator of a worker machine; the helper never sets these itself. When the helper process has both of these set, every claude it starts for a turn gets ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN from them, and nothing else changes:

Helper variableBecomes, in the claude child
AYA_WORKER_ANTHROPIC_BASE_URLANTHROPIC_BASE_URL
AYA_WORKER_ANTHROPIC_AUTH_TOKENANTHROPIC_AUTH_TOKEN

With neither set, claude runs exactly as before, on its own credentials. With only one set (or one blank), nothing is passed and the helper logs a warning naming the two variables. The token is never logged.

Fresh checkouts for work runs

When a work run’s profile on the hub sets checkout: "fresh", the helper that claims it gives the run its own working copy instead of the profile’s working directory: a new run-… folder under work_runs_dir in the helper’s config (default <data_dir>/runs, created the first time a run needs it), holding a clone of the profile’s repository (or a worktree of a local mirror) on the run’s branch. When the turn ends, after its claude process tree has exited, the helper posts a work checkout: notice on the turn with the branch, its head commit, the commits the run made and whether anything was left uncommitted, then removes the folder. Commits that were not pushed (clone mode) go with it; the notice still lists them. Work runs whose profile keeps the default checkout: "workdir" run exactly as before. Fresh checkouts are for Claude turns only.

Security model

  • Separate scopes. workers:reserve and workers:admin are not implied by chat:read or chat:drive, and do not imply them.
  • Your reservation is yours. The hub compares your identity with the reservation’s requester on every chat route. Someone else’s reservation and one that does not exist return the same 403.
  • Workers cannot be impersonated. The hub is a second OIDC issuer for worker tokens. A hub-issued token is judged only by the hub’s own, revocation-aware verifier. A token from the external issuer whose sub starts client-w- is refused, because that prefix belongs to the workers the hub enrolls.
  • Workers only get their own work. A reserved worker claims only its reservation’s turns and never the shared pool; every other worker never sees a reserved turn.
  • Short-lived, narrow tokens. Worker tokens last minutes (10 by default) and carry only the scopes the pool granted.
  • A worker can only narrow its claims. Its self-description can drop capabilities and labels declared at enrollment, never add them.
  • Release is final. Release revokes the worker’s tokens and disables its identity in one transaction.
  • You stay read-only by default. Reserved chats follow the same permission rules as any remote chat: read-only unless you ask for allow-edits.

For the operator’s side (pools, backends, signing keys, rotation and rate limits) see Run your own.