Skip to content
Agentic Control Plane

Roll ACP out to every Codex seat

Codex has the most explicit enterprise channel of any harness: requirements.toml, a file of admin-enforced constraints that users can’t override, delivered by a system path, an MDM profile, or the ChatGPT Enterprise workspace itself. OpenAI’s admin rollout guide and managed configuration reference are the source for every key below. Read the shape all four harnesses share first if you haven’t.

Before you push

  1. Claim your company domain on the workspace’s Team page so enrollments land in your workspace (why).
  2. Decide your fail-mode for unenrolled machines. The sample below sets ACP_REQUIRE_ENROLLMENT=1: a seat with no credential blocks each shell command with the enrollment step. Drop the variable to get the self-serve posture instead (run, warn, log).
  3. Audit any existing managed_config.toml. Its approval_policy, approvals_reviewer, and sandbox_mode fields are now read as requirements, so a stale value locks every developer into that mode.

Step 1: Put the script in a managed directory

Codex enforces the hook configuration from requirements.toml but doesn’t distribute the scripts it names, and asks that commands use absolute paths inside managed_dir. Your MDM drops two files there. macOS and Linux:

COMMIT=<sha>            # a commit of github.com/agentic-control-plane/claude-code-acp-plugin
install -d -m 755 /etc/acp
curl -sf "https://raw.githubusercontent.com/agentic-control-plane/claude-code-acp-plugin/$COMMIT/bin/govern.mjs" -o /etc/acp/govern.mjs
echo "<expected-sha256>  /etc/acp/govern.mjs" | shasum -a 256 -c

cat > /etc/acp/codex-hook.sh <<'SH'
#!/bin/sh
ACP_CLIENT=codex ACP_HARNESS=codex ACP_REQUIRE_ENROLLMENT=1 exec node /etc/acp/govern.mjs
SH
chmod 755 /etc/acp/codex-hook.sh
chmod 644 /etc/acp/govern.mjs

Windows, into C:\ProgramData\acp\: the same govern.mjs, plus codex-hook.cmd:

@echo off
set ACP_CLIENT=codex
set ACP_HARNESS=codex
set ACP_REQUIRE_ENROLLMENT=1
node "C:\ProgramData\acp\govern.mjs"

Node 18 or newer must be on the PATH of the signed-in user. The script has no other dependency.

Step 2: The requirements file

# requirements.toml — admin-enforced; users can't override these values.

# Only hooks from managed layers run. User, project, session, and plugin hooks are skipped.
allow_managed_hooks_only = true

[features]
hooks = true                      # pin on, even for users who disabled hooks locally

[hooks]
managed_dir = "/etc/acp"
windows_managed_dir = 'C:\ProgramData\acp'

[[hooks.PreToolUse]]
matcher = ".*"
[[hooks.PreToolUse.hooks]]
type = "command"
command = "/etc/acp/codex-hook.sh"
command_windows = 'C:\ProgramData\acp\codex-hook.cmd'
timeout = 5
statusMessage = "ACP policy check"

[[hooks.PostToolUse]]
matcher = ".*"
[[hooks.PostToolUse.hooks]]
type = "command"
command = "/etc/acp/codex-hook.sh"
command_windows = 'C:\ProgramData\acp\codex-hook.cmd'
timeout = 5

# MCP allowlist. A server is enabled only when the user's config defines one whose
# identity matches. Do NOT leave this table present-but-empty: that disables all MCP.
[mcp_servers.acp]
identity = { url = "https://api.agenticcontrolplane.com/mcp" }

What each block does:

  • allow_managed_hooks_only makes the ACP hook the only hook. Without it, requirements hooks run alongside whatever users add, which is fine for a shadow phase and wrong for enforcement.
  • [features] hooks = true pins the hooks engine on. Hooks are on by default since 0.145, but a user can turn them off locally, and a pinned feature can’t be.
  • [hooks] names the managed directories. Both hook commands are absolute paths inside them, per OpenAI’s guidance.
  • matcher = ".*" is what the self-serve installer uses. Codex only fires PreToolUse for the shell tool today, so this matches everything it will ever send.
  • [mcp_servers.acp] allowlists ACP’s endpoint by URL. It doesn’t add the server: each user’s ~/.codex/config.toml needs [mcp_servers.acp] with url = "https://api.agenticcontrolplane.com/mcp", and Codex signs them in with MCP OAuth on first use. To seed it fleet-wide, put that block in managed_config.toml (managed defaults, which users may change) rather than in requirements.

Step 3: Deliver it

Three channels, lowest to highest precedence. Higher layers override scalars and lists from lower ones; hooks compose per OpenAI’s field-specific rules.

Channel Where Notes
System file /etc/codex/requirements.toml (macOS, Linux); %ProgramData%\OpenAI\Codex\requirements.toml (Windows) Root-owned. Your MDM writes it alongside the script.
ChatGPT Enterprise workspace chatgpt.com/codex/settings/managed-configs A cloud config bundle for users signed in with ChatGPT. Carries requirements only. If the fetch fails with no valid cache, Codex returns an error rather than starting without the layer.
macOS MDM profile Preference domain com.openai.codex, key requirements_toml_base64 The TOML, base64-encoded with no line wrapping. Jamf Pro, Kandji, Fleet all work. Highest precedence.

The cloud bundle is the interesting one: an admin with a ChatGPT Enterprise workspace can push the hook configuration to every seat with no device management at all. The script still has to exist on disk, so MDM is unavoidable for the hook itself; the MCP entry alone is console-only.

Step 4: Each person enrolls once

The pushed config identifies no one. Each developer opens cloud.agenticcontrolplane.com/plugin/authorize, signs in with their work identity, and pastes the one line it shows:

echo 'gsk_…' > ~/.acp/credentials

Until they do, with ACP_REQUIRE_ENROLLMENT=1 every shell command is denied with exactly that instruction in the message. Codex also asks each user to confirm hooks once in /hooks on first run; managed hooks are enforced regardless, but the confirmation is where a user sees which hooks are running.

What you’ll see

  • Console rows with client codex, one per person, with Codex’s session source (cli, vscode, exec, subAgent) on each row.
  • On an unenrolled seat: [ACP] Not enrolled: this machine runs your organization's ACP policy, but no workspace credential is present… and the command doesn’t run.
  • On a policy deny: the rule’s reason. On an ask rule: a deny carrying the approval link, because Codex’s hook parser acts on deny only. After a human approves, the identical re-run passes under the standing grant.

Codex-specific limits are unchanged from the self-serve integration: hooks fire for shell commands only, updatedInput is rejected so scoped vendor-token injection is off, and type = "command" is the only handler. Details on the Codex integration page.

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