The action cycle
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 makes it useful, and it is also a risk: the same key that deploys an application can add machines you pay for, move a database between clusters, or remove an application along with its storage.
Flui keeps the agent fully capable and puts a person between the model and the action. More than sixty write routes are declared inside the action cycle: when the caller is an agent, the call does not run. It becomes a question addressed to a person.
This chapter explains that pause. It is written for the person connecting an agent and for the agent itself, since both end up reading it.
What the agent gets instead of an effect
The call is refused, but the refusal is not an error. It carries:
- a request id;
- one plain sentence describing what was asked for, naming the resource (“change how this cluster scales itself”, not just “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 provides it, what happens if the person allows it.
The person answers in one of three ways:
- Once: let this exact call through and remember nothing.
- Always: let it through and record a standing permission.
- No: refuse it.
The message the agent receives says that this is a wait and not a failure, that nothing was changed, and that changing the arguments to get around it only raises a second question instead of answering the first.
The agent learns the answer by retrying
Nothing holds a connection open while someone decides. The agent finds out by making the identical call again: if the person allowed it, it goes through.
This has two benefits. A request can wait an hour for an answer without a socket held open anywhere. It also works for an agent with no special client support: an agent driving Flui with plain HTTP calls meets the same pause and learns the answer the same way.
Who counts as an agent
Two things make a caller an agent, and the caller cannot set either of them itself:
- its credential carries agent scopes;
- the call arrives through an agentic surface, which is the assistant inside Flui.
A person filling in a dashboard form or typing a flui command
is not paused. They are the approver, not the applicant, and
asking them to confirm what they just typed would add nothing.
The assistant inside Flui is paused too, even though the same person is at both ends of it. What decides the pause is who wrote the arguments, not whose credential it is. A form carries what a person typed. A tool call carries what a model chose, so confirming it is a real check: the person confirms something they did not write.
An agent cannot answer its own request
An agent key is issued as its person, so every permission check on the page where requests are answered would pass, and the applicant could approve its own request.
That page therefore refuses a caller for what it is, whatever it may do. An agent credential is turned away from the decision routes even though its permissions are sufficient by construction. The same rule keeps the assistant inside Flui on the applicant’s side: the chat cannot approve its own request, while the person reading it in the dashboard can.
A request also belongs to someone. Only the person it was addressed to can answer it. Running the whole installation does not let you answer a question asked of someone else, and another person’s request looks as if it does not exist, rather than forbidden.
A request lasts one hour
A request expires after one hour, because of the price. A request carries an estimate of what the action will cost, and that estimate goes stale. Once the hour is up, the request can no longer be answered. The agent’s next attempt refreshes it with a new hour and a new price, and keeps the same id, so the number the agent already reported still points at what the person is looking at.
”Always” is one action, one resource, one credential
A standing permission is recorded against a specific action and the specific resource that action names: “change how this cluster scales itself”, not “change scaling”. Pointed at another cluster, it does not match, and the agent has to ask again.
It is also tied to that one credential. A different key, even one the same person created a minute later, starts by asking again. The permission belongs to one agent, not to everything the person may run later.
A request that cannot name its own resource is only ever offered once. With no resource to record, there is no narrow permission to grant, so “always” is not offered.
A standing permission grants nothing
Allowing an agent to stop asking can never let it do something it could not already do.
The pause is the last check. By the time a call reaches the action cycle, every check that decides whether the call is allowed has already run and passed. The cycle can only remove the pause in front of it. “Always” removes a question about something the person’s own access already allowed; it grants nothing new.
Taking it back
A person can see what their agents may do without asking, revoke any of it, and, before deciding, see what is still running under that permission.
Revoking takes effect immediately for anything that has not started. The standing permission is read on every request, so once it is revoked nothing new starts under it.
It does not abort work already running. A separate option also asks running work to stop, and even that takes effect at the next safe step, never mid-step. A ten-step provisioning run cut in half would leave resources you pay for orphaned somewhere, which is worse than what the person was trying to stop.
That is why the revoke dialog shows the running list first. After a revoke, provisioning runs already in progress continue, and the dialog makes that visible before you confirm.
Small rules that follow from the same thinking
Repeated attempts collapse. An agent retrying the same call with the same arguments finds the request it already raised. However many attempts it makes, there is one question.
The arguments are never stored. They are read once, to recognise a repeat, and discarded. Some of them are secrets (the answers to a catalog install can include an administrator password), and a table meant to be read by a person deciding whether to say yes should not hold them. Only the sentence is kept.
A dry run does not ask. A route that can be told to check and change nothing, such as validating a manifest, skips the pause. Asking a person about a deploy that is not happening would describe the wrong action, and that sentence is what gets stored.
The price is withheld from the agent. Where a request has a cost, the agent is told that a price exists, that it cannot see it, and that the person deciding can. It is instructed to say exactly that, and not to describe the action as free or invent a figure.
A settled “no” reads as settled. Retrying something that was already refused gets a distinct answer: the person answered, so stop, tell them what was refused and offer another course of action. It is not phrased as an access-control failure, because an agent that reads it as one goes off asking for permissions the person just declined.
Where a person answers
In the dashboard, on the Agents page, where requests raised by your agents wait for you. For the assistant inside Flui, your yes in the chat answers the request once. The link in the refusal points straight to the individual request. The API serves the same thing directly.
There is no CLI command for this. You cannot list requests, approve one or revoke a standing permission from the terminal. If the CLI is your only surface, the pause still applies and you answer it elsewhere.
The trade-off
A pause the agent does not understand is a bad experience. The agent stops, reports something confusing, and the person wonders why nothing happened.
That is why the refusal says it is a wait, what was asked for, and who can allow it and where. There is a known gap: the onboarding document Flui gives an agent when it connects does not mention the action cycle. The tool that serves it says the document teaches the pattern, but version 1.3.0 of the document does not contain it. An agent following it meets its first pause with no idea what it is.
The fix is manual and cheap: tell the agent about the cycle, or point it at this page. An agent that knows the rule reports “I asked, and someone needs to allow it” instead of “it failed”.
Where this chapter goes from here
- The Flui Assistant and MCP: the surfaces an agent reaches Flui through, and the scope model underneath them.
- Identity and OIDC: where the credential an agent carries comes from, and whose it is.