0.57.0 - 2026-08-06
Release Notes
Added
-
A native workspace Board is now the complete cross-repository program authority (C-588).
A default.flux/board.tomlbinds member repositories and canonical refs, active milestone,
ordered program lanes, cross-repository dependencies, configured waves and planning documents.
Plainflux boardauto-selects it;board nextandfleet scheduleconsume the same validated
projection and never admit an unrelated ready story when a program catalogue exists. Board
planning config, Fleet execution config and mutable Fleet state now have disjoint schemas and
ownership, accepted decisions no longer surface as open attention, and workspace metrics report
real program stories, milestone lanes, waves and members instead of legacy placeholders. -
flux tui --fleet[=ROOT]is a native Board/Fleet operations surface (C-556, C-557, C-582).
Explicit attachment validates one Fleet root, opens the reserved main coordinator's isolated
store and exact recorded session, and journals conversation intake through accepted, delivered,
and completed/failed acknowledgements. A typed in-process projection supplies a responsive
attention rail plus bounded Overview, Board, Workers, Decisions, and Stats views from the same
durable readers and exactflux.board-stats/v1cube as the CLI; it does not capture ANSI, scrape
tmux, or spawn CLI subprocesses. Refresh keeps the last-good snapshot on failure. Only explicit
intake and a twice-confirmed open-decision choice may mutate state; push, release, deploy,
Fleet-apply, cleanup, and capacity changes remain outside the TUI. Desired/draining capacity is
shown as unavailable until canonical Fleet state carries it. -
The attached Fleet main now has a closed coordinator runtime instead of an ordinary coding
agent catalog (C-556). Fleet config binds an explicit operator-authored Flux-Lang loop; each
turn supplies only the current request and a bounded native Board/Fleet catalog, bypassing the
generic intent/explore and retained-history budget path. The parent can manage Board and Fleet
state or start onetask; that child uses a separately configured host-authored loop and an
independently closed read-only research catalog with no genericcreate_plan, shell, edit,
git-write, Board/Fleet mutation or nested delegation authority. Missing or invalid main/research
loop config refuses at startup instead of silently widening behavior. -
Every runnable agent now has a resolved, versioned loop binding (C-569). General omission
resolves to the explicitadaptive@1preset; CLI/SDK, role, nested-task, app and served starts all
cross the same validation boundary. Start, status, stream and terminal receipts carry bounded
profile/revision/source-digest/runner metadata, while resume reconstructs digest-addressed source
and refuses a live-session switch. Fleet task kinds resolve through operator-authored loop policy,
validate against the admitted capability ceiling, and snapshot exact source for message, restart,
resume and rework.fleet.runreceipts now also return admitted worker ids and wave linkage.
Changed
- Board and Fleet public docs now define the complete domain model. Concepts appears before
Coding, and the guides distinguish epics, stories, milestones, program lanes, configured waves,
dispatched wave instances, workers, handoffs, review, gates, apply and release. Compact diagrams
show story states, eligibility, isolated worktrees, review/rework and the separate publication
boundary.
Added
flux fleet reclaim [wave]reclaims a finished wave's build output, and its worktrees when they
provably hold no commit and no uncommitted change. Reclamation previously ran on acceptance alone, so
every wave that ended any other way — cancelled, parked, conflicted, gate-red — kept its target
directories and checkouts for as long as the fleet root existed. Disk is what caps how many workers can
run, so those are not free: the first run on a real fleet removed 47 stale worktrees and retained the
two that held work. Cancelling a wave now reclaims it in the same step. A wave that can still advance is
refused rather than reclaimed, since deleting a build it is about to use would cost work rather than
space.
Changed
- A Fleet agent no longer pays disk for incremental compilation it can never reuse. Each story gets
a fresh worktree and a fresh target directory that is discarded when the wave is reclaimed, so
incremental artifacts were written and then deleted without ever being read — roughly half of a
checkout's build output. Disk, not model concurrency, is what caps how many workers can run, so this
is directly a width change.CARGO_INCREMENTALalso joins the forwarded environment allow-list,
without which the variable was dropped between the agent process and thecargoit runs and setting
it had no effect at all.CARGO_TARGET_DIRis deliberately not forwarded: it is a path, and one
shared target directory is locked exclusively by cargo, which would serialize the parallelism the
fleet exists to provide. An operator's own rebuilds keep incremental compilation. flux board createcommits the document it creates, path-scoped, with--no-committo opt out.
Items are resolved at a git ref wherever a board is federated — a workspace member's stories are read
withls-tree/showat itscanonical_ref— so an uncommitted document is invisible to every read
that matters. Twenty-seven stories were filed, reported as created, and could not be scheduled: the
command had succeeded and nothing had happened. The commit names exactly the new path and never sweeps
in unrelated dirt, it goes to the current branch rather than a side branch (a side branch would
reproduce the same invisibility), it does nothing outside a git repository, and it refuses rather than
committing into an in-progress merge or rebase. The reported envelope carries the commit sha.
Fixed
Added
- A repository may declare a
preparestep that regenerates its candidate's derived artifacts before
the gate. Some checked-in artifacts are derived from a whole wave rather than from one story — a
documentation mirror, a generated index — and they belong to the candidate, not to any story. Two
stories regenerating one artifact collide, and regenerating it on either branch alone produces an
artifact missing the other story's contribution. Observed on a real wave whose gate refused the
candidate withembedded docs are stalewhile both stories were correct in isolation. The step runs
once, after every cherry-pick for that repository and before its gate, and whatever it regenerates is
committed into the candidate so it survives into the accepted tag. A preparation failure is recorded as
that repository's failure rather than surfacing later as the stories being wrong.
Fixed
- Accepting a candidate no longer requires the repository to have stood still. Two refusals were left
over from when applying MERGED the candidate: a merge has to land on the base it was tested against and
it touches the working tree, so a moved canonical ref and a dirty checkout were both real hazards.
Acceptance now writes an annotated tag on the candidate and nothing else — it does not read the working
tree, moves no branch, and cannot be invalidated by unrelated commits arriving on the canonical ref.
Keeping the checks cost a green wave: one passed both repository gates and was then refused with "moved
from its pinned base" because ordinary work had continued during the hours the wave took, which means the
longer a wave is worth accepting the more certain it becomes that it cannot be. The invariant that does
matter is still enforced — the candidate branch must point at the commit that was gated — and the base it
was gated against is now recorded in the acceptance entry, because the later step that writes the
canonical branch has to re-gate against whatever that branch has become. Acceptance is not landing. - A concurrent write no longer throws away an entire integration. Integration is the longest operation
the fleet performs — cherry-picks, candidate preparation and a full repository gate, tens of minutes from
cold — and it writes state several times along the way. Any coordinator write inside that window lost the
compare-and-set and discarded the whole run: one real integration died onstale fleet revision 428; current revision is 429after both gates had already produced their verdicts. Each of those writes now
rebases onto current state, which is safe for the same reason it is safe for dispatch: the call replaces
the record of exactly one wave, and that wave is owned by the integration for its duration, with the
ownership recorded. - A retry no longer re-judges a repository whose candidate is already accepted. Acceptance is the end of
that repository's road — its candidate is pinned by a tag and nothing later in the wave can improve it —
so re-gating it spends the pipeline's longest operation to re-derive a known answer, and a re-gate that
came out red would make accepted work read as failed. Stale preparation evidence is cleared too, so a
retry cannot report work it did not do. - Reclamation no longer removes a worktree an unfinished wave still needs. Build output is regenerable
and always goes; a worktree is structure, andworktree_holds_workanswers "does this contain work?"
rather than "is this still needed?". Reclaiming a wave whose worker turn had failed therefore removed its
integration worktree — legitimately empty and at its pinned base — while a story worktree in the same
wave held 940 uncommitted lines, leaving the wave with deliverable work, nowhere to assemble a candidate,
and no operation able to rebuild the missing structure. Worktrees are now removed only for a wave that is
applied or cancelled. - A failed integration can be retried once its cause is fixed.
conflictandredwere terminal, so
a wave that failed kept a memory of failing and no laterfleet integratewould touch it — which made
fixing the cause pointless. Three delivered stories stayed unreachable after the defect that stranded
them was found, fixed and installed, refused with "not ready for integration". Retrying loses nothing:
the handoffs are still accepted and each integration worktree is reset to its pinned base first, which
the retry path must do or it refuses itself for a different reason than the original failure. The
guard worth keeping — never spend a second gate run on unchanged inputs — belongs to the candidate, not
the wave, and is now enforced by comparing the recomputed candidate with the one already gated. The
wave's verdict is also decided once, from the collected failures, rather than in each branch on its way
out: deciding it per branch is exactly how a path slipped through without setting one, leaving a wave
integratingwith its owner released and permanently wedged. - Clipped output keeps the end, where a failure explains itself. Captured gate output was clipped
from the head, and a repository gate emits thousands ofCompiling …andtest … oklines before
failing at the very end — so a failed gate's record filled its 16 KiB budget with progress and cut the
error off. On one real wave both streams were exactly 16385 bytes and the reason for the failure was
not recoverable from Fleet state at all. The budget is now split, two thirds to the tail, with an
explicit marker naming how many bytes were elided; completeness is never claimed silently. - Integration applies the worker's whole commit range, not just the commit its handoff cited. A
handoff names one commit and a story worker legitimately makes several — implementation, then its
record, which is the shape the contract asks for. Cherry-picking the cited commit therefore applied
only the LAST one and silently dropped everything before it: measured on one real wave, a two-commit
story contributed only its documentation commit and a five-commit story likewise. It surfaced as a
conflict, which was luck — a clean apply would have produced a candidate documenting code that was not
in it, and a gate that never compiles the missing part could even have passed it. The evidence already
assumed the range, since handoff verification computes the write set withdiff <base> <commit>, so
the record described a range the integration never applied. A handoff citing its own base is now
refused outright rather than producing an unchanged candidate. - A conflicted integration leaves its worktree reusable. The half-applied cherry-pick was kept on
disk, which bought nothing (the conflict evidence is recorded in state) and cost the retry: a wedged
worktree fails the next attempt's "clean at its pinned base" check, so the wave reported a different
reason the second time and could never recover on its own. - An interrupted integration can be retried.
integratingis transient and recorded no owner, so a
process dying mid-gate — the longest operation the fleet runs — left the waveintegratingforever
with every retry refused as "not ready for integration" and no escape but hand-editing state, which the
operating rules forbid. Integration now records its supervisor and releases it on every exit, and a
wave held by a process that is gone may be retried. - One repository's integration failure no longer discards another's candidate (C-630). Integration
assembles and gates one candidate per repository, but the first repository to conflict, gate red, miss
a gate argv, or hold a dirty integration worktree aborted the run for all of them — so a
flux-internal collision threw away an exchange candidate whose stories were independent and perfectly
integrable. Each repository's outcome is now recorded on its own, the error names both the repositories
that failed and the ones that produced a green candidate, andfleet apply <wave> --only <repository>
accepts a named candidate from a wave that is not wholly green. The per-repository green-gate
requirement is unchanged: this relaxes which waves may be asked, not what counts as accepted. A named
apply reports what it left behind, does not change the wave's own verdict, and does not reclaim storage
the remaining repositories still need. - A dispatch is no longer discarded by a concurrent write. Creating a wave already happened on disk
— a worktree and a branch per story — before the state write. If the coordinator or an operator wrote
Fleet state in that window, the compare-and-set lost and the whole dispatch was thrown away with its
worktrees already created and nothing recording them; observed as a dispatch failing the moment the
coordinator accepted an intake. The write now rebases onto current state, which is safe here precisely
because the call only ever adds keys nothing else could have written: a fresh wave id and its fresh
worker ids. Re-running the action instead would be wrong, since worktree creation refuses an existing
wave root and integration would spend a second gate. - Fleet state can now tell a live worker from a dead one. A turn runs synchronously in the process
that recordedworking, so that process dying is the one case where no terminal status is ever
written: a worker readworkingfor hours with nothing behind it, inflating the active count, keeping
its wave out of reaping, and forcing the driver to reimplement liveness by scanning/procbecause the
answer was not recoverable from state at all. The supervising process is recorded when a turn starts
and cleared on every terminal transition — on the wave path as well as the single-turn path, the wave
path being the one that produced the stuck record — and an active record whose supervisor is gone projects as
interrupted— which also routes it to the attention list, where it belongs. Records written before
this keep their recorded status rather than being downgraded. Pid reuse can still fool the check, which
is exactly the previous behaviour, so this is a strict improvement rather than a guarantee. - A handoff is no longer refused because a sibling test target filtered to nothing.
cargo test -p <pkg> <filter>runs the package's lib unittests and each of its integration-test binaries, so the
filter matches in one target and reports0 passed; 0 failed; N filtered outfor every other. The
"did this run any tests" check was a substring search over the combined output, so it saw the
non-matching target and concluded no test had run — silently refusing a handoff whose cited test
genuinely ran and passed, and parking a wave that held delivered, committed work. Every summary is now
counted, in both libtest and nextest vocabulary, and the run counts as executed if any target ran a
test. - A lost update no longer reports itself as a contradiction. Fleet's compare-and-set printed
stale fleet revision 392; current revision is 392when a concurrent writer had already produced that
revision with different content. Same message for two unlike failures, and unactionable for the one it
described least. The two cases now say what happened and what to do. - Two stories in one repository may now write the same file. Integration refused any wave whose
stories' write sets intersected — a proxy for "these commits will not combine", and within one
repository wrong far more often than right. A wave of two delivered stories was parked because each
had appended an entry to one changelog, exactly as the worker contract asked; and since nearly every
story in a subsystem edits the same module, the proxy made more than one story per repository per
wave impossible. The real test already followed: each accepted commit is cherry-picked into the
integration worktree, where a genuine conflict fails with the conflicting files, git's own stderr and
the preserved candidate. Disjoint edits to one file now integrate; overlapping edits are refused by
git rather than guessed at. One writer per story remains enforced. - A running wave is now visible while it runs (C-599, C-602). A wave wrote durable state exactly
twice — once before its first worker started, once after the last joined — and the surface's refresh
token hashed only those files, soflux tui --fleettook zero snapshots for a wave's entire
duration and a working fleet looked frozen. The supervisor now projects each worker's stream to a
bounded activity sidecar as it arrives, the refresh token includes it, and a worker's rail entry
shows live progress instead of its previous turn's events. The projection is structural only —
event type, operation name, coarse outcome — so tool inputs, result bodies and the prompt never
reach it. - The transcript no longer silently drops characters (C-341 revised). The vertical scrollbar is
drawn as an overlay into the transcript's own rect, so any character wrapped into the last column
was overwritten with nothing on screen to indicate the loss — a path, SHA or command output read off
the screen could simply be wrong. That column is now reserved. This reverses C-341's "the overlaid
scrollbar must not consume transcript width": one column of width is a smaller price than corrupted
text. - Board and Fleet results summarize their shape instead of their envelope (C-599). Every
board.*/fleet.*card previewed the first bytes of a bounded envelope — the identical
{"data":{"bounded":true,"byte_limit":262144,…prefix for every call, and the widest possible line.
Cards now read31 workers · 16 waves · r276, with the full envelope one expand away. - The Fleet header names the wave that is actually running (C-599). Active-wave selection tested
the inverse of a four-item terminal list, so any unrecognised status — including
agent-turn-failed— counted as active, andBTreeMapiteration returned the oldest match. A
long-dead failed wave held the header while another ran, with the same panel's worker list
disagreeing. Selection is now a closed allow-list of in-flight statuses, newest first by wave
number. Worker rows also prefer the current wave within a status class, so finished workers stop
crowding out the running one. flux fleet run --dry-runvalidates without writing (C-594). It promised "validate and return
the proposed result without writing" and skipped worktree creation accordingly — then wrote a loop
binding snapshot into a story worktree that deliberately did not exist, so every dry run failed with
workspace root …/stories/<ID>: No such file or directorybefore returning a topology.- An operator-authored segment is no longer told its actions are only captured (C-597). Every
ai_segmentinherited the adaptive planner's system prompt, which states that Flux may capture an
action instead of executing it and directs the model to hand a plan back throughfinalize_plan.
That is correct for the plan-then-approve CLI path and wrong for an authored segment, where effects
really do execute through the approve/execute batch path — and Fleet story workers complied with
the prompt: one made 35 read-only calls, attempted no effect, and reported it could not run
anything, while holdingwrite,edit,bashandgit_commitin its admitted ceiling. Authored
segments now carry an execution contract instead; the evidence and grounding discipline is
unchanged, and the adaptive planner's prompt is untouched. - An authored segment that reaches its history ceiling no longer destroys the turn (C-595).
A Fleet story worker committed its complete deliverable and then had the whole turn discarded by a
retained-history budget it crossed by 0.43%, so Fleet reported the one story that actually
delivered as the wave's only failure. Crossing the ceiling in an authoredai_segmentnow sheds
the oldest tool-result payloads intotool_result_omitteddigest receipts — the same shape an
oversized single result already used — and keeps running with the most recent exchange verbatim.
When elision cannot free enough, the segment returns its evidence ledger as a result instead of an
error.ai_segmentacceptsmax_history_bytesto raise the ceiling for a long implementation
loop. Ordinary adaptive turns are unchanged. - A failed worker turn now records the session and receipt it already proved (C-595). The turn's
parsed session id and event stream — including any commit the loop made before it failed — were
read successfully and then discarded, leaving afailedworker with no discoverable deliverable
and nofleet reworkre-dispatch (that path requires a recorded runtime session). Both are now
persisted on the failure path alongside the error. - An operator-authored
ai_segmentnow runs with the exact tool ceiling it declares (C-593).
The adaptive intent router's four-family cap bounds what the model may select; it was also
applied to the deterministic family union an authoredtools:ceiling expands to, so any authored
loop spanning five or more families failed before its first tool call withadaptive capability declaration selected N distinct families; the maximum is 4. A Fleet story-implementation loop
naming read, write, git, shell, datasource and system operations could never start a worker. The
cap now applies only to a model-declared selection; the independent operation-count and
schema-character budgets still bound both paths, and an adaptive state serialized by an older
runtime still resumes capped. - Workspace-root errors now name the offending path.
Workspace::newandWorkspace::with_root
reported a bareworkspace root: No such file or directorywhileWorkspace::new_optional
already named its path, leaving failures reached throughSystem::rerootedwith nothing to go on. flux fleet statusanddashboardnow return the boundedflux.fleet-status/v1operational
projection instead of embedding durablelast_turnreceipts, tool events, answers and intake
bodies. Current lifecycle state—not stale receipt text—drives active and attention counts, and
terminal cancelled/completed workers no longer keep old errors in the attention rail. The compact
response keeps exact worker, wave, Board ref, session and repository identity plus targeted
inspect commands for deeper evidence. Targeted Fleet inspection is bounded too: a 256 KiB
structural response ceiling preserves terminal facts and replaces oversized evidence with
indexed omission metadata referencing Fleet state or the event journal, including digests for
omitted strings.- Structured JSON tool results are now redacted as JSON values before reserialization. A
credential-shaped substring inside an escaped model-stage state can no longer consume an escape,
invalidate the object, and make an authored loop see its typed result as opaque text; results that
need no redaction remain byte-for-byte unchanged. - An agent attached through
flux tui --fleetcan now enumerate durable native Fleet workers with
the boundedfleet.agentsoperation instead of asking the operator to supply guessed worker ids.
The attachment-only read uses the same state asflux fleet agentsand the Workers view and stays
distinct from the pointwise transient-processfleet.worker_statusoperation. Both census paths
return role, task kind and resolved loop identity without embedding instructions or historical
turn receipts. The validated attachment pre-authorizes this bounded census, while an
operator-authored deny still wins. - Fleet-main Board/Fleet reads now use bounded native projections and require no approval. In
particular,fleet.statusreads the compact inspect snapshot rather than serializing historical
worker turns and waves into the model context. Typed mutations retain revision, idempotency and
acknowledgement guards, and the installed catalog cannot refresh back into general coding,
plugin, pane, eval or transient-process operations between turns. - Durable loop bindings now canonicalize the set-valued required-operation and runtime-feature
fields at every reconstruction and live-session comparison. Sessions admitted by an older build
resume when only insertion order differs, while source, digest, profile, revision, runner and
entry-point drift still require an explicit new session. - Fleet-attached TUI startup and refresh now build Board/Fleet projections away from the terminal
event loop. A cheap durable-state token avoids redundant rebuilds, explicit loading/unavailable
states replace a frozen screen, and a failed later refresh keeps the last-good view marked stale. - Concurrent
fleet.isolatecalls now serialize Git's shared worktree-administration mutation
while retaining disjoint checkout allocation, preventing partially written.git/worktrees
metadata from breaking a parallel Board/Fleet wave. - Release checksum verification accepts cargo-dist sidecars with trailing blank lines while still
requiring one exact lowercase digest/filename record and at least one newline terminator. - Candidate artifact preparation now verifies and reuses the controller's successful exact cut-CI
run instead of rebuilding, testing, linting and formatting the same release a third time.