Release notes
Hand-curated release notes for the visible Flux versions, with honest callouts of known limitations and ongoing work.
Flux ships releases against PyPI under the flux-core package. The history below is curated from the version surface observable at release time — pyproject metadata, the feature set documented in this site, and the limitations surfaced during verification work. There is no separate CHANGELOG.md in the source tree today; that gap is on the roadmap (see Roadmap).
Versions are reverse-chronological. Latest first.
0.56.0 — current shipped
The version this documentation site is verified against. This entry is a rollup of the 0.54–0.56 line — a short run of releases about where work lands: scoring policies for dispatch, workers that step out of the pool when starved, and locality for relayed calls. No new events or states, no Python-floor or extras changes, and the scheduler and retention sweep remain the only advisory-lock singletons — no leader election appeared.
Dispatch and routing
- Dynamic routing. Workflows can declare a scoring policy —
@workflow.with_options(routing=score(prefer(...), least(metric(...)), most(resource(...)), sticky(), least(load())))— that ranks the workers left after the hard constraints (labels, resources, runners, capacity) filter. Policies compile to data at registration and are evaluated per dispatch in event mode; malformed policies degrade to least-loaded rather than stranding executions. See Dynamic routing. - Built-in worker metrics feed routing. Every worker advertises a
flux.*-prefixed family of scalars on its heartbeat pong — loop lag (+p95), running executions, free slots, EWMA CPU, memory, failure/crash rates, throughput, duration p95, warm module count — persisted change-gated to theworkerstable and exposed in themetricsfield ofGET /workers. Routingmetric(...)selectors read them; ametrics_providerhook adds custom keys. These are routing inputs, not Prometheus series. - Sticky routing for relayed calls. A
call()relayed through the server (string workflow refs,mode="async", or runner-constrained targets) tags the child execution with anX-Flux-Preferred-Workerhint naming the calling worker. Event-mode dispatch prefers that worker when it is eligible, keeping agent-mesh hops on a warm module cache; poll mode ignores the hint. Not to be confused with load-balancer sticky sessions for the worker SSE stream, which remain a multi-replica infrastructure requirement — see High availability.
Worker self-health
- Workers detect their own event-loop starvation. A 1-second lag probe (
[flux.workers] loop_lag_probe_interval) flips the worker unhealthy after three consecutive probes overloop_lag_threshold(default 1.0 s;0disables) and recovers it after three clean ones. Unhealthy workers stay connected and finish running executions, but release newly assigned work for immediate re-dispatch and advertise{"healthy": false}on heartbeat pongs; the server excludes them from dispatch andGET /workersshowsstatus: "unhealthy"(on the replica holding the connection). The heartbeat reaper remains the backstop for total starvation. See How workers work. - Heartbeat pongs carry a payload. The pong body now carries self-health and the advertised metrics snapshot; legacy body-less pongs still work.
Approvals
- Standing approvals.
flux execution approve <id> <task> --always(or the REST equivalent) records an execution-scoped standing grant, so later approval gates on the same task name in that execution auto-approve — each with its own audit row. Built for retry loops and multi-step rollouts that would otherwise re-prompt per attempt. See Human approvals. - Fixed: retry-attempt replay after an approval pause. A task suspended at a retry-attempt approval gate now resumes at the correct attempt (with the compounded backoff it would have had) instead of re-running from attempt zero.
Observability
- Two new OTel instruments (25 total, was 23):
flux_worker_loop_lag_seconds(histogram, one sample per health probe) andflux_worker_health_transitions_total(counter, labelledstate:unhealthy/recovered). Both recorded in the worker process. See Metrics.
Packaging and examples
- Full-stack compose example.
examples/docker/docker-compose.full.ymlin the Flux repository runs every role from the single official image — PostgreSQL, an event-dispatch server with auth/retention/observability on, general, docker-runner, and GPU-labeled workers, an MCP server, and opt-in agent and OTel-collector/Prometheus profiles — hardened (cap_drop,no-new-privileges) and requiring real secrets via environment. The rootdocker-compose.ymlremains development-only. See Docker. - New approvals example:
examples/approvals/standing_grant.py(a multi-region rollout gated withapprove --always). - Three schema migrations:
0008(executions.preferred_worker),0009(workers.metrics),0010(approvalscope). All run automatically on database open, as usual —flux db upgradefor explicit control.
0.53.0
This entry is a rollup of the 0.36–0.53 line — a run of releases that turned Flux from a single-server topology into a horizontally scalable one. Python floor is 3.12 (^3.12 in pyproject.toml; CI covers 3.12–3.14).
Multi-replica servers and dispatch
- Multiple server replicas are supported on PostgreSQL. Replicas coordinate through the database — there is no leader election to configure. Scheduler dispatch and retention run as fleet-wide singletons per cycle via PostgreSQL advisory locks; execution dispatch is double-assign-safe (
FOR UPDATE SKIP LOCKED); worker heartbeats persist toworkers.last_seen_at, so any replica can judge liveness and reclaim executions from a dead replica’s workers. The worker SSE stream still needs sticky routing at the load balancer. SQLite remains single-node — the server warns when a second worker registers against it. See High availability. - Event-driven dispatcher.
[flux.dispatch] mode = "event"replaces the legacy per-worker query loop with LISTEN/NOTIFY wakeups and batchedSKIP LOCKEDclaims — the scalable mode for large worker fleets.pollremains the default for now. - Worker capacity slots. Workers advertise
[flux.workers] max_concurrent_executions(default 16;0= unlimited) at registration; the server never assigns beyond a worker’s free slots. - Graceful drain on
SIGTERM. A stopping worker stops accepting work, finishes running executions up todrain_timeout(default 60 seconds), flushes terminal checkpoints, then exits. - Delta checkpoints and claim-generation fencing. Checkpoints ship only new events, backed by a durable outbox with retry. When an evicted worker’s executions are reassigned, checkpoints from the stale claim are rejected with HTTP 409, so a partitioned worker cannot corrupt state after the partition heals.
Execution model
- Pluggable runners. Each execution runs through a runner:
subprocess(the new default — one credential-less child process per execution, with a sanitized environment holding no bootstrap token, security settings, or database URL),inprocess(the worker’s event loop, lowest latency), ordocker(opt-in; one container per execution viadocker_image). Workflows pin one via@workflow.with_options(runner=...); workers advertise enabled runners at registration and dispatch matches on them. See Runners. - Transient durability.
@workflow.with_options(durability="transient")persists only the outer execution lifecycle — no task-level checkpoints, at-most-once semantics, no pause, approvals, or schedules. Built for high-frequency agent/mesh workflows. Acall()targeting a transient workflow object on the same worker takes an in-process fast path: ~2.3 ms median per hop versus ~526 ms server-relayed (disable with[flux.workers] transient_fast_path = false). See Durability.
Persistence and operations
- Alembic-managed schema migrations. Schema changes ship as migration scripts inside the package and run automatically when the database is opened; legacy
create_alldatabases are stamped at the baseline revision and upgraded in place, and on PostgreSQL the migration step is guarded bypg_advisory_lock. New CLI group:flux db upgrade|current|historyfor controlling timing explicitly. See Upgrades and migrations. - Execution-history retention.
[flux.retention]deletes terminal executions older thanretention_days(default 30). Off by default so upgrades never silently remove history; enable it in production or the event tables grow without bound. - psycopg v3 replaces psycopg2. The
postgresqlextra now installspsycopg[binary,pool] ^3.2— one driver for both the sync SQLAlchemy engines and the async LISTEN/NOTIFY listener. The install command is unchanged:pip install 'flux-core[postgresql]'. GET /readyreadiness endpoint. Performs a database round-trip and returns 503 when the database is unreachable. Point load-balancer membership at/readyand liveness at/health.
Security
/workers/registeris rate-limited (30/minute per client IP by default;[flux.workers] register_rate_limit), guarding the shared bootstrap token against online brute force.- The server refuses to start when auth is enabled but
execution_token_secretor the encryption key is unset — these used to fail mid-traffic instead. - Auth resolution cache. Token→identity and principal→permission lookups are cached per replica (
resolution_cache_ttl, default 30 seconds). - Worker API keys rotate themselves. Keys are minted with a TTL (default 7 days); on the first 401 after expiry the worker re-registers and gets a fresh key.
- Execution token TTL tightened from 7 days to 24 hours — the token is minted fresh on every dispatch and resume, so it only needs to outlive one continuous run.
- Pickled database columns are HMAC-signed with the encryption key. Back up
FLUX_SECURITY__ENCRYPTION__ENCRYPTION_KEYalongside the database, or signed data becomes unreadable after a restore.
Packaging
- One hardened Docker image for every role.
FLUX_MODEselects server, worker, or MCP server; the image runs as a non-root user (UID 1000) under tini and is published with semver version tags. Releases are auto-taggedvX.Y.Zafter the PyPI publish. - fastmcp 3.x (was ^2.5.1; the
from fastmcp import FastMCP, Clientimport layout is unchanged). New runtime dependencies:alembic ^1.14,slowapi ^0.1.9.
0.35.0
Released against Python 3.14.
0.35.0 is primarily a correctness release. It closes twenty defects that were documented as known limitations and provider drift in the 0.33.x line. No new public surface area — the workflow, task, agent, scheduling, and operate APIs are unchanged from 0.33.1.
Reliability fixes
- Retry backoff compounds.
task.retry_backoffnow applies as a real exponential multiplier — attempt N waitsretry_delay × retry_backoff^(N-1), capped at 600 seconds. Earlier releases reset the delay each retry-loop iteration so every retry waitedretry_delayflat. See Errors and retries. task.timeoutapplies to every attempt. Retries are now wrapped in the sameasyncio.wait_forenvelope as the first attempt, so the timeout bounds each retry, not just the initial call. See Timeouts.
Graph task fixes
- Conditional edges work. The edge predicate is now awaited correctly, so conditional edges route on the predicate’s actual result instead of always evaluating truthy.
- Cycle detection works.
Graph.validate()raisesValueErrorwhen the graph contains a cycle. See Graph task. choicereturns the element type. Thechoicebuiltin returns the selected element (annotatedAny) rather than a wrapper.
Scheduling and history fixes
flux schedule historyworks. The CLI no longer crashes on non-empty results and emits valid JSON even when the history is empty.ScheduleHistoryResponsetimestamps are populated.started_atandcompleted_atcarry real values on each history entry.- Schedule history is scoped to its schedule. History is keyed to the originating schedule rather than the whole workflow.
Observability fixes
- Failed task spans set ERROR status. A task that fails now produces a span with the OpenTelemetry ERROR status, so failures are visible in the trace tree.
- Trace context propagates to resumed and cancelled executions. The trace continues across pause/resume and cancel boundaries instead of starting fresh.
- OTLP exporter protocol is configurable. The exporter protocol can be set to
grpcorhttp. See OpenTelemetry.
Operations and configuration fixes
/healthreturns HTTP 503 when the database is unreachable. Status-code-only probes now fail correctly on a database outage; earlier releases returned 200 regardless. See Health checks.- Config precedence is correct. Settings in
flux.tomloverridepyproject.toml’s[tool.flux]table. FLUX_SECURITY__AUTH__ENABLEDis a real settable field. Enabling auth without a configured provider is now rejected at startup rather than silently accepted.- MCP transport default is
streamable-http. The config default and theflux start mcpCLI default agree onstreamable-http. - Workers handle
SIGTERMgracefully. A worker that receivesSIGTERMshuts down cleanly instead of being killed mid-claim.
LLM provider fixes
- Anthropic structured output is API-enforced.
response_format=on an Anthropic-backed agent is enforced through a forced tool call, so the model returns JSON matching the schema rather than being asked to in the prompt. - OpenAI honors
max_tokens. The OpenAI agent path passesmax_tokensthrough to the provider. - Ollama
reasoning_effortpreserves granularity.low,medium, andhighare mapped distinctly instead of collapsing to a singlethink=True. - Ollama warns when it drops
response_format. When tools are configured, Ollama’s API cannot also takeresponse_format; Flux now logs a warning when it drops the constraint instead of doing so silently.
Removed
flux start console. The Textual TUI subcommand was removed. The operator surface is the CLI, the REST API, and the MCP server.
0.33.1
Released against Python 3.14. The shape:
- Durable workflows via
@workflow/@taskdecorators with replay-from-event-log semantics. Postgres in production, SQLite for development. - Server-side scheduling via
@workflow.with_options(schedule=cron(...))or imperative schedule registration through the REST API. Schedule auto-creation on register via_auto_create_schedules_from_source. - Agent path through
agent()fromflux.tasks.ai. Providers: Anthropic, OpenAI, Ollama, Gemini. Streaming supported on all four. Tools, response formats, memory (working / long-term), sub-agent delegation, and skills are all implemented. - MCP server via
flux start mcp— 22 generic management tools plus a separate workflows-as-services path that exposes registered workflows as named MCP tools. - REST API with token authentication, namespace-scoped RBAC, and bootstrap-token onboarding.
- OpenTelemetry for traces, metrics, and logs via optional
opentelemetry-*extras.
Limitations in 0.33.1 — all fixed in 0.35.0
The 0.33.1 line shipped with the following defects. They are documented here for users still on 0.33.1; every item below is fixed in 0.35.0 (see the 0.35.0 section above).
- Flat retry backoff.
task.retry_backoffdid not compound — every retry waitedretry_delayflat. Fixed in 0.35.0. task.timeoutdid not apply to retry attempts. Only the first attempt was wrapped inasyncio.wait_for. Fixed in 0.35.0.Graphconditional edges and cycle detection. Conditional edges always evaluated truthy;validate()did not detect cycles. Fixed in 0.35.0.choicereturn type. Did not return the selected element type cleanly. Fixed in 0.35.0.flux schedule historycrashed on non-empty results;ScheduleHistoryResponsetimestamps were unpopulated; history was workflow-scoped rather than schedule-scoped. Fixed in 0.35.0./healthreturned 200 even when the database was unreachable. Fixed in 0.35.0 — returns 503.- Config precedence.
pyproject.toml[tool.flux]could win overflux.toml. Fixed in 0.35.0. FLUX_SECURITY__AUTH__ENABLEDwas not a real field. Fixed in 0.35.0.- MCP transport default disagreed between config and CLI. Fixed in 0.35.0 — both default to
streamable-http. - Workers did not handle
SIGTERMgracefully. Fixed in 0.35.0. - Failed task spans did not set ERROR status; OTLP protocol was not configurable; trace context did not propagate to resumed/cancelled executions. All fixed in 0.35.0.
- Anthropic structured output was prompt-enforced, not API-enforced. Fixed in 0.35.0.
- OpenAI agent path ignored
max_tokens. Fixed in 0.35.0. - Ollama
reasoning_effortlost low/medium/high granularity; Ollama droppedresponse_formatsilently under tools. Fixed in 0.35.0 — granularity preserved, and the drop now logs a warning.
Three design points were documented as limitations of the 0.35.0 line rather than bugs. Two have since shipped:
- Single-scheduler topology. Shipped since: server replicas now coordinate through PostgreSQL advisory locks, so the scheduler runs safely in every replica. See High availability and the 0.53.0 entry above.
- No Alembic migrations. Shipped since: the schema is now Alembic-managed with automatic upgrades. See Upgrades and migrations and the 0.53.0 entry above.
- No automatic saga rollback. Still true in 0.56.0 —
task.with_options(rollback=...)runs only for the failing task; the saga pattern is manual orchestration, tracked on the Roadmap. See Rollback and compensation.
0.33.0
Predecessor of 0.33.1. The 0.33.x line is the first version that ships to this documentation site under flux-core. Archived release notes pending — for the diff against 0.33.1, refer to the GitHub release page once it is published.
0.32 and earlier
Pre-doc-site history. Archived release notes pending; the canonical record is the version sequence in pyproject.toml and the git tags on the Flux repository. A single archival entry covering the 0.31–0.33 transition will be backfilled here.
Release notes are hand-curated and will be auto-generated from git tags plus a future CHANGELOG.md in a later release. For the moment, the version of Flux you are running is the version this documentation is verified against — to check at runtime:
import importlib.metadata
print(importlib.metadata.version("flux-core"))
If that prints something other than 0.56.0, treat any documented behavior as a starting point and verify against your installed version.