Skip to content
Agentic Control Plane

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. There is no ACP integration for fx yet — status 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.json files 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:

  1. Rules are checked first. A matching rule resolves the call.
  2. 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 hardcodes zai/glm-5.2 (src/core/permissions/auto_classifier.zig). Either way, no setting, env var, or model choice changes it.
  3. The reviewer can only allow or escalate — an unresolved review falls through to a human prompt in interactive mode; it cannot silently hard-deny.
  4. A transient reviewer failure triggers one bounded retry.

Two consequences to know before relying on it:

  • Each review is an extra model request. auto can cost more than ask for 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.
  • nonethe 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_use event 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.4 in the docs, zai/glm-5.2 in 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 as session/request_permission with 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.)

Status: seam verified, integration not shipped

Plainly: there is no shipped ACP integration for fx yet. But the seam is real, and we verified it in source on launch day: run fx as an Agent Client Protocol server and every approval-gated call — terminal commands, file writes and deletes, install_skill — is routed to the client with its full raw arguments, three-way decision (allow once / allow for session / reject), fail-closed on anything else. ACP sessions default to ask mode, so the client sees everything sensitive. A governed protocol client that answers those requests from workspace policy is buildable on that contract, and we’re building toward it. Until it ships, this page claims nothing beyond what you just read.

What a layer above fx would add is the same list the rest of the comparison converges on, and fx’s strengths don’t touch it: the rules dialect is fx’s own (a deny you wrote for Claude Code or Codex doesn’t exist here), the approval loop’s reviewer is a model with no queryable trail, and policy is per-user, per-machine. Until there’s a seam, the honest recommendation is fx’s own: write the deny rules, leave yolo alone, and know that the record of what ran is the session and nothing else.