Claude Agent

CODEX

Run OpenAI Codex as the agent under evaluation in Coder Eval — installation, authentication, task configuration, and how Codex telemetry maps to sandboxed, weighted scoring.

LLM Mart · 0 points · 10 views 0 listing impressions 0 install-command copies

What vetted this — trust report

Download uipath-coder_eval-docs_agents_CODEX.md-dd918e6.zip · 5 KB
Part of uipath/coder_eval — 10 skills

Install

skills CLI npx skills add https://github.com/UiPath/coder_eval/tree/main/docs/agents/CODEX.md
Git git clone https://github.com/UiPath/coder_eval.git

The skills CLI installs just this skill, for any of its supported agents. Git is the plain clone.

Files (coder_eval)
  • CODEX.md 13.6 KB
    ---
    description: >-
      Run OpenAI Codex as the agent under evaluation in Coder Eval — installation,
      authentication, task configuration, and how Codex telemetry maps to sandboxed,
      weighted scoring.
    ---
    
    # Running OpenAI Codex in Coder Eval
    
    ## Overview
    
    Coder Eval can run OpenAI's Codex as the agent under evaluation, via the official Codex SDK. The `CodexAgent` mirrors the structure of `ClaudeCodeAgent` and plugs into the same sandbox, scoring, and telemetry pipeline — set `agent.type: codex` in a task and the rest of the framework works unchanged.
    
    ## Setup
    
    ### 1. Install the Codex SDK
    
    Install coder-eval with the `codex` extra:
    
    ```bash
    pip install 'coder-eval[codex]'
    ```
    
    This installs:
    - `openai-codex` - The official Codex Python SDK (from PyPI)
    - `openai-codex-cli-bin` - Platform-specific Codex CLI binaries (pulled in transitively)
    
    ### 2. Authentication
    
    Codex requires authentication. Options:
    
    ```python
    # Option 1: API Key (direct)
    await codex_client.login_api_key("your-api-key")
    
    # Option 2: ChatGPT (interactive)
    await codex_client.login_chatgpt()
    
    # Option 3: Device Code Flow
    await codex_client.login_chatgpt_device_code()
    ```
    
    `CodexAgent.start()` calls `login_api_key` automatically when **`CODEX_API_KEY`** is present in the environment. Without a key it falls back to any existing ChatGPT login. (Only `CODEX_API_KEY` is read — not `OPENAI_API_KEY`/`AZURE_OPENAI_API_KEY`; point `CODEX_API_KEY` at whichever endpoint's key you use.)
    
    ### Endpoint routing
    
    | Env var | Purpose |
    |---|---|
    | `CODEX_API_KEY` | Auth key/token for the selected endpoint (required for headless runs). |
    | `CODEX_BASE_URL` | Route to a custom endpoint. **Unset → standard OpenAI platform** (api.openai.com). Set → custom provider (gateway or Azure). |
    | `CODEX_MODEL` | Fallback model when `agent.model` is unset. On Azure this is the **deployment name**. |
    | `CODEX_API_VERSION` | Azure only: the required `api-version` query param. Leave unset for OpenAI/gateways. |
    
    **Standard OpenAI:** leave `CODEX_BASE_URL` unset, set `CODEX_API_KEY` to an OpenAI `sk-…` key and `CODEX_MODEL` to a Codex/Responses-capable model.
    
    **Azure OpenAI:**
    ```bash
    CODEX_BASE_URL=https://<your-resource>.openai.azure.com/openai
    CODEX_API_VERSION=2025-04-01-preview   # required by Azure
    CODEX_MODEL=<your-deployment-name>     # deployment, not the base model id
    CODEX_API_KEY=<azure-openai-key>
    ```
    This registers a custom Codex model provider (`base_url` + `env_key=CODEX_API_KEY` + `query_params={api-version}` + `wire_api=responses`). The Codex CLI only supports the Responses wire API (it rejects `wire_api=chat` as "no longer supported"), so the protocol is fixed. If your Azure deployment requires the key in an `api-key` header rather than `Authorization: Bearer`, that needs an additional provider `http_headers`/`env_http_headers` entry — open an issue if you hit that.
    
    ## Usage
    
    ### Command Line
    
    Run a task with Codex agent:
    
    ```bash
    coder-eval run tasks/agents/codex_hello_world.yaml --type codex
    ```
    
    Or override agent type for all tasks in an experiment:
    
    ```bash
    coder-eval run experiments/model-comparison.yaml --type codex
    ```
    
    ### Task Definition (YAML)
    
    Specify Codex in task YAML:
    
    ```yaml
    agent:
      type: codex
      permission_mode: acceptEdits
      allowed_tools:
        - Bash
        - Read
        - Write
      disallowed_tools:
        - Edit
      plugins:
        - type: local
          path: "$PLUGIN_PATH"
    
    success_criteria:
      - type: file_exists
        path: "src/solution.py"
        description: "Solution file must exist"
    ```
    
    Valid `permission_mode` values:
    - `default` - Standard access, requires approval on failure
    - `acceptEdits` - Automatically accept file edits, no filesystem restrictions
    - `plan` - Read-only sandbox, approval required for any changes
    - `bypassPermissions` - Full access, no approvals needed
    
    ### Skills (SKILL.md)
    
    CodexAgent supports SKILL.md files following the [Agent Skills open standard](https://agentskills.io/specification). Skills are discovered from:
    
    1. **config.plugins** - Local plugins with `type: local` and `path` pointing to a skills directory
    2. **plugin_tools_dir** parameter - Runtime plugin directory passed to `start()`
    
    Skills are symlinked (or copied) to `.agents/skills/` where the Codex CLI auto-discovers them. Environment variables in plugin paths (`$VAR`, `${VAR}`) are expanded at runtime.
    
    Example with environment variable:
    ```yaml
    agent:
      type: codex
      plugins:
        - type: local
          path: "$SKILLS_PLUGIN_PATH"
    ```
    
    Set environment variable:
    ```bash
    export SKILLS_PLUGIN_PATH=~/uipath/uipath-claude-plugins/plugins/uipath-coded-agents
    coder-eval run tasks/my_task.yaml
    ```
    
    ## Architecture
    
    ### Class Hierarchy
    
    ```
    Agent (ABC)
    └── CodexAgent
        ├── Codex SDK Client (openai_codex.Codex)
        ├── Thread Management (thread_start, turn.stream)
        └── Streaming telemetry (commands, token usage, agent text)
    ```
    
    ### Key Methods
    
    - **`start(working_directory)`** - Initialize Codex client and set working directory
    - **`communicate(user_input, timeout, stream_callback)`** - Execute one turn with Codex
    - **`stop()`** - Clean up resources
    - **`get_state()`** - Return current agent state
    - **`discard_pending_turn()`** - Rollback on failure
    
    ### TurnRecord Format
    
    Each turn returns a `TurnRecord` with:
    - `iteration` - Turn number
    - `user_input` - The prompt sent
    - `agent_output` - assembled from the streamed `agentMessage` deltas
    - `commands` - `CommandTelemetry` for each shell command (`Bash`) and apply_patch file change (`Write`)
    - `timestamp` - When the turn completed
    - `duration_seconds` - Wall-clock execution time
    - `token_usage` - input/output/cache-read token counts (from the SDK token-usage stream)
    - `model_used` - the pinned `agent.model`, when set
    
    ## Implementation Details
    
    ### Timeout Handling
    
    The agent uses a `ThreadedWatchdog` to enforce wall-clock timeouts. If a turn exceeds the deadline, a `TurnTimeoutError` is raised with a partial `TurnRecord` preserved in `pending_turn`.
    
    ### Error Recovery
    
    On failure, the agent:
    1. Sets `pending_turn` to a `crashed=True` TurnRecord with captured telemetry
    2. Raises `AgentCrashError` or `TurnTimeoutError`
    3. The orchestrator reads `pending_turn` and calls `discard_pending_turn()` to roll back state
    
    ### Permission and Tool Mapping
    
    The agent maps `permission_mode` to the Codex SDK's `Sandbox`. The approval mode is **uniformly `deny_all`** for every mode — the trust boundary is the sandbox, which does vary by mode:
    
    | `permission_mode` | `sandbox` | `approval_mode` |
    |-------------------|-----------|-----------------|
    | `bypassPermissions` | `full-access` | `deny_all` |
    | `acceptEdits` | `workspace-write` | `deny_all` |
    | `default` | `workspace-write` | `deny_all` |
    | `plan` | `read-only` | `deny_all` |
    
    `deny_all` means *run autonomously, never prompt, no server-side reviewer*: in-sandbox operations execute directly and only escalations beyond the sandbox are refused. Coder Eval uses it for every mode because the alternative (`auto_review`) adds a server-side reviewer that can spuriously return `declined` under gateway load.
    
    `allowed_tools` / `disallowed_tools` are normalized (`Bash` → `shell`, `Write`/`Edit` → `apply_patch`, etc.) and passed as `enabled_tools` / `disabled_tools` in the thread `config`. **Note:** the Codex SDK does not currently enforce `disabled_tools`; do not rely on it as a security boundary (the agent logs a warning when it is set).
    
    ### Skills Discovery
    
    The agent sets up SKILL.md files (Agent Skills open standard) in `.agents/skills/` directory:
    
    1. Scans `config.plugins` for local plugins with `path` field
    2. Checks `plugin_tools_dir` parameter passed to `start()`
    3. Expands environment variables in paths (`$PLUGIN_PATH`, `${PLUGIN_PATH}`)
    4. Symlinks skill directories (falls back to copying if symlink fails)
    5. Codex CLI auto-discovers skills in `.agents/skills/`
    
    ### Async Integration
    
    The Codex SDK is synchronous. The agent uses `_run_async()` helper to detect and await coroutines, preserving the async interface.
    
    ## Differences from Claude Code Agent
    
    | Feature | Claude Code | Codex |
    |---------|------------|-------|
    | **SDK Type** | Subprocess (CLI via JSON generator) | Sync client (app-server subprocess) |
    | **Command Tracking** | Full telemetry (tool name, params, duration) | Streamed telemetry: shell → `Bash`, apply_patch → `Write` |
    | **Model Selection** | Direct via `--model` or config | `agent.model` pinned into `thread_start` |
    | **System prompt** | `system_prompt` appended to the default prompt (SDK `claude_code` preset) | `system_prompt` passed as `developer_instructions` on top of the Codex base prompt |
    | **Session Resume** | `--resume {session_id}` | Via thread ID |
    | **Permissions** | `permission_mode` + `allowed_tools` | `permission_mode` → sandbox/approval + `allowed_tools`/`disallowed_tools` → thread config |
    | **Tool Enforcement** | Not enforced by Coder Eval wrapper | `enabled_tools` honored; `disabled_tools` NOT enforced by the SDK |
    | **`max_turns`** | Native SDK turn cap (assistant messages) | Visible-turn cap (tool calls), enforced on the notification pump |
    | **Early stop** | Supported (cooperative `should_stop`, polled between messages) | Supported — polled after each streamed notification; the in-flight turn is interrupted best-effort |
    
    Run-limit semantics per harness: [Run-Limit Parity](HARNESS_PARITY.md).
    
    ## Known Limitations
    
    1. **Tool-name collapse** - Codex reports shell tools (`Read`/`Grep`/`Bash`) all as shell commands, surfaced as `Bash` telemetry; name-keyed criteria that distinguish these tools aren't meaningful across agents.
    2. **`skill_triggered` criterion** - Codex has no distinct `Skill` tool (it engages a skill by reading its files via shell), so the criterion detects Codex engagement from that file-read signal (a command referencing `skills/<name>/`) instead of a `Skill` tool call. The file-read signal is weaker than Claude's explicit invocation.
    3. **`disallowed_tools`** - passed to the SDK but not enforced; not a security boundary.
    4. **Authentication** - Requires `CODEX_API_KEY` in the environment (point it at whichever endpoint's key you use — OpenAI, gateway, or Azure); the agent calls `login_api_key` when a key is present. `OPENAI_API_KEY`/`AZURE_OPENAI_API_KEY` are NOT read.
    5. **Model field** - `TurnRecord.model_used` reflects the pinned `agent.model`; the Codex `Turn` payload itself doesn't carry the resolved model.
    6. **Skills with Windows paths** - Symlink creation may fail on Windows; agent falls back to copying (slower).
    7. **No `system_prompt_mode`** - `replace` semantics are Claude-Code-only. `system_prompt` is always appended as `developer_instructions`; setting `system_prompt_mode` on a Codex `agent:` block is a validation error (unknown field).
    
    ## Migrating tasks that set `system_prompt`
    
    `system_prompt` was previously **ignored** on Codex tasks — silently dropped, so the
    task ran on Codex's base prompt alone. It is now forwarded as
    `developer_instructions`, layered on top of that base prompt. Any Codex task setting
    the field now actually receives those instructions, so **scores are not comparable
    across this boundary**. Two things to check:
    
    - A prompt written for Claude (naming `Read`/`Grep`/`Glob`, or Claude tool etiquette)
      is now live on Codex, where those tool names don't exist.
    - There is **no opt-out** (see Known Limitations #7). To restore the old behavior,
      remove `system_prompt` from the Codex variant — otherwise re-baseline.
    
    ## Future Enhancements
    
    - [ ] Implement session-based resume (thread ID tracking)
    - [ ] Strengthen the Codex `skill_triggered` signal — it currently infers engagement from a file read, weaker than Claude's `Skill` tool call
    - [ ] Capture the resolved model from the SDK (vs. the pinned config value)
    
    ## Testing
    
    Run the included test tasks:
    
    ```bash
    # Basic functionality test
    coder-eval run tasks/agents/codex_hello_world.yaml
    
    # Tool restriction test (verifies disallowed_tools enforcement)
    coder-eval run tasks/agents/codex_disallowed_tools_test.yaml
    
    # Skills discovery test (requires PLUGIN_PATH environment variable)
    export PLUGIN_PATH=~/path/to/skills
    coder-eval run tasks/agents/codex_skills_test.yaml
    ```
    
    Example unit test to verify agent setup:
    
    ```python
    import pytest
    from coder_eval.models import AgentKind, AgentConfig
    from coder_eval.agents.codex_agent import CodexAgent
    from coder_eval.agent import AgentState
    
    def test_codex_agent_initialization():
        """Verify CodexAgent can be instantiated with valid config."""
        config = AgentConfig(
            type=AgentKind.CODEX,
            permission_mode="acceptEdits",
            allowed_tools=["Bash", "Read", "Write"],
        )
        agent = CodexAgent(config)
        assert agent.get_state() == AgentState.WORKING
        assert agent.config.type == AgentKind.CODEX
    
    def test_tool_name_mapping():
        """Verify Claude Code tool names map to Codex SDK names."""
        from coder_eval.agents.codex_agent import _CLAUDE_TO_CODEX_TOOL_MAP
    
        assert _CLAUDE_TO_CODEX_TOOL_MAP["Bash"] == "shell"
        assert _CLAUDE_TO_CODEX_TOOL_MAP["Write"] == "apply_patch"
        assert _CLAUDE_TO_CODEX_TOOL_MAP["Edit"] == "apply_patch"
        assert _CLAUDE_TO_CODEX_TOOL_MAP["Read"] == "shell"
    
    def test_permission_mode_mapping():
        """Verify permission_mode maps to a sandbox; approval is uniformly deny_all."""
        from coder_eval.agents.codex_agent import (
            _CODEX_APPROVAL_MODE,
            _PERMISSION_MODE_TO_SANDBOX,
        )
    
        assert _PERMISSION_MODE_TO_SANDBOX["acceptEdits"] == "workspace-write"
        assert _PERMISSION_MODE_TO_SANDBOX["plan"] == "read-only"
        # Approval is the same for every permission mode — no per-mode mapping.
        assert _CODEX_APPROVAL_MODE == "deny_all"
    ```
    
    ## References
    
    - [Codex SDK Documentation](https://developers.openai.com/codex/sdk)
    - [Codex CLI Guide](https://developers.openai.com/codex/cli)
    - [Codex GitHub Repository](https://github.com/openai/codex)
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related