All posts
Deep dive 26 August 2026 · engineering · agents

Why an agent on Flui stops and asks before it changes anything

More than forty write routes refuse an agent's call and turn it into a question a person answers. How that works, why the agent learns the answer by retrying, and why a standing permission can never widen what anyone may do.

Dawit Abate

An agent connected to Flui acts as the person who connected it. It holds their credential, so it can do what they can do.

That is precisely what makes it useful, and it is also the problem. The same key that deploys an application also adds machines you pay for, moves a database between clusters, or removes an application along with its storage.

There are two conventional answers. Give the agent fewer permissions, and it stops being able to help. Give it all of them, and you have handed a language model your production infrastructure and gone to bed.

Flui takes a third one: keep the agent fully capable, and put a person between the model and the act.

The call does not run

More than forty write routes are declared inside what we call the action cycle. When the caller is an agent, the call does not execute. It becomes a question addressed to a person.

The agent gets a refusal, and the refusal is not an error. It carries:

  • a request id, so the same question can be pointed at from a chat, a log or a link;
  • one plain sentence describing what was asked for — “add nodes to cluster this one”, not “infrastructure”;
  • a link to the page where a person answers;
  • when the action has a price, the fact that a price exists;
  • when the route declared it, what happens if the person allows it.

The wording the agent receives says, in as many words, that this is a wait and not a failure, that nothing was changed, and that varying the arguments to get around it will raise a second question rather than answer the first.

That last clause is doing real work. An agent that reads “permission denied” will reasonably try a different shape of the same request — that is what a competent agent should do with a denial. So the refusal has to say what kind of refusal it is.

Three answers, and one of them is a rule

The person answers one of three ways:

  • once — let this exact call through, remember nothing;
  • always — let it through and write down a standing permission;
  • no — refuse it.

The constraint on “always” is the part worth dwelling on: a standing permission can never widen what the person granting it may do. It is a narrowing of a pause, not a grant of authority. Someone who cannot add nodes themselves cannot create a rule that lets their agent add nodes. Without that property, the pause would be a privilege escalation channel wearing a friendly interface.

The answer arrives by retrying, not by waiting

Nothing holds a connection open while somebody thinks. The agent finds out by making the identical call again: if the person allowed it, it goes through.

This is a deliberate choice, and it buys two things.

A request can sit unanswered for an hour without a socket held open somewhere, which matters because the realistic latency here is not milliseconds — it is however long it takes someone to look at their phone.

And it works for an agent with no special client support at all. An agent driving Flui over plain HTTP meets exactly the same pause and learns the answer the same way as one with a purpose-built integration. There is no privileged client.

One address, and nothing held open

The same principle shows up one layer down. Flui speaks MCP at a single address: the installation’s API base with /mcp on the end. Every call is a POST with the key in an Authorization: Bearer header.

There is no session. Nothing is opened, nothing kept alive, nothing to re-establish after a network blip. Each request is answered on its own and the server built to answer it is thrown away. That is protocol revision 2026-07-28, which removes sessions and the opening handshake from MCP altogether — and clients built against the 2025 revisions are still served, from the same tools, so an older agent does not need upgrading before it can connect.

The endpoint answers POST only. A GET for an event stream is refused, politely, because there is no stream to give: nothing is ever published outside the reply to a call.

The key is the agent’s, not yours

Handing an agent your own credential is the easy thing to do and the wrong one. Your key carries everything you carry, it is the key your own terminal uses, and once it is in a model’s context you cannot take it back out.

So an agent gets a key of its own, and that key is where you decide how far it reaches. Two separate questions:

What kind of work — the key’s scopes. Deploying, reading logs, touching access.

Which applications — a key can name specific applications, specific projects, or both. The two lists are alternatives rather than requirements: an application is reachable the moment either covers it.

Flui stores a hash of the key, never the key. That is not a policy that could be relaxed later — after minting, the value does not exist anywhere on the installation, so nobody can show it to you again, including whoever runs the place. Lose it and you revoke it and mint another. There is no recovery step and there is not meant to be one.

Why this is the interesting half of “agent-ready”

It is easy to build a product that lets an agent change infrastructure. It is much harder to build one where, six months later, somebody can still read what the infrastructure is and how it got that way.

That is the whole reason the desired state lives in a file. An application is described by flui.yaml — what the CLI reads, what the dashboard writes, what an agent changes. A change made in a conversation lands somewhere that diffs, versions and reverts, and there is no separate agent-shaped path into your installation that behaves differently from the one a person uses.

Agentic interaction, deterministic infrastructure. The first half is what makes heterogeneous infrastructure approachable. The second is what keeps it yours.

Try Flui for a day More posts