Skip to content

Permissions ​

What an agent may touch is decided by the flow, one role at a time. You choose the CLI and the model that fill a role; what that role may touch comes with the flow, and neither -a nor the agent sheet has a setting for it. Use this page to find out what a flow's agents may change before you run it, to see the limit hold, and to change it in a copy of the flow.

Nothing is put to anybody for approval

Every agent of every flow runs with approvals bypassed. No command or edit waits for a yes, from you or from the model. What limits an agent is the grant on this page. Read Security before you run a flow you did not write.

Try it ​

Ask an agent to write one file in its workdir and one outside it:

sh
mkdir -p /tmp/perm-demo && cd /tmp/perm-demo
hmz exec -f chat -a assistant=claude/claude-haiku-4-5-20251001:low \
    "Use the Bash tool to run exactly these two commands, one call each, then report each one's result in one line: 1) echo hi > inside.txt   2) echo hi > ~/outside.txt"
text
● 1. `echo hi > inside.txt` — Success: created inside.txt in the current directory (/tmp/perm-demo)
  2. `echo hi > ~/outside.txt` — Failed: permission denied writing to ~/outside.txt

inside.txt is there afterwards. ~/outside.txt is not: the agent was never able to write it, however it tried.

Before you start ​

  • Linux with Landlock, or macOS: on Linux, a kernel of 5.13 or newer, booted with it, and 6.7 or newer for a role that may not reach the network. On macOS, any version: Humanize uses the Seatbelt sandbox built into it. See Pitfalls for where a role is refused.
  • The flow's page, which says what its roles are granted. Most say nothing, and run at the default below.

What a role gets ​

A grant has four scopes, each NONE, READ or ALL (read and write). Here is what a role gets when its flow says nothing:

So by default an agent changes its workdir, reads around it, and searches the web, and cannot change anything else of yours. An outer scope never gets more than the one inside it, and online is either NONE or ALL.

ALL grants a little past its root, so that what a program does there by habit works:

  • local of ALL also writes the temporary directories every user shares, /tmp and /var/tmp, and the one $TMPDIR names, as Codex's workspace-write does.
  • user of ALL writes whatever your user account may change, wherever it is: a disk of yours mounted outside your home, a device your groups may use. The file permissions are what hold it. Run as root, it is your home, those temporary directories and /run/user/0, and the rest of the machine stays at system.

Every agent can still read what any program needs to run (the system's programs, libraries and certificates), and write its own settings and login, its sessions, a temporary directory of its own, the devices (the GPUs among them), and on Linux /proc, which CUDA writes to as it starts. Everything else is held to the grant, for the agent and for every command it runs.

What the official flows declare ​

Most roles run at that default. These are the ones that do not:

FlowRoleGrant
aotcriticlocal=READ, online=NONE: it reads the draft and never writes
flame_chasoid:parallelevery agentALL in every scope, online included
flame_chasoid:parallel_git_prthe lane agentsthe default, with user=ALL
recursive_lean_proverworker, reviewerlocal=ALL, user=ALL, system=READ, online=ALL

A flow's own page says what its roles are granted.

Example: watch the default grant hold ​

This is the run from Try it, in full. chat declares nothing, so its one role runs at the default: local is the directory you started in, and your home directory beyond it is user, which is READ.

sh
mkdir -p /tmp/perm-demo && cd /tmp/perm-demo                 # ①
hmz exec -f chat -a assistant=claude/claude-haiku-4-5-20251001:low \
    "Use the Bash tool to run exactly these two commands, one call each, then report each one's result in one line: 1) echo hi > inside.txt   2) echo hi > ~/outside.txt"
text
● assistant is working
● Bash(echo hi > inside.txt)                                              ②
● Bash(echo hi > ~/outside.txt)                                           ③
● 1. `echo hi > inside.txt` — Success: created inside.txt in the current directory (/tmp/perm-demo)
  2. `echo hi > ~/outside.txt` — Failed: permission denied writing to ~/outside.txt
✻ input 18 · output 413 · cache_read 34.7k · cache_write 7.3k · $0.01 · claude-haiku-4-5-20251001 · assistant
✻ Worked for 6s · assistant
sh
ls inside.txt ~/outside.txt                                  # ④
text
ls: cannot access '/home/you/outside.txt': No such file or directory
inside.txt

What each part means ​

  1. The directory you start in is the role's local scope, the only place the default grant lets it write. Start a flow in the project you want changed, never in your home directory.
  2. The write inside the workdir runs, with no question asked of anybody.
  3. The write outside it is refused by the kernel as permission denied. The CLI's own approval setting is bypassed; this is Humanize's limit, held around the agent and every program it starts, so a script the agent writes cannot get round it either.
  4. Checking the result yourself is the way to be sure: only the file inside the workdir exists.

Example: a role that checks each tool ​

Some roles let the flow look at each tool call and refuse the ones it does not want: the builder of humanize1:rlcr and the worker of recursive_lean_prover are two. Only some CLIs can fill such a role, and hmz exec checks before anything runs:

sh
hmz exec -f humanize1:rlcr -p budget.cost=1 -a reviewer=codex/gpt-5.5:high \
    -a builder=grok/grok-4:high "add undo"                        # ①
echo $?
text
hmz exec: error: humanize1:rlcr: 'builder' needs PermissionRequestHookAgentMixin, which grok does not support   ②
2                                                                                                             ③

What each part means ​

  1. builder=grok/… asks Grok Build to fill a role whose flow checks each of its tool calls.
  2. needs PermissionRequestHookAgentMixin names what the role asks of its CLI: that it stop before each tool call for the flow to decide. Grok Build cannot.
  3. Exit status 2: refused before any agent started. Give the role one of the CLIs below.
claudecodexkimievery other
yesyesyesrefused

At the prompt, the agent sheet does not offer the others for such a role.

Check that it worked ​

  • A role that is refused says so before its first turn, as hmz exec: error: … or in red at the prompt. A run that starts is a run whose every grant is held.
  • To see a limit yourself, ask an agent to write where its grant does not reach, as in the first example, and look for the file.

How each CLI holds to it ​

Every grant below everything is enforced on the agent's process and on every command it runs. Where a CLI can hold part of a grant itself, it is told to. Humanize holds the rest from outside:

  • local, user and system are held by Landlock, a Linux kernel feature, or on macOS by Seatbelt, the sandbox macOS is built with. An agent cannot write outside what its grant lets it write, or read outside what it lets it read.
  • online of NONE cuts the network. The agent can still reach the hosts its model and its login are at, and nothing else: no web search, no package index, no host a command names. On Linux nothing from outside reaches it either: a program it runs may serve on this machine's loopback address and on no other. Seatbelt cannot tell one address from another when a program starts to serve, so on macOS a program it runs may serve on any address, while it still reaches nothing but its model.
Backendlocal, user, systemonline of NONE
every CLIheld by Landlock or Seatbeltcut but for its model
cursor-agent, mcodeheld by Landlock or Seatbeltrefused
a CLI added over ACPheld by Landlock or Seatbeltcut but for the hosts declared for it

A role whose local is READ or NONE also runs in its CLI's read-only mode, where the CLI has one (every CLI but dsh, mcode and CLIs added over the Agent Client Protocol: those run with no rung of their own, and a grant that writes nothing is what keeps them from writing). Where a CLI can be told, online of NONE also switches its web tools off.

Cursor's web search and fetch cannot be switched off and run on Cursor's own servers, which it reaches for its model, so a cut network would not stop them: a cursor-agent role with online of NONE is refused, and needs online of ALL. MiniMax Code's web search runs on MiniMax's own service in the same way, so an mcode role with online of NONE is refused too. The aot critic is such a role: give it another CLI.

Humanize knows nothing of a CLI you added over the Agent Client Protocol: not the hosts its model is at, and not where it keeps its state. Declare both where it was added, or a role that grants it online of NONE is refused (see A CLI of your own).

Variations ​

Change what a role may touch ​

Copy the flow into your project: in /flow, put the cursor on it and press Copy here under the list. Then change the role's grant in the copy, as Writing a flow shows. The copy runs as @local/<flow>.

On a machine somebody else manages ​

Codex, where the organisation or the platform forbids full access. The agent runs one step down, and says so once:

text
codex: this machine will not run an agent at bypass, so it runs at auto, where what it asks for is granted

It keeps the same freedom: Codex asks before it reaches past the workspace, and Humanize says yes.

Claude Code, on an account whose managed settings turn off bypass mode, or as root. The agent runs at acceptEdits instead, and says so once:

text
claude: this account will not run an agent at bypass, so it runs at acceptEdits, where what it asks for is granted

It keeps the same freedom: Claude asks before what acceptEdits does not cover, and Humanize says yes. The organisation's own deny rules still apply.

On another machine ​

A container or an ssh host is held to the same grant as this machine. Every command the agent runs lands on the other machine and is fenced there, around that machine's own workdir and $HOME. Claude Code, Codex, Grok Build, Qwen Code and pi are started in a home of their own where they run: here, or on that machine where its own CLI is driven, so neither writes the settings of the machine it runs on (Security). With online of NONE, those commands reach no host at all. The agent's temporary directory is kept on the other machine at the path it has here, so a command that names it, as Claude Code's every shell command does, finds it there and ends with its own exit status.

Pitfalls ​

Where a grant cannot be held, the role does not start

Humanize never runs an agent with more than its grant. A role is refused before it starts (HarnessSandboxed) when:

  • this machine cannot fence: a Linux kernel older than 5.13 or booted without Landlock, or a Mac where Humanize itself runs inside another sandbox, which cannot start one of its own. A kernel older than 6.7 cannot cut the network, so online of NONE is refused there. So is a Linux machine where Humanize may not look into the programs it starts (inside a container with its default seccomp profile, or with Yama's ptrace_scope at 2 or 3): it could not keep what they listen on to this machine.
  • the CLI would reach the web around the cut: cursor-agent or mcode with online of NONE.
  • the work lands on another machine that cannot fence: a container or an ssh host whose kernel is too old, or a container whose seccomp profile refuses Landlock. Docker's own default profile allows it. An ssh host that is a Mac fences with Seatbelt.

Only a grant of ALL everywhere is enforced by nothing, because there is nothing to hold.

  • An agent says permission denied and gives up. That is the grant working. If the flow truly needs to write there, copy it and widen the role's grant, or start it in a directory that holds what it must change.
  • A flow you did not write. Its grants are its author's choice. Read its page, and its code in /flow, before you run it.

Next steps ​

Released under the Apache-2.0 licence.