Worker affinity

Route workflow executions to specific workers by declaring label requirements in your workflow definition.

Worker affinity lets you tell Flux that a workflow should only run on workers with specific capabilities. Attach labels to workers at startup, then declare matching requirements in your workflow definition. The scheduler routes executions accordingly.

How it works

Labels are static strings attached to a worker process when it starts. A label is a key=value pair — both key and value are strings. A workflow declares an affinity dict; when an execution is scheduled, the dispatcher checks each connected worker’s label set and only sends the execution to workers whose labels satisfy every entry in the affinity dict.

Matching follows three rules:

Starting a worker with labels

Pass one --label flag per label at startup:

flux start worker gpu-worker-1 \
  --label role=training \
  --label gpu=a100 \
  --label env=production

Labels are set once at process start and cannot change while the worker runs. To change labels, restart the worker with updated flags.

To see what labels a running worker has:

flux worker list
flux worker show gpu-worker-1

Declaring affinity in a workflow

Use workflow.with_options(affinity=...) to set the affinity constraint:

from flux import workflow, ExecutionContext

@workflow.with_options(affinity={"role": "training", "gpu": "a100"})
async def train_model(ctx: ExecutionContext[str]):
    # Only dispatches to workers started with
    # --label role=training --label gpu=a100
    dataset = ctx.input
    return f"Model trained on {dataset}"

The affinity value is a plain dict[str, str]. All values must be strings — match this to how you specify label values on the worker side.

A workflow without affinity runs on any available worker:

@workflow
async def generic_task(ctx: ExecutionContext[str]):
    # No affinity — any worker can claim this
    return ctx.input

Combining affinity with other options

affinity composes with namespace, requests, schedule, and other with_options parameters:

from flux import workflow, ExecutionContext
from flux.domain.resource_request import ResourceRequest

@workflow.with_options(
    namespace="ml",
    affinity={"role": "training", "gpu": "a100", "env": "prod"},
    requests=ResourceRequest(gpu=1, memory="16Gi"),
)
async def large_training_run(ctx: ExecutionContext[str]):
    # Requires: labels match AND >= 1 GPU AND >= 16 GiB free memory
    return f"Training: {ctx.input}"

When both affinity and requests are set, a worker must satisfy both. Labels are checked first; then resource availability is checked against the matched workers.

Practical example: routing to a browser worker

Some workflows need a worker with specific tools installed — a headless browser, for instance. Start a dedicated worker:

flux start worker browser-worker \
  --label role=browser-agent \
  --label browser=chromium

Then declare a workflow that targets it:

from flux import workflow, ExecutionContext, task

@task
async def fetch_page(url: str) -> str:
    # This task runs on a worker where browser tools are available
    return f"Page content from {url}"

@workflow.with_options(affinity={"role": "browser-agent", "browser": "chromium"})
async def scrape_site(ctx: ExecutionContext[str]):
    url = ctx.input
    content = await fetch_page(url)
    return content

Without the affinity constraint, the scheduler might send scrape_site to a generic worker without browser tools, and the task would fail at runtime.

Resume behavior

When a paused workflow resumes, Flux prefers the original worker. If that worker is no longer connected, any worker matching the affinity constraints can claim it. A worker without the required labels cannot pick up a resumed execution.

Affinity vs resource requests

Labels (affinity)Resource requests (requests)
QuestionWhat kind of worker?How much capacity?
Examplesrole=training, gpu=a100, env=prodcpu=4, memory="8Gi", gpu=1
MutabilityFixed at worker startupChecked per execution
MatchingAll keys must be present with exact valuesResources must be available at claim time

Use labels for stable capabilities — GPU model, environment type, installed tooling. Use resource requests for quantities that vary. See Resource requests for details.

Beyond hard constraints

Affinity decides which workers can run a workflow. To rank the eligible workers — by latency, load, locality, or custom metrics — add a scoring policy on top with workflow.with_options(routing=score(...)). Scoring is a soft preference evaluated after the affinity and resource filters, so it can never route around a hard constraint. See Dynamic routing.

What’s next