Skip to main content

Operations

Operations are the callable boundary between Flux-Lang and the host runtime. A call node names an operation; the host decides whether that operation exists, how arguments are validated, and what approval or policy checks apply.

The engine advertises a catalog built from the live tool registry. Plugins project additional operations into the same catalog.

The catalog is also evidence-gated: tool groups surface when the workspace shows their signal (the cargo_* ops appear in Rust workspaces, go_* alongside Go modules), and the generic bash op is opt-in — plans are steered toward dedicated, accurately-gated ops.

Arguments below are named; pass them directly (read(path: "…", limit: 100)), or use the bare form for a sole required parameter (read("README.md")). Optional arguments are in [brackets]. Ops marked approval may pause for user approval depending on the active policy.

Files, search, and web

opargumentsriskdescription
readpath[, limit, offset]lowRead a file (line-numbered), a list of files, or a glob pattern
read_manypathslowRead several files at once, sections headed per path
greppattern[, glob, literal, max_results, path]lowRegex search; literal: true for plain substrings
globpattern[, path]lowList files matching a glob pattern
file_statpathlowSize, line count, mtime
path_existspathlow"true"/"false" — branch on file presence with when/unless
writepath, contentmedium, approvalCreate or overwrite a file
editpath, old_string, new_string[, replace_all]medium, approvalReplace a string in a file (exact-match first, then progressively looser anchoring)
patchpath, editsmedium, approvalSeveral line-anchored edits in one call
appendpath, contentlow, approvalAppend to a file, creating it if absent
sourceslowEnumerate the datasource's sources: entity types + record count per source
searchquery[, source, entity, harness, limit]lowKeyword search over the indexed datasource; harness restricts to one local coding harness's history, and is advertised only where the host opted that in
getsource, entity, idlowFetch one datasource record in full by its address
listsource[, entity, offset, limit]lowEnumerate a datasource source's records, paged
relationsource, entity, id[, rel]lowFollow a datasource record's typed links
batch_getsource, entity, idslowFetch several datasource records in one call
web.fetchurl[, raw]mediumRead a URL as a document: HTML becomes condensed Markdown, PDFs become extracted text; raw preserves the body
web.crawlurl[, max_pages, max_depth, max_total_bytes]mediumCrawl a small site or section: from a seed, follow same-host links breadth-first (bounded by max_pages/max_depth, and optionally a total-content max_total_bytes budget that stops the crawl early), returning each page as condensed Markdown
html_to_markdownhtmllowPure conversion of an HTML string to condensed Markdown; no network access
http.requesturl[, method, query, headers, body, timeout]medium, approvalArbitrary HTTP request returning the record {status, headers, body} — select a field with resp.body.data.id. body is the parsed JSON when the response is a JSON object or array, and the raw capped text otherwise; non-2xx remains a result. Pass parameters as the query record — each value is percent-encoded, so a value carrying & or = cannot add a parameter
browser.open[url]medium, approvalStart a headless-Chromium session and return a non-visual page digest
browser.gotosession, urlmedium, approvalNavigate an existing browser session and return a delta
browser.snapshotsession[, view]lowRe-observe a session (full, actions, or content)
browser.actsession, action[, ref, value, full]medium, approvalClick, type, fill, select, press, scroll, navigate, or go back using digest refs
browser.closesessionlowClose a browser session and its Chromium child
web.search`queryqueries[, max_results, providers]`low
sqlite_querydb, sql[, limit]lowRead-only SQLite query (limit caps rows, default 200)
now / cwd / home_dir / sys_infolowClock, workspace/home paths, and host metadata — no shell needed

http.request and web.fetch ride out a rate limit rather than handing it straight back. A 429 Too Many Requests is retried up to three times, honouring Retry-After in either of its forms (delta-seconds or an HTTP-date) and otherwise backing off exponentially from 500 ms with a little jitter. Retrying is safe for any method, a POST included, because a 429 says the far side received the request and declined to act on it — which is why a 503 is not retried: it makes no such promise. Every wait stays inside the request's own timeout, so a retry that would overrun the budget returns the 429 instead of blocking past it, and cancelling the turn ends the wait immediately. When a request did wait, the rendered result says so — rate-limited, retried 2 times over 3.4s — so the latency is never unexplained. The retry happens wherever the request is made, so a selected remote substrate waits next to the service it is calling rather than across the link.

All native web operations share the [private_net] web scope. Public destinations are allowed by default; private/internal destinations require an explicit grant. Browser operations register in every host but are advertised only when a Chromium binary is discoverable. web.search is the model-facing alias projected by the first-party websearch plugin; its destinations are additionally bounded by that plugin's manifest and its Tavily credential is injected host-side.

Processes and toolchains

opargumentsriskdescription
bashcommand[, timeout_secs]high, approvalRun a shell command — opt-in, off by default
proc.runprogram[, args, timeout_secs]high, approvalOne argv-only process, no shell, cleared env
taskrole, taskmedium, approvalDelegate to a sub-agent role
cargo_check / cargo_build / cargo_test / cargo_clippy / cargo_fmt[package, args, …]medium, approvalThe Rust toolchain (Rust workspaces)
go_build / go_test / go_vet[package, args]medium, approvalThe Go toolchain (Go workspaces)
python_run / pytest[script, module, path, args]medium, approvalPython scripts and tests
npm / node_runargs / script[, args]medium, approvalNode tooling
make[target, args]medium, approvalRun make (surfaces on a Makefile)

Git

opargumentsriskdescription
git_statuslowWorking tree status
git_diff[path, staged]lowUnstaged (or staged) diff
git_log[limit]lowRecent commits
git_hunkspath[, context]lowList the unstaged diff as individually addressable hunks
git_stage_hunkspath, hunks[, context]mediumStage selected hunks by id, leaving the rest of the file unstaged
git_stage / git_unstagepathsmedium / lowStage or unstage files
git_commitmessage[, body]mediumCreate a commit
git_push[branch, remote]mediumPush to a remote
git_checkoutbranch[, create]mediumSwitch or create a branch
git_branchname[, delete]mediumCreate a branch without switching to it, or safe-delete one (-d refuses unmerged work and the checked-out branch)
git_mergebranch[, no_ff]highMerge a ref into the current branch (no_ff forces a merge commit); a conflict is a recoverable error naming the conflicting files — the merge is aborted and the tree restored, never left half-merged. Refuses outright if a merge is already in progress, and aborts nothing in that case: the in-flight resolution may be uncommitted work
git_revertcommit[, mainline]highRevert a commit by appending its inverse (mainline, usually 1, for a merge) — a new commit undoes the target, never a reset; requires a clean tree, and a conflicted revert is aborted and left clean, naming the conflicting files
git_worktree_enterhighMove this agent context into an isolated temporary git worktree (requires a clean main; creates a generated flux/worktree/* branch)
git_worktree_leavehighMerge the worktree's committed work back into main (--no-ff, guarded by an aborted trial merge), remove the worktree and branch, restore the original root

None of these ops rewrites history. git_revert undoes a commit by adding a new one on top, so the commit it reverts stays in the log and nothing already pushed is invalidated.

git_revert changed meaning — the old one is now git_reset

git_revert used to name the improvement loop's snapshot op, which hard-resets the working tree and discards uncommitted changes. That op is now called git_reset, and git_revert is the true revert described above. There is no alias: a flow that calls git_revert(snapshot) must be changed to git_reset(snapshot).

The rename matters because the call still looks valid. git_revert(snapshot) now asks git to append the inverse of that commit instead of resetting to it — a different outcome, and one that errors on a dirty tree rather than clearing it. If you have a flow that restored a snapshot, rename the call.

Cognition ops

The cognition pack splits into pure data-shaping ops — deterministic, no IO, never pause for approval — and model-backed ops that make one structured model call each. The model-backed ops carry a network effect and are advertised only when the host registers a provider for them.

Pure:

opargumentsdescription
needask, require[, done_when]Build a Need artifact — an explicit statement of missing info
gapsclaims, needReport a Need's still-unmet required fields
comparea, b{added, removed, common} over two arrays
dedupeitems[, by]Remove duplicates, first-seen order; by accepts dotted paths
sortitems[, by, order]Stable sort by a field or dotted path, asc/desc
topitems, nFirst n items
skipitems, nDrop the first n items
mergelistsConcatenate an array of arrays
map`items, pathexpr[, vars]`
filteritems[, where, vars, by, equals]Keep items by expression predicate or dotted field/equality
flattenitems[, depth]Flatten nested arrays up to depth levels
joinitems[, sep]Join stringified items into plain text
splits[, sep, trim]Split text into a JSON array of strings
sumitems[, path]Sum numbers, optionally plucked from a dotted path
count_byitems, pathCount items by dotted path, sorted by count desc then key
group_byitems, pathGroup items by dotted path in first-seen key order
anyitems[, where, vars]"true" when any item is truthy or matches an expression
allitems[, where, vars]"true" when all items match; empty lists are vacuously true
hasitems, value"true" when the array contains value by JSON equality
pickitems, keysKeep only listed object keys; accepts one object or an array of objects
omititems, keysRemove listed object keys; accepts one object or an array of objects
merge_objobjectsShallow-merge objects left to right; later keys win
coalescevalues[, default]First value that is neither null nor ""; otherwise default or null
keys / valuesitemObject keys or values in deterministic order
len / first / lastitemsCount, first item, last item
regex_matchs, patternReturns "true" or "false" if s matches the regex pattern (ReDoS-free)
regex_extracts, pattern[, group, all]Extract text matching pattern; returns first match or null, or all matches with all: true
citeclaimsA markdown citation list, one line per claim

Ops that select an existing string hand back the string itself. regex_extract (single match), first, last and coalesce bind the bare text, with no surrounding quote characters — so an extracted URL can be passed straight to an op that fetches one. Anything they select that is not a string (an object, an array, a number, a boolean, null) comes back as JSON, which the runtime reads back as structured data. Ops that build a new value are unaffected: split and keys still return arrays, and regex_extract with all: true still returns an array of matches.

Examples:

authors = map(items: issues, path: "author.username")
open = filter(items: issues, vars: { "min": 2 }, where: "it.state == 'opened' && it.upvotes > min")
all_pages = flatten(pages)
rest = skip(items: candidates, n: 1)
report = join(items: lines, sep: " | ")
hosts = split(s: raw_hosts, sep: ",", trim: true)
total = sum(items: invoices, path: "amount")
by_status = count_by(items: issues, path: "state")
grouped = group_by(items: issues, path: "author.username")
has_bug = has(items: labels, value: "bug")
all_green = all(items: checks, where: "it.status == 'ok'")
slim_issues = pick(items: issues, keys: ["iid", "title", "state", "web_url"])
public_issue = omit(items: issue, keys: ["author_email", "raw_payload"])
merged = merge_obj([defaults, overrides])
assignee = coalesce(default: "unassigned", values: [issue.assignee.username?, issue.author.username?])
field_names = keys(issue)
field_values = values(issue)
has_error = regex_match(pattern: "ERROR", s: log_line)
when has_error
alert(msg: "Error detected")
version = regex_extract(group: 1, pattern: "v(\\d+\\.\\d+\\.\\d+)", s: "flux-cli v1.2.3")
emails = regex_extract(all: true, pattern: "\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b", s: body_text)

Model-backed:

opargumentsdescription
ai.extractfrom[, ask, schema]Extract typed items (e.g. Claims) from free text
ai.rankitems[, by]Reorder items by a natural-language criterion
ai.judgeclaim[, evidence]Adjudicate a claim into a Verdict
ai.reasonask[, ctx]Free-form reasoning over a context pack
synthclaims[, format, cite]Synthesize a cited Answer from claims
ai.rewritetext[, style]Rewrite text in a requested style

These produce and consume the prelude artifact typesClaim, Evidence, Verdict, Answer, and friends — so multi-step reasoning pipelines stay typed.

Second opinion

opargumentsriskdescription
consultquestion[, context, model]lowAsk a DIFFERENT model for a second opinion — pure advice, no tools

consult is the one op that deliberately calls a model other than the agent's own — typically a stronger or differently-biased one — for a hard sub-question. It takes a question plus caller-supplied context and an optional provider/model override, makes exactly one model call, and returns the answer as text. It carries no filesystem, process, or network authority beyond that single call, so it adds no new authority to the safety envelope; the reply enters context as untrusted content. Only surfaced when an operator has configured a default target ([consult] model in .flux/config.toml) — see configuration — so an unconfigured workspace never sees a churn-prone catalog entry. A per-turn call cap ([consult] max_calls) keeps it a cheap escape valve, not a council of models.

App orchestration ops

Registered only by the flux app run host for multi-agent programs — journeys use them to drive the event bus and channels:

opargumentsdescription
emitevent[, payload]Publish an event to the bus (fires matching triggers)
sendchannel, messageSend a message to a named channel
askchannel, messageSend and return a correlation id
spawnrun[, input]Run a named journey to completion and return its result

Fleet ops

task delegates to a local sub-agent and waits for it. The fleet.* ops are the half task cannot express — hand work to a remote flux worker (a flux serve instance reachable over A2A) without waiting, then poll or stop it. A coordinator can therefore keep many workers in flight and reconcile them later.

opargumentsdescription
fleet.dispatchworker, task[, role, context_id]Send a task to a remote worker and return its task id without waiting. A worker that replies synchronously returns a null task id plus its answer, instead of an id that would be polled forever
fleet.statusworker, task_idRead a dispatched task's current state, whether it is terminal, and its final text
fleet.cancelworker, task_idStop a dispatched task. An already-finished task reports that it was not cancelable
fleet.isolateitemCreate branch impl/<item> in its own git worktree and return the checkout path — a per-item isolated workspace for one local worker. Unlike a worktree session it does not move the caller's own working root, so one call per item in a wave is legal. Requires a clean checkout and a free branch name; removing the worktree afterwards is the caller's job
fleet.startitem[, worktree, context_id, model]Start a flux worker for one board item and return the endpoint to dispatch to. worktree confines it to an isolated checkout; the returned context id resumes the same worker session later. Reaching the returned endpoint needs --allow-private-net, since it is a loopback address
fleet.worker_statusworker_idReport whether a worker is starting, live, or dead — with its exit code and the tail of its own output when it died. The worker's liveness, not a task's state
fleet.stopworker_idStop a worker started by fleet.start. An already-exited worker succeeds; an unknown worker id is an error
fleet.agents[limit]On an explicitly attached native Fleet main only, list bounded durable worker admissions and current statuses without requiring known worker ids. This is separate from transient A2A/process workers

The worker address is an argument, not configuration, so it is model-reachable and gated as such. Every call resolves the endpoint through the same egress guard as web.fetch before any request, and the approval subject is the worker's origin — never a wildcard. An address flux cannot parse reports no subject at all, which forces an approval prompt rather than matching a broad grant. These ops carry no standing private-network grant: they are not part of the web egress scope, so reaching a worker on a private address needs the explicit --allow-private-net override. A worker behind flux serve's bearer token is not reachable yet.

fleet.status is never served from the operation cache — observing the change since the last poll is the point of a status call.

fleet.agents is installed only for the main agent started by flux tui --fleet. Its result is capped at 100 worker records, reports the untruncated total, and omits worker instructions and turn bodies. The validated attachment pre-authorizes this read, while an operator-authored deny rule still wins.

That attachment replaces the colliding transient fleet.status and fleet.cancel tools and closes the model-facing catalog to these native coordinator services plus bounded research task:

opargumentsdescription
board.show{}Show the authoritative workspace Board and planning documents
board.getidRead one exact namespaced item
board.next[limit]List dependency-satisfied ready items in deterministic order
board.check{}Validate Board configuration and story contracts
board.start / board.unblockid[, guards]Apply the exact guarded Board transition
board.blockid, reason[, guards]Block an item and record why
board.comment / board.evidenceid, text[, guards]Append durable item context or evidence
fleet.status{}Read a compact durable lifecycle snapshot without historical turn bodies
fleet.schedule{}Read the dependency-aware schedule derived from the Board
fleet.runitems[, prepare_only, guards]Prepare and launch a wave for 1–10 exact Board refs
fleet.messagetarget, message[, wait, guards]Deliver an acknowledged message
fleet.cancel / fleet.resumetarget[, guards]Control one exact durable target

Safe native reads are pre-authorized unless an operator deny wins. Mutations keep revision, idempotency, and acknowledgement guards. The parent has no general coding tools; its task child is independently limited to read-only workspace and git inspection, without shell, edits, git writes, Board/Fleet mutation, or nested delegation. [main].research_loop forces the operator-authored child loop, overriding generic role-loop defaults such as create_plan.

Work board operations are not in this catalog, because they do not exist until a program asks for them. A first-class board declaration generates the board operations named after that binding, so a board declared as board yields board.listboard.record_evidence while one declared as queue yields queue.listqueue.record_evidence. Nothing is callable without the declaration. They are documented with the declaration that creates them, in Work boards and the fleet.

Endpoints

Registered as one evidence-gated group when a kubeconfig is present or the persisted endpoint store was non-empty at session startup. See Endpoints.

opargumentsdescription
endpoint.discoverproduct[, query, limit]Ask installed discovery providers for ranked weak endpoint references
endpoint.listList endpoint records known in the session registry
endpoint.infoidInspect one endpoint record and its credential location
endpoint.selectidReturn one model-safe EndpointRef for reuse in another operation
endpoint.importidPersist a known record to ~/.flux/endpoints.toml (approval-gated local write)

Hosts

Registered as one evidence-gated group, surfaced when named execution-substrate bindings are declared ([[host]] config or the hosts store). Weak references only: backend kind, address, availability and a credential presence marker — never a value.

opargumentsdescription
host.listList the session's named execution-substrate bindings
host.infoidInspect one host binding: backend kind, address, availability, labels
host.probeidSide-effect-free identity check: substrate identity and, for a remote backend, the negotiated protocol version
host.metricsidThe binding's own condition — CPU, load, memory, swap, disk, uptime, temperature, fans — as typed readings; an unmeasurable metric is explicitly unavailable, never zero

Agent-invoked commands and skills

Registered as one evidence-gated group, surfaced only when the session discovers at least one command file or skill explicitly marked agent-triggerable: true in its own frontmatter (default false — human-only stays the default). See Claude compatibility.

opargumentsdescription
command.invokekind, name[, arguments]Invoke a discovered command file (kind: "command") or skill (kind: "skill") by name — only when it is policy-permitted, discovered in this session, AND explicitly agent-triggerable. A command expands $ARGUMENTS/$1..$9 and returns the substituted body as prompt text (no nested execution); a skill returns its body. Any missing gate is a clean refusal, never a partial run

Panes on the terminal

Surfaced only when the host running the session installed a pane channel — the interactive TUI does; a headless flux run, the HTTP server and SDK embeddings do not, and their models never see these ops. The decision is taken once, when the session's catalog is assembled, so the advertised tool set never changes mid-session.

A pane is a durable container for status or results the user should keep in view (a build's progress, a checklist, a table of findings) — not a place to put the answer, which stays in the reply. The agent proposes where a pane sits and how long it lives; the surface owns geometry, colour, bounds and the mark that identifies a region as agent-authored, and the payload carries no styling field at all.

data is one object with exactly one key naming its shape: rows (header, rows), kv (pairs), log (lines), progress (label, done, total), tree (roots) or markdown (text). slot is left/right/bottom/overlay (default right) and lifetime is turn/session (default session).

opargumentsriskdescription
pane.openid, title, data[, slot, kind, lifetime]LowOpen a pane under id, the handle later calls address. Re-opening a live id replaces that pane rather than adding a second one; ids beginning host: belong to the surface's own panes and are refused
pane.updateid, dataLowReplace an open pane's content — the whole payload each time. An id that is not open (closed, or turn-scoped after the turn ended) is dropped by the surface
pane.closeidLowClose the pane opened under id; closing one that is not open is not an error. turn-scoped panes close at the end of the turn, and no pane outlives the session

Permission rules may scope a pane by name (pane.update:build), and pane content passes the same redactor as every other tool result before it reaches the screen.

Typed user interaction

The local terminal and TUI can expose one schema-driven question operation. Headless, served, stream-JSON and app surfaces omit it, so an agent never waits on a human where no question UI exists.

opargumentsriskdescription
user.askprompt, schemaLowAsk through the attached UI and wait for a schema-valid value. Boolean, single-choice, multi-choice and simple form schemas use native controls; unusual schemas fall back to validated JSON. Returns an explicit submitted value or cancelled status

Questions and approvals are separate: answering a question cannot authorize an operation. Schemas and answers are size-bounded, validated on both sides of the UI, and refused if they request or contain secret material. Audio prompts use opaque host-owned asset references and work only in an SDK host that declares audio support; the stock terminal surfaces are text-only.

Model-invoked skills

Opt-in Claude-style progressive skill disclosure (--skills-model-invoked / [skills] model_invoked): every discovered skill's name+description is surfaced compactly, and the model loads a body on demand instead of every skill's full text being injected up front. Off by default — manual --skill activation stays the (measured cheaper) default path. Advertised only when the opt-in is on and at least one loadable skill was discovered; a skill declaring disable-model-invocation: true never enters this catalog. See Model-invoked skills (opt-in).

opargumentsdescription
skill.loadnamePull one skill's full body into context by exact name. Idempotent and persistent: once loaded, the skill behaves like an explicitly --skill-activated one for the rest of the session

Flows

Discover and run reusable flows and composite ops stored under .flux/flows (project) and ~/.flux/flows (global) — see Where flows live:

opargumentsdescription
flow_listList the flows and composite ops in the flows home, each with its description and params
flow_runname|path[, inputs]Run exactly one stored-flow name or workspace-relative .flux path; path source is reread for every call, an inputs object is seeded as $key binds, and the flow is checked against the current operation catalog before guarded execution. Returns the resolved path, flow name, and seeded input keys as a route receipt
flow_rendersource|name[, view]Render Flux-Lang as a syntax-highlighted SVG — inline source or a stored flow name; view: "source" (default) renders the highlighted source, view: "tree" the execution-path plan tree. Returns SVG markup inline for surfaces that can't highlight .flux (READMEs, Slack, docs, chat)
op.registersource, scope[, replace, expose]Validate and register one composite op for the turn, session, project, or global scope

Evidence and strict-review helpers

These deterministic helpers are registered with the built-ins. The default loop uses the evidence family internally; strict-review flows use the review family directly.

opargumentsdescription
observekind[, data]Append a structured observation to the current run's evidence log
evidence[kind]Read all observations, or only one kind
metricsSummarize tool calls, errors, and iterations from the evidence log
review.normalizefindingsNormalize raw reviewer output and quarantine malformed entries as gaps
review.aggregatefindings[, files, reviewers]Deduplicate, rank, and summarize findings into a stable review report

Scheduled wake-ups

opargumentsdescription
schedule_wakeupin_secs, promptRegister a future wake-up on this session: after in_secs, the session resumes with prompt

Medium risk, LocalSystem effect. Off by default and absent from the catalog entirely until [wakeup] enabled = true — see the configuration reference for the horizon and pending-count bounds, and flux wakeups list | cancel in the CLI reference to inspect and revoke them. Enabling the table does not grant the op: registration still needs approval-gated host.write authority.

Agent-loop stages

These belong to the reflect group, which is never surfaced into a model-facing catalog — the model cannot call them. They exist so the agent turn loop can be authored in Flux-Lang rather than hard-coded, and you only write them when supplying your own loop via [agent] loop.

opargumentsdescription
detect_intentturn contextDetect the turn's intent and resolve capability signals into a durable IntentSet
exploreexploration stateContinue evidence gathering and native-schema action proposal; may return more work
approve_batchbatchRequest aggregate approval for one immutable ActionBatch; returns a session-bound one-shot receipt
execute_batchbatch, receiptConsume a matching receipt and dispatch every operation through authorization and guarded IO
ai_segmentscope, exit conditionHand a bounded run of model turns to the loop under a capability scope and an explicit exit condition
present_resultsstage artifactRender a terminal adaptive-stage artifact into channel-neutral answer text

The approve/execute split is the safety envelope made explicit: approve_batch produces a receipt bound to one immutable batch, and execute_batch refuses anything the receipt does not match. See The agent loop and Durability for ai_segment's role in bounded adaptive work.

Improvement loop

The ops the self-improvement flows orchestrate. They are registered in every session, so a flow can call them directly.

Status

The Improvement pillar is de-prioritized and on hold (since 2026-07-06). The machinery below is real and runnable; the headline claim — a repeatable, grader-confirmed gain — is not yet proven. Use these ops to measure and audit, not on the assumption that the loop reliably improves the harness. See Improvement.

Running and scoring evals

opargumentsdescription
eval_runadapter[, limit, model, tasks]Run a benchmark suite against the flux binary; returns {adapter, pass_rate, scalar, total, …}
eval_scalarreportThe report's score scalar as a plain string
eval_report_mdreportRender a report as categorized Markdown (headline score, per-task table)
eval_sessionsreportExtract session references [{id, db, task_id}] from a report
eval_adoptreportReturn a report unchanged — used to re-bind the baseline after adopting a candidate
score_comparecandidate, baseline"true" iff the candidate report is strictly better
score_compare_multicandidate, baseline"true" iff better overall and no member benchmark regressed
gradecriterionEvaluate a pass/fail criterion against the current workspace

score_compare_multi exists because a single combined score can hide a regression: it requires every member benchmark's pass rate and check rate to be at least the baseline's.

Mining and ranking improvements

opargumentsdescription
sessions_digestsessionsRender each session's run trace into a compact transcript for review
painpoints_collectsessionsMine pain-points — tool errors, retry loops, missing tools, churn — from session references
improvements_aggregatemined, reviewedCluster mined pain-points (mined) and review findings (reviewed) into ranked candidates
candidates_advancecandidatesDrop the consumed candidate and return the rest
candidates_emptycandidates"true" iff the candidate list is empty
improve_logrecordAppend a timestamped round record to .flux/eval/improve-log.jsonl

Applying a round, under guard

opargumentsdescription
change_implementtasks[, limit]Implement each derived task by spawning a worker sub-agent; returns a per-task summary
gate_check[build, test, clippy, fmt, timeout_secs]Run the dev gate (build/test/clippy/fmt) and return "true" or "false"; each step is individually toggleable
guard_protectedsnapshotRestore grader/suite/loop/CI paths to the round snapshot after the worker runs
git_snapshotCapture HEAD for later revert; errors if the working tree is dirty
git_resetsnapshotDestructive — hard-reset the working tree to a snapshot, discarding the round's changes; refuses a snapshot it cannot verify
git_tagname[, message]Tag the current commit (name is a prefix — the short HEAD sha is appended; annotated when message is given)

guard_protected is the anti-cheat step: it restores the grader, the suite, the loop, and the CI config after each worker run, so a round cannot raise its own score by editing what measures it. git_reset carries the Destructive risk tier and is approval-gated accordingly. It is a blanket restore — reset --hard plus an unscoped git clean -fd, which deletes untracked files outright — so it checks a precondition first, guaranteeing that a reset can only rewind within this checkout's own line of history. The snapshot must carry clean: true (set by git_snapshot, and only after it found the tree clean) and its head must be an ancestor of the current HEAD; a snapshot from a divergent line is refused with the working tree listed and left untouched. Only the second check is unforgeable — nothing verifies that a clean: true payload really came from git_snapshot — so treat the guarantee as "rewinds stay on this line", not "only this round's work is discarded". guard_protected needs no such check on which paths it may touch: every path it restores or removes is an explicit pathspec filtered through the protected set, so it cannot reach outside it.

Cutting a release

The automatic path is examples/release-cut.flux: a deterministic program over release_plan, release_verify_versions, and release_cut. It uses already-reviewed [Unreleased] notes and calls no model. The remaining two ops support the optional model-assisted release-note rehearsal in examples/release.flux. These are separate, narrow ops rather than proc.run calls because fixed argv and a fixed path set are authority you cannot mistype.

opargumentsdescription
release_planThe last v* tag, the commit subjects and diffstat since it, and the host's bump decision + next version
release_verify_versionsRun scripts/check-crate-versions.sh (fixed argv); errors with the offending protocol-line crate named
release_parse_notestextStrictly parse the scribe's textual JSON contract into typed release-note fields; normalizes one canonical json fence, rejects surrounding prose and schema drift; pure, with no external authority
changelog_insertfile, body, [section], [apply]Insert markdown under a changelog's ## [<section>] heading, deterministically and idempotently. apply defaults to false (preview)
release_cutbump, [apply]Cut with scripts/cut-release.sh <bump> (fixed argv), stopping at the local annotated tag. apply defaults to false

The irreversible decision stays in the host. release_plan derives the bump from complete commit messages plus the customer-facing Action needed signal — a ! or BREAKING marker means breaking, and breaking means a minor bump while flux is 0.y. crates.io is yank-only, and a wrong version cannot be withdrawn. The automatic release neither selects nor calls a model. In the optional manual notes rehearsal, a model may return a bump_opinion, but that field cannot change the host's number.

task() returns text, even when the prompt requests JSON. release_parse_notes is the explicit host boundary that accepts only the exact release-note object before the flow reads any of its fields.

changelog_insert addresses only CHANGELOG.md, WHATS-NEW.md, and website/docs/whats-new.md, resolving its target through the canonicalizing IO boundary first — so the model's prose is an input to the file, never the file's contents. release_cut never pushes and never publishes: the tag it leaves is local, and the existing tag-triggered workflows promote it.

The loop itself

flux's own agent turn loop is a Flux-Lang flow. Its hidden adaptive stages are detect_intent, explore, approve_batch, execute_batch, and present_results; they are never advertised to the model. See The agent loop.

Whatever the op, the rule is the same: every call crosses authorization, approval, and guarded IO. There is no trusted shortcut for any operation on this page.