Skip to main content

Modules, composite ops & programs

A .flux file can hold one flow, a reusable module, or an application declaration. This page covers the module-level surface: multiple flows, composite op declarations, and the program declarations that the app host understands.

Multi-flow modules

A file with several flows is a module. Each flow header sits at column 0; blank lines and comments between flows are allowed:

flow fetch-and-grep
hits = grep(glob: "*.rs", pattern: "TODO")
return hits

flow summarize(text: String) -> String
summary = task(role: "summarizer", task: "Summarize:\n{text}")
return summary

Because the flow header is always required, any single-flow snippet is valid in a multi-flow file without modification.

Composite ops

A module may declare reusable custom operations with op. A composite op has typed parameters, optional metadata, and an ordinary Flux-Lang body — it is callable like any other op from flows in the same module:

op repo-health(path: String, prior: Ctx) -> Health
description "Check git state and summarize failures"
risk "medium"
idempotency "idempotent"
effects [read, process, local_system]
expose true

status = git_status()
tests = cargo_test(args: ["--workspace"])
ctx pack
purpose "repo-health"
budget 8000
include prior, status, tests
return { status, tests }

The metadata lines, all optional:

keymeaning
description "…"what the op does — shown in catalogs
risk "…"declared risk level
idempotency "…"whether re-running is safe
effects [...]declared semantic effects
limits {...}declared operational limits
expose trueoffer the op beyond this module
view "…"display hint

Rules that keep composite ops safe and analyzable:

  • The safety envelope still applies. A composite op is a scoped sub-flow; its inner calls dispatch through authorization, approval, and guarded IO exactly like top-level calls. Wrapping an operation in an op never launders its risk.
  • await is rejected inside composite ops.
  • Recursion is invalid — direct or indirect. Composite ops are compositions, not general functions.

Composite ops are how a module grows a vocabulary: name a multi-step pattern once, call it like a built-in everywhere else.

Reuse across sessions — the flows home. Beyond the module they are declared in, composite ops saved as .flux files under .flux/flows (project) or ~/.flux/flows (global) auto-load as callable ops on every run — so a ~/.flux/flows/greet.flux that defines op greet(...) is callable by name anywhere. Agents use flow_list / flow_run; from a terminal, flux flow list shows the same catalog and flux flow run <name> runs a saved flow directly. Supply declared parameters with --inputs '{"key":"value"}' or repeatable --arg key=value; opt in to natural-language mapping only when wanted with --map-inputs "…".

The complete discovery, precedence, CLI-input, and op.register scope rules live in Saved flows and custom operations.

Program declarations

Beyond flows and ops, a module may declare a whole multi-agent application: permissions, agent, agent_loop, channel, datasource, trigger, and journey declarations describe the capability ceiling, agents, their outer loops, channels, indexed data, and event-triggered journeys that tie them together — one typed .flux file for the entire app. A journey's optional agent attribute makes ownership executable: the fixed flow inherits that agent's model, persona, datasource scope, and capability narrowing. Secrets are declared as references (secret "ENV_VAR") and resolved at load time; values never live in the file.

agent_loop — authoring the outer loop

An agent_loop declaration is a named flow body that replaces the built-in adaptive turn loop. An agent selects one with a loop "<name>" attribute; an agent that declares none gets the host's adaptive preset.

agent_loop triage
intent = detect_intent()
found = explore(intent: intent)
ok = approve_batch(batch: found.batch)
done = execute_batch(batch: found.batch, receipt: ok)
return present_results(stage: done)

agent responder
model "sonnet"
loop "triage"

The stage ops it calls belong to the reflect group and are never offered to the model — see Agent-loop stages. Because the loop is an ordinary flow body, it is analyzed and budgeted like any other, and every operation it dispatches still crosses the same safety envelope.

Top-level and agent allow/deny lists contain exact operation names. The app list is the ceiling; an agent list may narrow but never widen it. tools remains the separate model-visible catalog.

Programs are run by the app host:

flux app run program.flux # or: flux run program.flux (auto-detected)

The declarations, the app runtime, the event bus, and a runnable example live in Multi-agent programs. The extra operations the app host registers for journeys (emit, send, ask, spawn) are listed in Operations.