The fx control model, explained
fx is Vercel Labs’ minimal coding agent — written in Zig, announced August 18, 2026, installed with curl -fsSL fx.sh/setup.sh | bash. Minimal does not mean uncontrolled: fx ships a complete native permission system with a deterministic rules grammar, a model reviewer in the approval loop, and a repo-config lockdown that several older harnesses should copy. This page is the reference: what each mechanism does — from the docs as of launch day, cross-checked against the open-source tree and the v0.0.3 binary where they disagree — and where the native model ends.
This page covers fx’s own controls. ACP governs fx through @agenticcontrolplane/fx, a protocol middleware for fx acp — details at the bottom. For the cross-harness picture, see the controls comparison.
Three modes
| Mode | Behavior |
|---|---|
ask |
Sensitive calls prompt for approval, except those resolved by a rule or by the inspection grammar below |
auto |
The default. Rules first; unresolved sensitive calls go to an automatic model review, with a human fallback in interactive mode |
yolo |
fx permission checks off, no command sandbox — the run has the full authority of the launching process |
The mode is set three ways, in the usual precedence shape: /permissions <mode> in-session, permission_mode in ~/.fx/settings.json, or the FX_PERMISSION_MODE env var. /permissions reset returns to ask and clears any session grants.
yolo requires yolo_acknowledged before it will run — a deliberate speed bump rather than a bare flag. What it doesn’t leave behind is any record that the controls were off.
The rules grammar
Policy is a nested JSON permission object in ~/.fx/settings.json, user-global or per-workspace profile:
{
"permission": {
"*": "ask",
"bash": {
"git *": "allow",
"git push *": "deny"
}
}
}
Wildcard patterns map to allow, deny, or ask. Resolution is simple and stated: last matching rule wins, and workspace rules beat user-global ones. Rules are managed interactively via /allowlist.
Two design decisions here are worth crediting:
/permissions remember <allow|deny> <tool-name> <arguments-json>stores an exact confirmed rule without running the action. That’s the right shape for turning a one-off decision into policy: the rule matches precisely what you confirmed, not a broadened prefix, and you don’t have to execute the command to record the verdict./permissions revoke <rule-id>removes it.- Session grants don’t outlive the session. A “yes, and don’t ask again” answer is in-memory only — not persisted, and not restored by
fx resume. A grant you gave in context stays in that context.
And the biggest one:
Project
.fx.jsonfiles cannot define permission rules or set the model. Permission policy lives only in the user’s own settings. This is deliberate anti-repo-hijack design: a cloned repository cannot widen its own authority or swap the model out from under you.
Several harnesses get this wrong in one direction or another — repo-level settings that grant, or escape hatches that trust the checkout’s config wholesale. fx drew the line in the right place on day one.
auto mode: a model in the approval loop
auto is the default, and it puts a model where the human prompt was — the same move as Claude Code’s auto mode. The mechanics, precisely:
- Rules are checked first. A matching rule resolves the call.
- An unresolved sensitive call goes to an automatic review by a hardcoded model request. Which model? The docs say
openai/gpt-5.4; the launch-day source tree hardcodeszai/glm-5.2(src/core/permissions/auto_classifier.zig). Either way, no setting, env var, or model choice changes it. - The reviewer can only allow or escalate — an unresolved review falls through to a human prompt in interactive mode; it cannot silently hard-deny.
- A transient reviewer failure triggers one bounded retry.
Two consequences to know before relying on it:
- Each review is an extra model request.
autocan cost more thanaskfor the same call — the mode that removes the prompt adds inference. - The reviewer’s decisions leave no documented record. Nothing states what the reviewer was shown, what it decided, or why. A model is deciding what runs on your machine, and the only artifact is the session itself.
Sensitive tools and the inspection grammar
The docs name the sensitive tools: write_file, edit_file, delete_file, rename_file, copy_file, create_folder, run_command, open_file, install_skill, vision.
In ask mode, fx carves out a prompt-free inspection grammar: restricted forms of pwd, ls, wc, and printf, and pipelines of up to 8 stages composed only of those. Everything else needs approval. Output from the grammar is escaped and capped at 65,536 bytes.
This is the deterministic answer to the “safe commands” problem, and it’s the honest one: a parser decides what’s prompt-free, not a model classifying its own command as harmless. The grammar is narrow — read-only inspection, nothing that writes or reaches the network — which is exactly why it can be prompt-free.
The sandbox
Sandboxing is a separate setting from permissions, with three values:
os— native sandbox, macOS only: reads allowed, writes restricted to the workspace, additional configured dirs, and temp; outbound network allowed; listening on localhost needs separate authority.none— the default.auto.
One more decision worth crediting: widening sandbox access is a separate approval from approving the command. Saying yes to a command is not saying yes to it escaping the write boundary — the two authorities are distinct prompts.
The caveats are the mirror image: the sandbox is off by default, macOS-only when on, and outbound network is open even inside it.
Where the native model ends
fx’s native controls are genuinely good for a day-one release: a clear mode model, a deterministic rules grammar with sane precedence, exact-match rule capture, ephemeral session grants, and the repo-config lockdown. Where the model ends:
- No hook or plugin surface — externally. The source contains an internal hook harness with a
pre_tool_useevent that can block or rewrite a call, but nothing attaches to it from outside: no settings key, no subprocess execution, no docs. It’s where a native integration would go; today it’s compiled-in only. - The reviewer is fixed and opaque. Hardcoded, no setting to change it, no documented record of its decisions — and the docs and source disagree about which model it even is (
openai/gpt-5.4in the docs,zai/glm-5.2in the launch-day tree). A model decides what runs on your machine, and you can’t select it or audit it. - No documented record beyond the session. No append-only log, no decision trail. Session grants evaporating is right for safety and also means there’s no ledger of what was granted.
- No cross-machine policy. Rules live in each user’s
~/.fx/settings.json; two engineers on one codebase share nothing. - The real external seam is
fx acp— an Agent Client Protocol server (Zed’s editor-embedding protocol, unrelated to the Agentic Control Plane despite the initials). Reading the source, this is more than a transport: fx sends every approval-gated call to the connected client assession/request_permissionwith the full tool arguments, honors the client’s allow/deny, and resolves any error, cancellation, or malformed answer to deny. A client that can answer that request can stand on fx’s call path. (A gateway override,FX_GATEWAY_BASE_URL, also exists in source — loopback-only, the local-inference mechanism — so a co-resident model proxy fits too.)
Governing fx with ACP
The seam described above is exactly where @agenticcontrolplane/fx stands. fx has no plugin API, so it isn’t a plugin — it’s a protocol middleware: a zero-dependency stdio proxy between your Agent Client Protocol client (Zed, or any ACP driver) and the fx acp subprocess. Every message passes through untouched except session/request_permission — the request fx sends for each approval-gated call, raw arguments attached. The middleware checks that call against your workspace policy first: a policy deny answers reject before the prompt ever reaches you, a policy allow answers allow-once on your behalf — never allow-always, so every subsequent call for the same tool still goes through governance instead of fx caching a blanket approval — an ask relays the original prompt to your client for a human decision, and every decision lands in the audit stream.
npm install -g @agenticcontrolplane/fx
acp-fx -- fx acp # point your ACP client here instead of `fx acp`
Two details worth knowing, both from fx’s own design: the middleware strips FX_PERMISSION_MODE=yolo from the child environment (the env bypass would otherwise silence the permission stream it governs), and in --unattended mode it fails closed — no reachable gateway means reject, matching fx’s own posture on malformed answers. Interactively it fails open and loud: no key or no gateway, and it tells you on stderr and steps aside rather than bricking the session. Source.
What the layer adds is the same list the rest of the comparison converges on: rules that exist across harnesses (a deny you wrote for Claude Code now holds here too), a queryable decision trail where fx’s model reviewer has none, and policy that is workspace-wide instead of per-user, per-machine. If you run fx without it, the recommendation stays fx’s own: write the deny rules, leave yolo alone, and know that the record of what ran is the session and nothing else.
Run agents? ACP lets you see, control, and price every tool call they make — allow/ask/deny policy, per-session cost, and a full audit log for Claude Code, Cursor, Codex, and OpenClaw, in one command:
curl -sf https://agenticcontrolplane.com/install.sh | bash
Getting started → · see your first governed call → · free up to 5 agents · prefer fully on-device? add --local