Skip to main content

Every way to run this, and what each costs

flux can run in a lot of shapes: entirely on your laptop, confined by the OS, served over HTTP to a thin client, embedded in another Rust program, or compiled to WebAssembly. Most of these already exist, and nothing collected them — so people tend to discover the shape of the product by accident.

This page is a decision aid, not a brochure. Its value is that it says what each option costs.

A topology is four independent choices

Four things decide the shape, and they move independently — which is exactly what makes this confusing without a page:

  1. the runtime — what decides: authorization, the approval prompt, policy.
  2. the system — where things happen: file IO, process spawning, network egress.
  3. the model — a local provider or a hosted one.
  4. the workspace — whose files the agent is actually editing.

The rule that makes the first two legible is stated in flux's own substrate design:

flux-runtime decides whether something may happen. flux-system is where it happens.

They are peers, not layers. That is why "run the agent here and land the effects there" is even expressible — you are putting that boundary across a network. It is also why the two questions readers actually get wrong are where are my files and where does the approval prompt appear: those follow from axes 4 and 1, and they do not have to move together.

How to read the status column

This page is only worth having if it is honest about what is built, so every row carries one of three words:

wordmeaning
shipsin the released binary. The command shown runs today.
partialsome of it ships. The row names which part, and what is missing.
proposeddesigned and filed, not built. No command runs. The proposed spelling is shown in a plain block, never a runnable one.

Commands in sh blocks on this page are checked against the shipped CLI by a test, so a renamed flag breaks the build rather than quietly turning a documented topology into a lie.

At a glance

TopologyStatusRuntime (decides)System (does)Your filesApproval prompt
Fully localshipsyour machineyour machineyour machine, unconfinedyour terminal
Local, OS-sandboxedships on Linux and macOSyour machineyour machine, confinedyour machine; only the workspace is writableyour terminal
Local runtime, containerized opsproposedyour machinea containerundecidedyour terminal
Local runtime, remote systemshipsyour machinethe remote hostthe remote workspace is canonicalyour terminal, which is the whole point
Served agent, thin clientshipsthe serverthe serverthe server'syour choice: over the network (--remote-approval), or nowhere (--yes)
Embedded in your programshipsyour processyour processyour process's working dirwhichever approver you install
Portable WebAssemblypartial — language core onlythe embeddernothing; there is no host authoritynonenone; there is nothing to approve
Hosted / multi-tenantpartial, and earlyflux-exchangeflux-exchange: HTTP invoke plus generated socket channelsnot applicablenot applicable
ssh to the boxships — it is not a flux featurethe remote hostthe remote hostthe remote'syour terminal, over the ssh session

The rest of this page is one section per row.

Execution placement matrix

“Supports Docker” or “supports Kubernetes” is ambiguous unless the job is named. Flux keeps four different jobs separate:

  • Manage infrastructure: inspect or mutate an existing Docker daemon or Kubernetes cluster.
  • Place guarded effects: choose where one approved file, process, or network operation happens.
  • Place agent workers: choose where a whole fleet worker process runs.
  • Provision isolation: create, pool, snapshot, or destroy the container, pod, VM, or microVM that supplies the boundary.
TargetManage infrastructurePlace guarded effectsPlace agent workersProvision isolation
Native hostnot a separate integrationships — local is the defaultships — guarded process workers; externally managed workers exist as a library runtimeships — Bubblewrap on Linux, Seatbelt on macOS
Dockerships temporarilyDocker plugin; its future connector executes only through ExchangeproposedC-397proposedA-124BYO remote system — run the daemon in an operator-supplied container
Kubernetesships temporarilyKubernetes plugin; its future connector executes only through ExchangeBYO remote system — deploy the daemon in a pod; there is no native pod-per-effect backendproposedA-125BYO — the cluster/runtime owns pod isolation
microVMno management integrationBYO remote system — run the daemon inside the guestexternally managed workers are possible through the runtime port, but not CLI-configurableBYO — Flux does not provision, pool, snapshot, attest, or destroy microVMs

The Docker and Kubernetes plugins are today's temporary management integrations, not placement backends. C-500 migrates their vendor-specific surfaces to connectors executed through Exchange while preserving that distinction. Installing one does not make --remote send integration calls to that target, and it does not turn fleet.start into a container or pod scheduler. Conversely, the generic remote-system daemon can run inside a container, Kubernetes pod, VM, or microVM without teaching the local runtime how that boundary was provisioned.

See Deploy a remote execution system for the shipped BYO profiles and their TLS, token, workspace, persistence, and readiness contract.

Fully local

Status: ships.

Everything on your machine: the runtime, the system, the workspace, and — unless you point it at a hosted provider — your credentials. Nothing leaves the box except the model call.

flux tui

Bare flux with no arguments opens the REPL instead; flux run "…" does a single headless turn.

  • Where your files are: your working directory. The agent reads and edits the tree you started it in.
  • Where the approval prompt appears: your terminal. The TUI raises a modal; the plain CLI asks y/a/N. Closing the channel counts as a denial.
  • Good for: everyday coding, and anything where you want to see each effect before it lands.
  • What it costs: the blast radius is your machine. An interactive run is not confined by default — see the next row.

Local, OS-sandboxed

Status: ships on Linux and macOS. No Windows backend exists yet.

The same topology, with the effects confined by the operating system: bubblewrap on Linux, Seatbelt (sandbox-exec) on macOS. Confinement is applied at a single spawn choke point, and that is enforced rather than asserted — an architecture lint fails the build if any crate spawns a process outside it.

flux tui --sandbox
flux run --sandbox "run the test suite"

Unattended runs opt in for you. Anything with no human at the keyboard starts at the confined profile: --yes runs, flux app run --serve, a .flux program in daemon mode, flux review, and flux plugin call. Only an explicit --no-sandbox (or FLUX_SANDBOX=off) turns that back off, and it prints a warning when it does.

  • Where your files are: your machine, but the sandboxed child sees only the workspace as writable.
  • Where the approval prompt appears: unchanged — your terminal. Confinement and approval are different mechanisms; the sandbox bounds what a permitted effect can reach.
  • Good for: unattended work, untrusted repositories, anything you would not want to run unwatched.
  • What it costs: under the unattended profile the network defaults to closed, so anything that fetches — a package install, a dependency resolve — fails until you grant it. On a platform with no backend, an unattended surface refuses to start rather than running unconfined; an interactive one runs unconfined.

See Safety for the envelope this sits in, and Configuration for the [sandbox] table.

Local runtime, containerized ops

Status: proposed. No container backend exists in the tree. Filed as C-397.

The idea: keep the runtime and the approval prompt local, and land process effects in a container instead of on your host. It is the cheapest way to get a real blast-radius boundary without a network hop.

Do not confuse this with the fleet's worker placement, which does exist. AgentRuntime decides where a worker agent runs; this row is about where a single guarded operation lands. Today the shipped worker runtimes are an OS process and an externally-managed one — neither is a container.

Local runtime, remote system

Status: ships. Designed in remote-agents; the substrate half is C-399.

The one where the agent you drive is here and the system it acts on is there — you approve on your machine and the effect lands on another host, which may itself be a container, pod, VM, or microVM. The local mode remains the default; this is an explicit operator-selected execution target, never a mode the model may select. Flux ships the daemon and protocol, not the surrounding container image, Kubernetes manifest, or microVM provisioner; those are BYO deployment profiles.

# On the execution host. Use a CA-issued certificate in production.
export FLUX_REMOTE_SYSTEM_TOKEN='generate-a-long-random-token'
flux system serve --workspace /srv/project --bind 0.0.0.0:8790 \
--cert server-cert.pem --key server-key.pem

# On the machine where you run the model and approve effects.
export FLUX_REMOTE_SYSTEM_TOKEN='the-same-token'
flux tui --remote https://worker.example:8790 --remote-ca worker-ca.pem

The same --remote turn controls are available on flux run, flux tui, flux fork, flux record, and agent-backed app runs. Omit the flag and flux uses the native local system exactly as before. The bearer value comes only from the environment variable named by --remote-token-env (default FLUX_REMOTE_SYSTEM_TOKEN); it is never accepted in the URL or as a literal CLI flag. Publicly trusted certificates need no --remote-ca. A private/loopback endpoint also requires the explicit global --allow-private-net grant.

The v1 daemon serves one canonical workspace over authenticated HTTPS, with authenticated WSS for managed processes and guarded network streams. The TUI keeps the endpoint and the canonical remote workspace in its header for the entire session. Port-aware coding operations are available; tools that still own native-only resources are hidden and refused in remote mode, never run on the local machine as a fallback.

Operation compatibility comes from one typed declaration on the live catalog:

  • local-control-plane — coordinator work such as model/session/UI control stays local and is valid with either a native or selected remote execution system.
  • selected-execution-system — guarded effects use the selected system, so they land locally in native mode and on the remote host in remote mode.
  • native-system-only — the implementation still owns native host state or handles and is hidden and refused whenever a non-native target is selected. An unannotated downstream operation receives this fail-closed classification automatically.

These categories are compatibility metadata, not authorization. Every available operation still passes through authorization, approval, and guarded IO.

  • Where your files are: the remote workspace is canonical. Every project-relative read, write, discovery operation and process cwd uses that tree. There is no implicit synchronization with the directory from which the local TUI was started. A local editor sees a different tree unless you explicitly mount or attach it to the remote workspace.
  • Where the approval prompt appears: your terminal. That is the property this topology exists to keep, and the reason it is not the same thing as serving an agent.
  • What it costs: latency on every operation, a deliberately smaller operation catalog while native-only integrations are ported, and a new trust question. Refused, Unserved, Unreachable, and Unknown are structurally distinct. Unknown means the daemon accepted an effect but cannot prove its terminal result; flux does not automatically retry a mutation.

The daemon stores a bounded delivery ledger at .flux/remote-system-delivery.json in the remote workspace. It contains operation ids, request fingerprints, states, and timestamps—not arguments, results, or secret values. Replaying the same id cannot execute the effect twice; reusing it for a different request is refused.

The split below extends the native-substrate contract in Concepts rather than redefining it. "Remote" does not mean "mostly the same":

GuaranteeRemote classificationWhy
Default-deny authorization and approvaltravels — stays localThe local flux-runtime dispatches and approves before sending an operation. Authorization and approval stay local.
Model selection and provider credentialstravels — stays localModel calls are made by the local runtime; the remote system receives no provider key.
Workspace path confinementbecomes the remote system's responsibilityOnly the remote host can resolve symlinks and physical paths against its canonical root. The local side can validate spelling but cannot prove the remote filesystem result.
Argv-only spawning, cleared child environment and output capsbecomes the remote system's responsibilityThe daemon must use its guarded System; a report from an arbitrary endpoint is not proof that it did.
OS sandbox and egress guardbecomes the remote system's responsibilityBubblewrap/Seatbelt and DNS/IP pinning act where the process or socket is created, which is now remote.
Redaction and evidence recordingtravels, with weaker provenanceThe local runtime redacts returned bytes and records them, but the record is remote reported, not locally observed. The remote must also redact its own diagnostics and logs.
Tool credentials needed by a remote effectchanges meaningThe credential store and model credentials stay local, but an operation-bound secret crosses the encrypted link when the selected remote operation must use it. The daemon may hold it only in memory for that operation and must never log or persist it.

The last row corrects an easy but unsafe shorthand: remote mode cannot promise that every credential value stays on the local machine while also promising that an authenticated process or request runs on the remote one. The operator must treat the selected remote system as able to observe any secret explicitly delivered for an approved effect.

For a full remote shell and editor rather than split runtime/effect placement, ssh remains the simpler option.

Served agent, thin client

Status: ships — both halves, server and client.

Here the whole agent runs elsewhere — planning, model calls, tools — and you talk to it. This is the Docker-CLI shape: a thin client, and the far side does everything.

Choose the approval posture — it is not chosen for you

flux's envelope is authorization → approval → guarded IO. Approval is the only one of those three with a human in it, so which posture it runs under is a decision, and both answers are legitimate:

# Ask me, over the network, before each guarded effect.
flux app run --serve 127.0.0.1:8787 --remote-approval

# Do not ask. Constrain through policy, the sandbox floor and budgets instead.
flux app run --serve 127.0.0.1:8787 --yes

There is no default. Starting a served agent without one of those flags is refused, because guessing is how someone ends up unattended without meaning to.

--remote-approval parks every guarded effect and waits for a human:

curl -s localhost:8787/approvals
# { "approvals": [ { "id": "ap_…", "fingerprint": "…", "tool": "write",
# "subjects": ["report.txt"], "mutating": true, … } ],
# "timeout_secs": 120 }

curl -s -X POST localhost:8787/approvals/ap_… \
-H 'content-type: application/json' \
-d '{"fingerprint":"…","decision":"allow"}'

Three properties worth knowing before you build a client against it:

  • An effect nobody answers is denied. The wait is FLUX_APPROVAL_TIMEOUT_SECS (default 120), and there is no "wait forever" — an unbounded wait is a wedged turn, not a decision.
  • You must echo the fingerprint. It is the effect in canonical form, and it is what binds your yes to the effect you were shown; a decision that names a different one is refused with 409. A decision is also single-use, so it cannot be replayed onto the next effect.
  • The operator boundary is one shared token (or open loopback). The routes sit behind the server's auth, and an unauthenticated non-loopback bind is refused outright. Principal auth is refused for this posture: one global queue would otherwise let one principal answer another's effects. Anyone holding the shared token can make the agent do anything it is authorized to do.

--yes never asks. That is not safety switched off: authorization policy, the mandatory fail-closed sandbox floor on this surface, and the resource budgets are still doing the constraining, and for high-autonomy work — research, security hardening, long exploration — that is often the better design. Interrupting an agent for every effect is not caution if nobody is going to read the prompts.

Either way, treat the endpoint's authentication as a real boundary and do not serve one onto a network you do not control.

What this looked like before

Until this landed, no approver in flux spoke over a network — every one was local (the terminal prompt, the TUI modal, the sub-agent approver). A served agent therefore had no posture to pick: it was --yes or it did not start. If you are running a served agent today, you have been running the unattended posture. That may well still be the right one for your job — but it is now something you choose rather than something that was chosen for you.

The rest of the surface

Either posture exposes a /.well-known/agent-card.json discovery card, POST /a2a JSON-RPC with message/send and message/stream, and a session REST subtree (POST /sessions, GET /sessions/{id}, POST /sessions/{id}/messages, plus an SSE stream). See HTTP API and A2A.

Connect to one:

flux a2a http://127.0.0.1:8787 "summarize the open bugs"

With no prompt it opens an interactive session against the remote agent instead.

For a full-screen client — panes, the approval sheet, an interrupt that reaches the remote turn — attach the TUI instead:

flux tui --attach http://127.0.0.1:8787

--attach is this row; --remote is the row above it. The TUI refuses them together, and the attached header says which machine you are on. Note the consequence that surprises people most: an attached conversation lives in the server's session store, so it is not in your flux sessions and cannot be flux replayed locally. The full artifact-by-artifact split, and the affordances the A2A wire does not carry (tool calls and results do not cross it), are in A2A.

  • Where your files are: the server's. The agent edits the tree it was started in; your local files are not in the picture at all.
  • Where the approval prompt appears: wherever your client puts it. Under --remote-approval the server parks each effect at /approvals and it is your client's job to show it to a human (flux tui --attach raises them in its ordinary approval sheet); under --yes nobody is asked.
  • Good for: giving a team or another agent access to one configured agent; agent-to-agent work.
  • What it costs: the model choice and the credentials live on the server, and under --remote-approval every guarded effect costs a network round trip and a human. If what you wanted was "my terminal's approval prompt, someone else's blast radius", this is still the wrong row — that is local runtime, remote system.

Embedded in your program

Status: ships.

Another Rust program takes flux as a library — codewandler-flux-sdk — and drives the agent in-process. There is no CLI in the picture.

let client = flux_sdk::Client::builder()
.model("anthropic/opus")
.build(provider, ".")?;
let out = client.run("Summarize the README").await?;
  • Where your files are: whatever root your program hands the client.
  • Where the approval prompt appears: wherever you put it. You install the approver, so it can be a terminal prompt, your own UI, a policy function, or nothing.
  • Good for: building a product on flux rather than around it.
  • What it costs:the SDK does not inherit the CLI's sandbox floor. A library has no argv to classify, so auto_approve(true) does not imply confinement — the embedded client reads the ambient FLUX_SANDBOX setting, which is off unless you set it. If you auto-approve in an embedded program, ask for the sandbox explicitly.

See the SDK overview.

Portable WebAssembly

Status: partial, and narrower than "flux in the browser". Filed as C-268.

What ships: the Flux-Lang evaluation core compiles to wasm32-unknown-unknown behind a three-function ABI, and a parity test proves the wasm build agrees with the native one on the same source.

bash scripts/build-portable-wasm.sh

What does not ship: everything that touches the world. The portable core is handed no host authority at all — its operation catalogue is empty and every dispatch returns a denial. So literals, expressions, formatting, field access and control flow evaluate; tools, the model, IO and the agent loop do not exist there. Serving the guarded port through host imports is the next step and has not started.

  • Where your files are: there are none.
  • Where the approval prompt appears: nowhere — there is nothing to approve, by construction.
  • Good for: evaluating Flux-Lang inside an embedder that cannot spawn a process.
  • What it costs: it is not an agent. Do not plan on it as one yet.

Hosted / multi-tenant

Status: partial, and early. This is a separate project, flux-exchange, which holds credentials and knows about tenants.

Exchange is the only official integration executor. Core Flux remains useful without it for the language, agent loop, SDK and built-in tools; official external integrations are unavailable when Exchange is unavailable, with no local connector or plugin fallback.

What exists today (v0.16.0): a loopback service with OIDC sign-in, per-tenant connections and grants, canonical Service Account authentication, an HTTP invoke endpoint, durable workflow drafts/runs, and supervised generated connector WebSocket channels delivered through authenticated /api/subscribe. Three facts decide whether you can plan on it:

  • Flux embeds the one-shot Exchange client. With an operator-configured origin and Service Account token, authenticated effective-catalogue operations are mounted at turn boundaries and invoked through Exchange. Subscribe and lifecycle consumption remain later slices.
  • A multi-tenant deployment refuses to execute on the host. HTTP is shareable because the effect leaves the machine; process spawning, container exec and raw sockets consume the host's own identity and filesystem, so a shared deployment serves only HTTP and remote runtimes and refuses the rest. "Runs ops per tenant" today means HTTP connector operations, not agent execution.
  • Rich outbound connector dispatch is still planned. The generated socket channel is a real hosted protocol slice, but Docker, Kubernetes, SQL, process/container execution, arbitrary streamed results, and leases wait on the C-500/X-111 cross-repository program.

General webhooks/polls, leases, rich-runtime streams and isolated worker execution are described in the design and are not built.

ssh to the box

Status: ships — though it is not a flux feature, and that is the point of listing it.

Install flux on the remote machine and run it there over ssh. This is a legitimate answer, it costs nothing, and for a lot of people it is the right answer.

ssh you@remote-host

…then any of the local topologies above, on that box.

  • Where your files are: the remote's — all of them, consistently. There is no synchronisation problem, which is the failure mode that sinks most "run it over there" tooling.
  • Where the approval prompt appears: your terminal, over the ssh session.
  • Good for: a beefier machine, a machine with the data on it, a throwaway VM.
  • What it costs: the model call, your API credentials and the agent's whole context live on the remote box. Your local editor is not looking at those files. One session per host, and no single view across several.

The model axis is independent

The three axes above say nothing about which model answers, and that choice is orthogonal — you can pair any of these topologies with either kind of provider.

  • Hosted provider — Anthropic, OpenAI, OpenRouter and friends. Your prompt and context leave the machine, whatever the rest of the topology does. A fully local, fully sandboxed run still makes a network call to the provider.
  • Local provider — an OpenAI-compatible server on your own machine, e.g. flux run -m ollama/qwen2.5-coder:7b "…". Nothing leaves the box, at the cost of a smaller model and your own hardware.

The pairing that surprises people: a served agent with a local provider still runs the model on the server, not on yours. Moving the agent moves the model call with it.

See Providers.

Choosing

  • Coding on your own machine, want to see each effect → fully local, add --sandbox when you step away.
  • Unattended or untrusted work → local, OS-sandboxed. You already get it on the unattended surfaces.
  • Someone else's machine should do the work, and you accept losing the approval prompt → served agent, or just ssh.
  • You want the approval prompt to stay yours while effects land elsewhere → that is local runtime, remote system. Use --remote when you specifically want the local approval and model boundary; use ssh when moving the whole terminal is simpler.
  • Building a product on flux → embedded, and set the sandbox yourself.