Running Codex CLI on KruxOS¶
OpenAI Codex CLI is the command-line agent
interface OpenAI ships with ChatGPT and API plans. It has first-class MCP
client support baked in, which means you can bring every file, process,
network, and git operation Codex performs under KruxOS governance with a
single codex mcp add command.
This guide is the direct counterpart to the Hermes Agent guide: same bridge, different client, a few Codex-specific twists around the Landlock sandbox and project-scoped config.
Why connect Codex to KruxOS?¶
Codex already runs every tool call inside a Landlock-based sandbox — that's the right instinct, and it's the same security philosophy KruxOS takes. What Codex does not give you is:
- A policy engine that can express "this agent may read
/workspacebut never write to/etc" as data, not shell arguments. - A dashboard approval queue so a human can stop a dangerous tool call before it executes, with full input context.
- An audit log that chains every capability invocation across every agent in your organisation into a single tamper-evident stream.
- A uniform governance surface across Claude, Hermes, Codex, and your own Python SDK agents — all three clients hit the same capabilities through the same policies.
Connecting Codex to KruxOS layers these on top of Codex's existing sandbox without replacing anything Codex already does well.
Architecture:
Codex CLI (Rust binary)
│ stdio (newline-delimited JSON-RPC, MCP 2024-11-05)
▼
kruxos.connectors.claude_bridge (launched by Codex as a subprocess)
│ WebSocket
▼
KruxOS Gateway ──► Policy Engine ──► Sandbox ──► Capabilities
│
└──► Audit Log ──► Dashboard Approvals
Same bridge as Claude Desktop, Claude Code, and Hermes — MCP stdio is a
standard transport, so KruxOS ships a single kruxos.connectors.claude_bridge
Python module and every MCP-capable client launches it the same way.
Prerequisites¶
- KruxOS running and reachable at
ws://HOST:7700. - A KruxOS agent token: Save the 64-char hex token.
- The bundled
mcp-bridgeat/opt/kruxos/bin/mcp-bridge— ships on the appliance (VM or Docker image). - Codex CLI installed (
npm install -g @openai/codex, or whichever install method your plan uses). Either an API key or a ChatGPT subscription — KruxOS does not care which, the bridge runs downstream of Codex's auth.
Use the seed-config generator instead of hand-editing
On the appliance, kruxos cli-config generate --write emits the right
~/.codex/config.toml + ~/.codex/hooks.json stanzas (and, with root,
/etc/codex/requirements.toml) referencing the bundled mcp-bridge,
with the token pulled from the vault — never on argv. The hand-written
approach below is provided for reference if you're managing Codex's
config outside the appliance.
apply_patch routing
In v0.0.1, Codex's built-in apply_patch tool is not yet routed through
the KruxOS approval queue — it's an upstream limitation. The MCP-proxy
fix that closes that gap lands in v0.0.4. Other Codex tools (shell,
unified_exec) are disabled at the user-config and requirements layers,
so all governable surfaces route through KruxOS already.
Step 1 — Add KruxOS as an MCP server in Codex¶
The hand-edit alternative (when kruxos cli-config generate isn't an option):
This writes a [mcp_servers.kruxos] entry into ~/.codex/config.toml.
You'll need to add the environment variables by hand. Open
~/.codex/config.toml and make the entry look like this:
[mcp_servers.kruxos]
command = "/opt/kruxos/bin/mcp-bridge"
args = []
[mcp_servers.kruxos.env]
KRUXOS_ENDPOINT = "wss://localhost:7700"
KRUXOS_AGENT_NAME = "codex"
KRUXOS_AGENT_TOKEN = "7f3a8c1d2e9b5a4f8e6c1d3b7a9f2e5c8d1b4a7f3c9e6d8b1a4c7f2e5d9b8a3c"
These are the three env vars the bridge reads:
| Var | Purpose | Default |
|---|---|---|
KRUXOS_ENDPOINT |
Gateway WebSocket URL | wss://localhost:7700 |
KRUXOS_AGENT_NAME |
Agent name (from kruxos agent create) |
(required) |
KRUXOS_AGENT_TOKEN |
Agent token (64-char hex) | (required) |
Codex launches the bridge as a child process on startup, pipes MCP messages over its stdin/stdout, and the bridge proxies everything to the gateway.
Step 2 — Verify the connection¶
List the configured MCP servers:
The kruxos entry should appear and show as running. If it does not start,
jump to Troubleshooting.
Then ask Codex to use a KruxOS tool:
Codex will dispatch the call through the bridge. Tool names that contain
dots on the KruxOS side (filesystem.read, network.http_request) are
rewritten to underscores (filesystem_read, network_http_request) on
the way out because MCP tool names must match ^[a-zA-Z0-9_-]{1,128}$.
The bridge restores the dotted name before forwarding the call to the
gateway, so the audit log records filesystem.read, not the mangled form.
You should see the call in:
- KruxOS audit log:
kruxos audit tail --agent codex - Dashboard (
http://localhost:7800): a new session for agentcodex - Approval queue (if policy is
require_approval): a pending request
Sandbox layering: Codex Landlock + KruxOS sandbox¶
Codex has its own Landlock-based sandbox for tool execution, and KruxOS has its own sandbox at the gateway level (namespaces, cgroups, seccomp, Landlock, nftables — see security-model). When you run Codex against KruxOS via MCP, both sandboxes apply in series:
Codex tool dispatch ─► Codex Landlock ─► bridge stdio
│
▼
KruxOS Gateway
│
▼
KruxOS Sandbox ─► Capability handler
Each layer catches different things. Codex's Landlock is scoped to the Codex process and prevents Codex itself from touching files outside its working directory. KruxOS's sandbox is scoped to the capability handler that runs on the gateway side of the bridge, and enforces the agent's workspace policy regardless of where the client is running. They are not redundant — they defend against different threat models:
| Threat | Caught by |
|---|---|
Prompt injection makes Codex decide to rm -rf / |
Codex Landlock + KruxOS policy |
| Bug in Codex lets a tool call bypass Codex's sandbox | KruxOS sandbox |
| Compromised bridge or gateway process | Codex Landlock (nothing else) |
| Agent operating outside its assigned workspace | KruxOS policy + sandbox |
For the getting-started setup below (Mode A — Codex on the host, KruxOS in a container), you get both. For the production setup (Mode B — Codex inside a KruxOS agent container), Codex's Landlock becomes redundant with KruxOS's container isolation. Both modes are valid; pick based on how much operational complexity you want.
Mode A: Codex on the host (getting started)¶
This is what Step 1 above sets up. Codex runs on your laptop or dev VM, connects to a KruxOS gateway that runs wherever — Docker, the OS image, a remote host. This is the simplest mode and gives you the full governance picture on day one.
Pros: zero container orchestration, fastest path to a working demo, works
with codex mcp add out of the box.
Cons: Codex still has direct host access for its own internal state (working directory, temp files, cache), so KruxOS only governs what goes through MCP tool calls — not what Codex does on its own.
Mode B: Codex inside a KruxOS agent container (production)¶
For full governance, run Codex as an agent inside a KruxOS agent container. Every file Codex touches — working directory, temp files, cache, output — is then inside the KruxOS sandbox, not just the tool calls it makes.
KruxOS already ships a multi-container agent isolation pattern in
docker-compose.agent.yml. Add a Codex service on top:
# docker-compose.codex.yml
services:
codex-agent:
image: node:20-bookworm
container_name: kruxos-codex-agent
depends_on:
kruxos:
condition: service_healthy
working_dir: /workspace
networks:
codex-net:
volumes:
- codex-workspace:/workspace
environment:
KRUXOS_ENDPOINT: "ws://gateway:7700"
KRUXOS_AGENT_NAME: "codex"
KRUXOS_API_KEY: "${CODEX_API_KEY}"
OPENAI_API_KEY: "${OPENAI_API_KEY}"
command: >
bash -c "
apt-get update && apt-get install -y python3 python3-pip &&
pip install --break-system-packages kruxos &&
npm install -g @openai/codex &&
mkdir -p /root/.codex &&
cat > /root/.codex/config.toml <<'TOML'
[mcp_servers.kruxos]
command = \"python3\"
args = [\"-m\", \"kruxos.connectors.claude_bridge\"]
[mcp_servers.kruxos.env]
KRUXOS_ENDPOINT = \"ws://gateway:7700\"
KRUXOS_AGENT_NAME = \"codex\"
KRUXOS_API_KEY = \"${KRUXOS_API_KEY}\"
TOML
tail -f /dev/null
"
volumes:
codex-workspace:
networks:
codex-net:
driver: bridge
Start it alongside the gateway:
docker compose -f docker-compose.yml \
-f docker-compose.agent.yml \
-f docker-compose.codex.yml up -d
Then docker exec -it kruxos-codex-agent codex drops you into Codex inside
the sandboxed container. The gateway is reachable at ws://gateway:7700
on the compose network, and Codex's filesystem is confined to the
codex-workspace volume mounted at /workspace.
In Mode B you can disable Codex's own Landlock sandbox if you want — the KruxOS container already isolates Codex from the host — but leaving it on costs nothing and gives you defense-in-depth.
Project-scoped config¶
Codex supports .codex/config.toml inside a project directory, and it
takes precedence over ~/.codex/config.toml when Codex is launched from
that directory. This is the most underrated Codex feature for team setups:
- One engineer sets up KruxOS and the agent API key.
- They commit
.codex/config.tomlto the project repo (with the API key read from an env var, not hardcoded — see below). - Everyone who clones the repo gets KruxOS governance automatically the
first time they run
codexin that directory.
Example .codex/config.toml for a repo:
[mcp_servers.kruxos]
command = "/opt/kruxos/bin/mcp-bridge"
args = []
[mcp_servers.kruxos.env]
KRUXOS_ENDPOINT = "wss://localhost:7700"
KRUXOS_AGENT_NAME = "codex-team"
# Token resolved from the shell environment at Codex launch time
KRUXOS_AGENT_TOKEN = "${CODEX_KRUXOS_TOKEN}"
Add CODEX_KRUXOS_TOKEN to .envrc / direnv / your shell profile, and
do not commit the actual token. This pattern gives you centralised
governance for a whole team with one commit.
Don't commit tokens
~/.codex/config.toml is fine for personal use, but a project-scoped
.codex/config.toml lives in the repo. Use environment-variable
interpolation (${VAR}) for KRUXOS_AGENT_TOKEN, never a literal
64-char hex token.
Codex as an MCP server (not covered here)¶
Codex can also run as an MCP server via codex mcp-server, which means
another agent could in theory call Codex as a tool. That's interesting for
multi-agent orchestration, but it's the reverse direction of this guide
(KruxOS governing Codex's tool calls, not Codex exposing itself as a tool
to KruxOS). It is out of scope for the v0.0.x line.
Troubleshooting¶
codex mcp list shows kruxos as failed¶
Run the bridge by hand with the same env vars to see the error:
KRUXOS_ENDPOINT=wss://localhost:7700 \
KRUXOS_AGENT_NAME=codex \
KRUXOS_AGENT_TOKEN=7f3a8c1d2e9b5a4f8e6c1d3b7a9f2e5c8d1b4a7f3c9e6d8b1a4c7f2e5d9b8a3c \
/opt/kruxos/bin/mcp-bridge
Structured exit codes (10 = auth, 11 = network) point at the cause.
Most common causes:
mcp-bridgebinary not on the path Codex sees. Codex launches the command verbatim; verify/opt/kruxos/bin/mcp-bridgeis executable and reachable from the Codex working directory.- Env vars missing.
~/.codex/config.tomlhas to have the[mcp_servers.kruxos.env]subsection —codex mcp adddoes not create it for you. Edit the file and add the env vars manually. - Gateway unreachable.
curl -sk -o /dev/null -w '%{http_code}\n' https://localhost:7800— if the dashboard isn't responding the gateway is down.
Tools appear but every call errors with "Session not initialized"¶
The gateway requires an MCP initialize handshake before tools/list or
tools/call. Codex should do this automatically. If it doesn't, you are on
a very old Codex build — upgrade (npm install -g @openai/codex) and try
again. The bridge faithfully forwards whatever the client sends and the
gateway rejects out-of-order traffic per the MCP spec.
"Authentication failed" in the bridge logs¶
The KRUXOS_AGENT_TOKEN value does not match a registered agent. Re-check
the token you saved from kruxos agent create --name codex. Agent tokens
are 64-char hex strings in v0.0.1. Set KRUXOS_BRIDGE_LOG_LEVEL=DEBUG in the env
section to see more.
Tools return "blocked by policy" immediately¶
This is not a bug — KruxOS is doing its job. Inspect the policy:
You will see the blocked call with the matching policy rule. Edit the
policy file in policies/ or assign the codex agent to a different
policy group:
Approval requests appear in the dashboard but Codex times out¶
The bridge waits up to 5 minutes (_APPROVAL_TIMEOUT_SECS) for an approval
decision, and 2 minutes (_EXECUTION_TIMEOUT_SECS) for execution after
approval is granted. If your reviewers are slower than that, increase both
constants in claude_bridge.py or approve faster. A timed-out approval
returns a STOP error to Codex — the call is not silently dropped.
Codex's own sandbox blocks a legitimate tool call¶
If a tool call reaches the gateway and gets a real result but fails somewhere in Codex's own handling, that's Codex's Landlock sandbox catching something, not KruxOS. Check Codex's logs; KruxOS's audit log will show the call succeeded.
Next steps¶
- Approval Workflow — dashboard approvals in detail
- Policies — write the policy that governs the
codexagent - Model Providers — Codex as a model (the other direction: KruxOS calling the OpenAI Codex API to back its own chat sessions — not to be confused with this guide, which is Codex calling KruxOS as a tool)
- Running Hermes on KruxOS — same integration pattern for Hermes Agent
- Docker Agent Isolation — the compose file Mode B builds on