Skip to content
Registry

codex

v0.1.20

OpenAI Codex as an iii worker; codex::run/start/stop/status/sessions::list spawn the codex CLI for headless turns, deliver raw thread events on the codex::raw-event trigger type and AgentEvent frames on codex::agent-event.

license: Apache-2.0agentcodex-clicoding-agentheadlessopenai

Install

bash
iii trigger compose::add worker=codex@next

Run in your project with the iii engine running. Exact versions are immutable and digest-pinned.

Agent-ready brief for v0.1.20
Install, config, dependencies, readme, skills and API reference, all in one place. Fetch as agent-context.md for an LLM to consume.
The same content rendered as discrete blocks below is exposed as a single markdown document at /workers/codex.md?version=next. Paste it into an LLM prompt or pipe it through curl from a worker.

Install

iii trigger compose::add worker=codex@0.1.20

Configuration

iii-config.yaml
- base_url:
  codex_executable:
  defaults:
    approval_policy: never
    cwd:
    model:
    reasoning_effort:
    sandbox_mode: workspace-write
    skip_git_repo_check: true
  iii_context: true

Dependencies

No dependencies for v0.1.20

Readme

README.md

codex

OpenAI Codex as an iii worker: the Codex API exposed as functions and trigger types on the iii bus, nothing else. The worker spawns the same codex binary the user runs in their terminal, with the same login (ChatGPT or API key), the same filesystem, and the same sandbox. codex::run executes one headless turn and returns the result; the raw Codex thread events are delivered verbatim on the codex::raw-event trigger type, and a translated AgentEvent view on codex::agent-event, so the acp worker and any sibling worker observe a Codex run exactly like a native harness turn.

Install

iii trigger compose::add worker=codex

Requires the Codex CLI on the host (npm i -g @openai/codex) and either an existing codex login or OPENAI_API_KEY in the worker environment.

Skills

Install the codex agent skill for Claude Code, Cursor, and 30+ other agents:

npx skills add iii-hq/workers --skill codex

Quickstart

From zero to a Codex turn over the bus:

curl -fsSL https://install.iii.dev/iii/main/install.sh | sh
iii trigger compose::add worker=codex
iii   # starts the engine + worker

Then talk to it like any other function: from iii trigger codex::run, or from any SDK:

import { registerWorker } from 'iii-sdk';

const iii = registerWorker('ws://127.0.0.1:49134', { workerName: 'demo' });

const res = await iii.trigger({
  function_id: 'codex::run',
  payload: {
    prompt: 'Add a /health endpoint to server.ts and run the tests',
    cwd: '/path/to/repo',
    sandbox_mode: 'workspace-write',
  },
  timeout_ms: 600_000,
});
// { session_id, codex_thread_id, result, stop_reason, usage }

Or straight from the terminal with the iii trigger CLI:

# one full turn (raise the timeout; the default 30s is too short for agent turns)
iii trigger codex::run --timeout-ms 600000 \
  --json '{"prompt":"add a /health endpoint and run the tests","cwd":"/path/to/repo"}'

# quick reads use key=value syntax
iii trigger codex::sessions::list
iii trigger codex::status session_id=<session_id>

# background turn + control
iii trigger codex::start --json '{"prompt":"...","cwd":"/path/to/repo"}'
iii trigger codex::stop session_id=<session_id>

# ask the running engine for a function's description and parameter table
iii trigger codex::run --help

A turn from the CLI and the published schema:

iii trigger codex::run returning the result with usage and reasoning tokens

iii trigger codex::run --help printing the published request schema as a parameter table

Call codex::run again with the returned session_id to continue the same conversation: the worker maps iii session ids to Codex thread ids in engine state and resumes automatically (threads persist in ~/.codex/sessions).

Two ids come back from every run. session_id is the iii session id: the key for codex::status, codex::stop, resume, and the stream group. codex_thread_id is Codex's internal thread id (what the worker passes to resumeThread under the hood) — returned for reference, not a lookup key.

Functions

Function Purpose
codex::run Run one turn, wait, return the final result
codex::start Fire-and-forget turn; progress arrives on the streams
codex::stop Interrupt a live run
codex::status Session state, live flag, usage
codex::sessions::list All sessions this worker has run

codex::run accepts either a bare prompt string or a messages array ([{ role: 'user', content: [{ type: 'text', text }] }]) — the same input contract as the claude-code worker and run::start_and_wait, so the acp worker drives Codex with --brain-fn codex::run — plus model, cwd, sandbox_mode, approval_policy, reasoning_effort, skip_git_repo_check, output_schema, images (local image paths), additional_directories (extra writable roots), and codex_config.

Raw API pass-through

The worker drives the codex exec --json CLI, so anything the CLI exposes as a config.toml override goes through the codex_config field untouched — MCP servers, model providers, profiles, network access, web search, and the rest. Each key/value becomes a --config key=value on the spawn:

{
  "prompt": "...",
  "codex_config": {
    "sandbox_workspace_write": { "network_access": true },
    "web_search": "live",
    "mcp_servers": { "github": { "command": "gh-mcp" } }
  }
}

Extra writable directories use the named additional_directories field (the CLI's --add-dir).

And the full output side is available raw: every event Codex emits (thread.started, turn.started, item.started/updated/completed for commands, patches, MCP tool calls, web searches, reasoning, agent messages, turn.completed with usage, turn.failed) is delivered verbatim on the codex::raw-event trigger type. Consumers that want the exact Codex wire format bind codex::raw-event; consumers that want harness-shaped frames bind codex::agent-event. Same turn, two views.

Event feeds

The worker owns two trigger types. Bind one to a function of your own with { session_id } to receive one session's frames:

Trigger type Carries
codex::agent-event translated AgentEvent frames (function_execution_start / function_execution_end, message_complete, turn_end, agent_end), what the acp worker renders
codex::raw-event every Codex thread event, verbatim

Binding config: { "session_id": "", "metadata": { } }. session_id is required (non-empty, at most 512 characters); metadata is optional and is handed to your function (it wins over the binding's own metadata). Any other key rejects the binding, and each trigger type accepts at most 256 bindings.

Your function gets one fire-and-forget call per frame:

{
  "session_id": "sess-1",
  "event_id": "sess-1-<epoch>-00000007",
  "seq": 7,
  "epoch": "<uuid>",
  "source": "codex",
  "event": { "type": "message_complete", "message": { } }
}

seq counts from 0 per feed and session within one epoch (a uuid minted per worker process, so a restart starts a new epoch); the two feeds count independently. Deliveries may arrive out of order: order by (epoch, seq) and dedup by event_id. The feeds are ephemeral: nothing is stored or replayed, and a binding made mid-turn only sees later frames. History lives in the session record (codex::status, codex::sessions::list) and in the codex::run result. A failing consumer never fails the turn. These trigger types replace the former agent::events / codex::events streams (see the guide "Migrate from iii-stream and pubsub").

Configuration

engine_url: ws://127.0.0.1:49134

defaults:
  model: ""                       # empty = Codex default
  sandbox_mode: workspace-write   # read-only | workspace-write | danger-full-access
  approval_policy: never          # never | on-request | on-failure | untrusted
  reasoning_effort: ""            # empty = Codex default
  cwd: ""                         # default working directory for runs
  skip_git_repo_check: true

codex_executable: ""               # path to the codex CLI; empty = PATH resolution

Sandboxing is Codex's own: read-only blocks writes, workspace-write allows edits inside cwd, danger-full-access disables the sandbox. approval_policy is forwarded to Codex per turn from the payload or the configured default; headless callers leave it at never so a command the sandbox blocks fails instead of prompting, but any other value is passed through and respected.

The agent on the bus

By default every turn carries the iii runtime context, delivered through Codex's native developer_instructions config (a developer-role message in the turn context, the channel Codex itself uses for standing instructions): the same engine-grounded rules as the harness identity prompts, retargeted to the iii CLI the agent reaches through its sandboxed shell. The agent discovers capabilities from the live engine instead of memory — iii trigger engine::functions::list to find function ids, iii trigger --help as the contract before every first call, the registry flow (directory::registry::workers::list/info, worker::add) when nothing registered fits — plus the calling rules and error-handling discipline that go with them. Local file edits stay on Codex's native tools; backend actions go through registered functions.

# the agent answers this by querying the live engine itself
iii trigger codex::run --timeout-ms 300000 \
  --json '{"prompt":"List every worker connected to this engine and what each one does.","cwd":"/tmp"}'

The block rides the turn context on every call, resumes included, without touching your prompt or Codex's base instructions. Turn it off per call with "iii_context": false or globally in config.yaml; a caller-supplied developer_instructions in codex_config wins over it.

Plan-then-execute with sandbox modes

Codex has no named plan mode; the equivalent is a planning prompt under the read-only sandbox. The guarantee is OS-level (Seatbelt on macOS, Landlock on Linux), so writes physically fail rather than being policy-declined. Because the worker resumes threads, plan-then-execute is two calls against the same session_id:

# 1. plan (OS-enforced read-only)
iii trigger codex::run --timeout-ms 600000 \
  --json '{"prompt":"Plan how to add rate limiting to the REST API. Do not implement.","cwd":"/path/to/repo","sandbox_mode":"read-only"}'

# 2. execute the plan with full context, same thread
iii trigger codex::run --timeout-ms 600000 \
  --json '{"session_id":"<from-step-1>","prompt":"Implement the plan.","sandbox_mode":"workspace-write","cwd":"/path/to/repo"}'

The approval step is whatever sits between the two calls — a human reading the plan, another worker, or a trigger.

Observability

Every codex::run is an ordinary traced invocation on the engine: the trace carries the full input payload and the output (result, usage) as span events, with per-function p50/p95/p99 in the console's trace explorer — no extra instrumentation in the worker.

codex::run invocations in the iii console trace explorer, with input and output payloads

How it maps

Codex iii
SDK runStreamed() turn codex::run invocation
every thread event, verbatim codex::raw-event delivery
agent_message / reasoning item message_complete frame on codex::agent-event
command_execution / file_change / mcp_tool_call / web_search function_execution_start / function_execution_end frames
turn end turn_end + agent_end frames, function return value
thread resume engine state scope codex_sessions, keyed by iii session_id
extra capability another iii worker on the bus (shell, database, storage, ...)

Skills

API reference (JSON)

agent-api-reference.json
{
  "functions": [
    {
      "description": "Internal: reload codex configuration when it changes.",
      "metadata": {
        "internal": true
      },
      "name": "codex::on-config-change",
      "request_schema": {
        "properties": {},
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Run a Codex coding-agent turn and wait for its result. Accepts `prompt` or a `messages` array plus a `codex_config` pass-through; delivers raw Codex events on codex::raw-event and AgentEvent frames on codex::agent-event, and returns {session_id, result, usage}.",
      "metadata": {},
      "name": "codex::run",
      "request_schema": {
        "definitions": {
          "Message": {
            "properties": {
              "content": {
                "description": "Either a plain string or an array of content blocks."
              },
              "role": {
                "type": "string"
              }
            },
            "required": [
              "content",
              "role"
            ],
            "type": "object"
          }
        },
        "properties": {
          "additional_directories": {
            "default": null,
            "description": "Additional writable directories alongside the working root (--add-dir).",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "approval_policy": {
            "default": null,
            "description": "Codex approval policy; headless callers leave it at never.",
            "type": [
              "string",
              "null"
            ]
          },
          "codex_config": {
            "default": null,
            "description": "Codex config.toml overrides for this turn (e.g. mcp_servers, model_providers, profiles)."
          },
          "cwd": {
            "default": null,
            "description": "Working directory the turn runs in.",
            "type": [
              "string",
              "null"
            ]
          },
          "iii_context": {
            "default": null,
            "description": "Inject the iii runtime discovery prompt as developer_instructions (default true via config).",
            "type": [
              "boolean",
              "null"
            ]
          },
          "images": {
            "default": null,
            "description": "Paths to local images attached to the prompt.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "messages": {
            "description": "Alternative to prompt: role/content messages; the last user entry becomes the prompt.",
            "items": {
              "$ref": "#/definitions/Message"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "model": {
            "default": null,
            "description": "Model id; empty = Codex default.",
            "type": [
              "string",
              "null"
            ]
          },
          "output_schema": {
            "default": null,
            "description": "JSON schema for structured final output."
          },
          "prompt": {
            "default": null,
            "description": "The user prompt for this turn.",
            "type": [
              "string",
              "null"
            ]
          },
          "reasoning_effort": {
            "default": null,
            "description": "Model reasoning effort: minimal | low | medium | high | xhigh.",
            "type": [
              "string",
              "null"
            ]
          },
          "sandbox_mode": {
            "default": null,
            "description": "Codex sandbox mode: read-only | workspace-write | danger-full-access.",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "default": null,
            "description": "iii session id; reuse to resume the same Codex thread.",
            "type": [
              "string",
              "null"
            ]
          },
          "skip_git_repo_check": {
            "default": null,
            "description": "Allow running outside a git repository.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "timeout_ms": {
            "default": null,
            "description": "Reserved for callers; not forwarded.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "title": "RunRequest",
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "busy": {
            "type": "boolean"
          },
          "codex_thread_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_error": {
            "type": "boolean"
          },
          "num_turns": {
            "type": "integer"
          },
          "reason": {
            "type": "string"
          },
          "result": {
            "type": "string"
          },
          "session_id": {
            "type": "string"
          },
          "stop_reason": {
            "type": "string"
          },
          "usage": {
            "type": [
              "object",
              "null"
            ]
          }
        },
        "type": "object"
      }
    },
    {
      "description": "List every Codex session this worker has run.",
      "metadata": {},
      "name": "codex::sessions::list",
      "request_schema": {
        "properties": {},
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "sessions": {
            "items": {
              "type": "object"
            },
            "type": "array"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Start a Codex turn and return immediately; bind codex::agent-event (or codex::raw-event) with { session_id } for progress and turn_end, or poll codex::status.",
      "metadata": {},
      "name": "codex::start",
      "request_schema": {
        "definitions": {
          "Message": {
            "properties": {
              "content": {
                "description": "Either a plain string or an array of content blocks."
              },
              "role": {
                "type": "string"
              }
            },
            "required": [
              "content",
              "role"
            ],
            "type": "object"
          }
        },
        "properties": {
          "additional_directories": {
            "default": null,
            "description": "Additional writable directories alongside the working root (--add-dir).",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "approval_policy": {
            "default": null,
            "description": "Codex approval policy; headless callers leave it at never.",
            "type": [
              "string",
              "null"
            ]
          },
          "codex_config": {
            "default": null,
            "description": "Codex config.toml overrides for this turn (e.g. mcp_servers, model_providers, profiles)."
          },
          "cwd": {
            "default": null,
            "description": "Working directory the turn runs in.",
            "type": [
              "string",
              "null"
            ]
          },
          "iii_context": {
            "default": null,
            "description": "Inject the iii runtime discovery prompt as developer_instructions (default true via config).",
            "type": [
              "boolean",
              "null"
            ]
          },
          "images": {
            "default": null,
            "description": "Paths to local images attached to the prompt.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "messages": {
            "description": "Alternative to prompt: role/content messages; the last user entry becomes the prompt.",
            "items": {
              "$ref": "#/definitions/Message"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "model": {
            "default": null,
            "description": "Model id; empty = Codex default.",
            "type": [
              "string",
              "null"
            ]
          },
          "output_schema": {
            "default": null,
            "description": "JSON schema for structured final output."
          },
          "prompt": {
            "default": null,
            "description": "The user prompt for this turn.",
            "type": [
              "string",
              "null"
            ]
          },
          "reasoning_effort": {
            "default": null,
            "description": "Model reasoning effort: minimal | low | medium | high | xhigh.",
            "type": [
              "string",
              "null"
            ]
          },
          "sandbox_mode": {
            "default": null,
            "description": "Codex sandbox mode: read-only | workspace-write | danger-full-access.",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "default": null,
            "description": "iii session id; reuse to resume the same Codex thread.",
            "type": [
              "string",
              "null"
            ]
          },
          "skip_git_repo_check": {
            "default": null,
            "description": "Allow running outside a git repository.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "timeout_ms": {
            "default": null,
            "description": "Reserved for callers; not forwarded.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "title": "RunRequest",
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "session_id": {
            "type": "string"
          },
          "started": {
            "type": "boolean"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Check the status of one Codex session.",
      "metadata": {},
      "name": "codex::status",
      "request_schema": {
        "properties": {
          "session_id": {
            "description": "iii session id returned by codex::run / codex::start.",
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "SessionIdRequest",
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "live": {
            "type": "boolean"
          },
          "record": {
            "type": [
              "object",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Interrupt a live Codex run for a session.",
      "metadata": {},
      "name": "codex::stop",
      "request_schema": {
        "properties": {
          "session_id": {
            "description": "iii session id returned by codex::run / codex::start.",
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "SessionIdRequest",
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "reason": {
            "type": "string"
          },
          "session_id": {
            "type": "string"
          },
          "stopped": {
            "type": "boolean"
          }
        },
        "type": "object"
      }
    }
  ],
  "triggers": [
    {
      "description": "Translated AgentEvent frames of one codex session. Config: { session_id, metadata? }. Payload: { session_id, event_id, seq, epoch, source, event }. Ephemeral (not stored, not replayed); order by (epoch, seq).",
      "invocation_schema": {},
      "metadata": {},
      "name": "codex::agent-event",
      "return_schema": {}
    },
    {
      "description": "Raw Codex thread events (codex exec --json lines, verbatim) of one codex session. Config: { session_id, metadata? }. Payload: { session_id, event_id, seq, epoch, source, event }. Ephemeral (not stored, not replayed); order by (epoch, seq).",
      "invocation_schema": {},
      "metadata": {},
      "name": "codex::raw-event",
      "return_schema": {}
    }
  ]
}

Search the registry

Find workers, kits and executables, or jump to a page.