Runners

Runners example.

Pinning workflows to a specific execution runner.

Run it

python examples/runners.py
"""Pinning workflows to a specific execution runner.

Workers run each execution through a *runner*. Which one is used comes from
the worker's ``[flux.workers] default_runner`` (``subprocess`` unless
changed) — or from the workflow itself via
``@workflow.with_options(runner=...)``, which also constrains dispatch: the
workflow only goes to workers that advertise that runner.

- ``subprocess`` (the default): one child process per execution. Fault
  isolation — a crash or blocking call can't take down the worker — and a
  sanitized environment: workflow code never sees worker credentials.
- ``inprocess``: runs on the worker's event loop. Lowest latency, no
  isolation; pair it with trusted, async-clean workflows — transient
  mesh hops especially.
- ``docker``: one container per execution (workers must enable it and set
  ``docker_image``). Full filesystem/dependency isolation for untrusted
  code.

The runner option matters when executions are dispatched to workers;
running a workflow inline (``workflow.run()``, as below) executes in the
current process regardless.
"""

from __future__ import annotations

from flux import ExecutionContext
from flux.task import task
from flux.workflow import workflow


@task
async def double(x: int) -> int:
    return x * 2


@workflow.with_options(runner="inprocess")
async def fast_hop(ctx: ExecutionContext[int]):
    """Latency-sensitive and trusted: stay on the worker's event loop.

    Combine with ``durability="transient"`` for the lowest-overhead
    configuration for agent-to-agent (mesh) calls.
    """
    if ctx.input is None:
        raise TypeError("Input not provided")
    return await double(ctx.input)


@workflow.with_options(runner="subprocess")
async def isolated_by_default(ctx: ExecutionContext[int]):
    """Explicitly pinned to the subprocess runner.

    This is also the worker default, so the annotation here just makes the
    requirement dispatch-enforced: the workflow will only run on workers
    that advertise the subprocess runner.
    """
    if ctx.input is None:
        raise TypeError("Input not provided")
    return await double(ctx.input)


@workflow.with_options(runner="docker")
async def containerized(ctx: ExecutionContext[int]):
    """Runs in its own container, on workers configured with:

        [flux.workers]
        runners = ["inprocess", "subprocess", "docker"]
        docker_image = "<registry>/flux:<version-matching-the-worker>"

    Use for untrusted code or conflicting dependency sets. Workers without
    the docker runner never receive this workflow.
    """
    if ctx.input is None:
        raise TypeError("Input not provided")
    return await double(ctx.input)


if __name__ == "__main__":  # pragma: no cover
    ctx = fast_hop.run(21)
    print(ctx.to_json())
    ctx = isolated_by_default.run(21)
    print(ctx.to_json())
    ctx = containerized.run(21)
    print(ctx.to_json())

Last verified against Flux 0.56.0.