Skip to content

Commit 0e68788

Browse files
ivasuyAbirAbbasclaude
authored
Add Codex CLI runtime support (#72)
* feat: add shared runtime provider mapping * feat: support codex runtime in planner config * feat: support codex runtime in fast agent * refactor: route harness providers through shared adapter * fix: preserve fast runtime fallback mapping * feat: patch agentfield codex structured output * chore: install codex cli in docker images * docs: document codex runtime configuration * fix: stabilize docker codex runtime startup * fix: make codex structured output schema-valid * fix(codex): gate prompt-suffix patch on active provider The codex harness patch was replacing _schema.build_prompt_suffix and _runner.build_prompt_suffix globally at import time, so claude_code and open_code runs were also receiving the codex-specific instruction: "Do not try to create .agentfield_output.json yourself; the Codex CLI will persist your final JSON response for AgentField." That instruction is wrong for those providers — Claude / OpenCode are supposed to use their Write tool to create the output file (the fast path the runner expects), and forcing them onto the stdout-parse fallback costs latency, drops the inline schema for small schemas, and sends a confusing instruction referencing a Codex CLI that isn't in the loop. Use a contextvars.ContextVar set by a wrapped Agent.harness so that the suffix dispatcher returns the codex-native suffix only when the active call is for codex, and falls back to the original AgentField suffix for every other provider. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(fast-planner): force fallback_used=False on successful parse The codex strict-schema patch strips `default` from properties and marks every field as required, so when FastPlanResult flows through Codex the model has to invent a value for `fallback_used`. Despite the prompt example showing `false`, Codex sometimes returns `true` alongside a perfectly valid task list — making the flag meaningless for any downstream consumer that gates on it. `fallback_used` is planner-side state, not an LLM self-assessment: it should be True iff the planner's `_fallback_plan(...)` path ran. Override it back to False after a successful parse so the flag reflects what actually happened, regardless of what the model wrote. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(codex): flag SWE_DEFAULT_MODEL requirement and bwrap host needs Two gotchas surfaced when actually running a full main-mode build with the codex runtime that weren't covered in the existing setup notes: 1. The Docker image bakes ENV HARNESS_MODEL=openrouter/moonshotai/kimi-k2.6 as an OpenCode-side fallback, and SWE-AF's model-resolution env cascade reads HARNESS_MODEL. So a codex deployment that only sets SWE_DEFAULT_RUNTIME=codex (without SWE_DEFAULT_MODEL) hands an OpenRouter Kimi model id to the Codex CLI and the Product Manager reasoner fails in ~13s. Document that SWE_DEFAULT_MODEL=gpt-5.3-codex (or per-build models map) is required to pin the Codex model. 2. Codex CLI's workspace-write sandbox uses bubblewrap (`bwrap`) and needs Linux user namespaces enabled on the host. Docker-on-WSL2 and hardened environments refuse with "bwrap: No permissions to create a new namespace", and the coder agents return success while writing no files. Document the symptom so operators can recognize and fix it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Abir Abbas <abirabbas1998@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6fdf197 commit 0e68788

30 files changed

Lines changed: 864 additions & 59 deletions

.env.example

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,17 @@ ANTHROPIC_API_KEY=sk-ant-api03-...
1414
# OpenRouter (recommended - 200+ models including DeepSeek, Qwen, Llama, MiniMax)
1515
# OPENROUTER_API_KEY=sk-or-v1-...
1616

17-
# OpenAI (GPT-4, GPT-4o, etc.)
17+
# OpenAI API-platform billing for OpenAI models and Codex api_key mode.
1818
# OPENAI_API_KEY=sk-...
1919

20+
# Codex CLI runtime auth. Values:
21+
# auto Use OPENAI_API_KEY when set; otherwise use local Codex login.
22+
# chatgpt Use ChatGPT Free/Plus/Pro/Team login. Run `codex login` on the
23+
# host, keep OPENAI_API_KEY unset for this process, and Docker will
24+
# mount ~/.codex into both swe-planner and swe-fast.
25+
# api_key Use OpenAI API-platform billing. Set OPENAI_API_KEY=sk-...
26+
# SWE_CODEX_AUTH_MODE=auto
27+
2028
# Google Gemini
2129
# GOOGLE_API_KEY=...
2230

@@ -66,7 +74,7 @@ GH_TOKEN=ghp_...
6674
# Lets the deployer pick the runtime once instead of every caller threading
6775
# a config through. Falls back to claude_code if unset; an invalid value is
6876
# logged as a warning and ignored.
69-
# SWE_DEFAULT_RUNTIME=claude_code # or: open_code
77+
# SWE_DEFAULT_RUNTIME=claude_code # or: open_code, codex
7078

7179
# Default model when callers don't pass `models` in the request config.
7280
# Applies to all 16 agent roles for whichever runtime is active. Caller
@@ -78,7 +86,7 @@ GH_TOKEN=ghp_...
7886

7987
# Runtime/model selection is configured via API request config (V2):
8088
# {
81-
# "runtime": "claude_code" | "open_code",
89+
# "runtime": "claude_code" | "open_code" | "codex",
8290
# "models": {
8391
# "default": "sonnet or provider/model-id",
8492
# "coder": "provider/model-id",
@@ -89,6 +97,7 @@ GH_TOKEN=ghp_...
8997
# Runtime mapping:
9098
# claude_code -> Claude backend
9199
# open_code -> OpenCode backend
100+
# codex -> OpenAI Codex CLI backend
92101
#
93102
# Legacy keys are removed: ai_provider, preset, model, and all *_model fields.
94103
#
@@ -97,6 +106,9 @@ GH_TOKEN=ghp_...
97106
#
98107
# Example Claude runtime request config:
99108
# {"runtime": "claude_code", "models": {"default": "sonnet", "coder": "opus"}}
109+
#
110+
# Example Codex runtime request config:
111+
# {"runtime": "codex", "models": {"default": "gpt-5.3-codex"}}
100112

101113
# Available open runtime model IDs (format: provider/model-name):
102114
# deepseek/deepseek-chat # DeepSeek via OpenRouter

Dockerfile

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ WORKDIR /app
88
# System deps: git (worktrees, branches), curl (healthcheck), jq (agent bash),
99
# openssh-client (optional SSH git), gh CLI (draft PRs)
1010
RUN apt-get update && apt-get install -y --no-install-recommends \
11-
git curl openssh-client jq && \
11+
git curl openssh-client jq nodejs npm && \
1212
# Install GitHub CLI
1313
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
1414
| dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \
@@ -17,6 +17,37 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
1717
apt-get update && apt-get install -y --no-install-recommends gh && \
1818
# Install OpenCode CLI v1.2+ for opencode provider (with run --model support)
1919
curl -fsSL https://opencode.ai/install | bash && \
20+
# Install Codex CLI for codex runtime provider
21+
npm install -g @openai/codex && \
22+
codex_path="$(command -v codex)" && \
23+
mv "${codex_path}" /usr/local/bin/codex-real && \
24+
printf '%s\n' \
25+
'#!/usr/bin/env bash' \
26+
'set -euo pipefail' \
27+
'' \
28+
'auth_mode="${SWE_CODEX_AUTH_MODE:-auto}"' \
29+
'' \
30+
'case "${auth_mode}" in' \
31+
' chatgpt)' \
32+
' unset OPENAI_API_KEY' \
33+
' ;;' \
34+
' api_key)' \
35+
' if [ -z "${OPENAI_API_KEY:-}" ]; then' \
36+
' echo "SWE_CODEX_AUTH_MODE=api_key requires OPENAI_API_KEY to be set" >&2' \
37+
' exit 2' \
38+
' fi' \
39+
' ;;' \
40+
' auto)' \
41+
' ;;' \
42+
' *)' \
43+
' echo "Invalid SWE_CODEX_AUTH_MODE: ${auth_mode}. Expected one of: auto, chatgpt, api_key" >&2' \
44+
' exit 2' \
45+
' ;;' \
46+
'esac' \
47+
'' \
48+
'exec /usr/local/bin/codex-real "$@"' \
49+
> /usr/local/bin/codex && \
50+
chmod +x /usr/local/bin/codex && \
2051
rm -rf /var/lib/apt/lists/*
2152

2253
# Add OpenCode to PATH for non-interactive shells

README.md

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,10 @@ Most agent frameworks wrap a single coder loop. SWE-AF is a coordinated engineer
188188

189189
</details>
190190

191-
**Claude & open-source models supported**: Run builds with either runtime and tune models per role in one flat config map.
191+
**Claude, open-source, and Codex models supported**: Run builds with any runtime and tune models per role in one flat config map.
192192
- `runtime: "claude_code"` maps to Claude backend.
193193
- `runtime: "open_code"` maps to OpenCode backend (OpenRouter/OpenAI/Google/Anthropic model IDs).
194+
- `runtime: "codex"` maps to the OpenAI Codex CLI backend.
194195

195196
## Adaptive Factory Control
196197

@@ -279,6 +280,42 @@ curl -X POST http://localhost:8080/api/v1/execute/async/swe-planner.build \
279280
}
280281
JSON
281282

283+
# With Codex CLI runtime
284+
curl -X POST http://localhost:8080/api/v1/execute/async/swe-planner.build \
285+
-H "Content-Type: application/json" \
286+
-d @- <<'JSON'
287+
{
288+
"input": {
289+
"goal": "Add JWT auth",
290+
"repo_url": "https://github.com/user/my-project",
291+
"config": {
292+
"runtime": "codex",
293+
"models": {
294+
"default": "gpt-5.3-codex"
295+
}
296+
}
297+
}
298+
}
299+
JSON
300+
301+
# Fast mode with Codex CLI runtime
302+
curl -X POST http://localhost:8080/api/v1/execute/async/swe-fast.build \
303+
-H "Content-Type: application/json" \
304+
-d @- <<'JSON'
305+
{
306+
"input": {
307+
"goal": "Add a focused bug fix",
308+
"repo_url": "https://github.com/user/my-project",
309+
"config": {
310+
"runtime": "codex",
311+
"models": {
312+
"default": "gpt-5.3-codex"
313+
}
314+
}
315+
}
316+
}
317+
JSON
318+
282319
# Local workspace mode (repo_path) + targeted role override
283320
curl -X POST http://localhost:8080/api/v1/execute/async/swe-planner.build \
284321
-H "Content-Type: application/json" \
@@ -303,6 +340,12 @@ JSON
303340

304341
For OpenRouter with `open_code`, use model IDs in `openrouter/<provider>/<model>` format (for example `openrouter/minimax/minimax-m2.5`).
305342

343+
For Codex with ChatGPT subscription auth, install the Codex CLI on the host, run `codex login`, leave `OPENAI_API_KEY` unset for this process, and set `SWE_CODEX_AUTH_MODE=chatgpt` or `auto`. For OpenAI API-platform billing, set `SWE_CODEX_AUTH_MODE=api_key` and `OPENAI_API_KEY`.
344+
345+
> **Codex deployments using the Docker image must set `SWE_DEFAULT_MODEL=gpt-5.3-codex` on the environment** (or pass `models: {"default": "gpt-5.3-codex"}` in every build's `config`). The image bakes `HARNESS_MODEL=openrouter/moonshotai/kimi-k2.6` as an OpenCode fallback, and SWE-AF's model-resolution env cascade reads `HARNESS_MODEL` — so without `SWE_DEFAULT_MODEL` set, the Codex CLI receives an OpenRouter model id it can't handle and the Product Manager reasoner fails in ~13s. Setting `SWE_DEFAULT_MODEL` makes the cascade pin every role to the Codex model.
346+
347+
> Codex CLI's `workspace-write` sandbox uses bubblewrap (`bwrap`) and needs Linux user namespaces enabled on the host. Most production Linux hosts and managed container runtimes (Railway, etc.) allow this by default, but local Docker on WSL2 or hardened environments may refuse with `bwrap: No permissions to create a new namespace`. If the verifier reports that error, the coder ran but couldn't write files — enable user namespaces on the host before relying on the codex runtime there.
348+
306349
### Optional: web search
307350

308351
Coding and review agents can look up external documentation, library APIs, error messages, and version/deprecation status during a build. This is opt-in via two env vars on the deployment:
@@ -611,7 +654,7 @@ Pass `config` to `build` or `execute`. Full schema: [`swe_af/execution/schemas.p
611654

612655
| Key | Default | Description |
613656
| ------------------------- | --------------- | ----------------------------------------------------- |
614-
| `runtime` | `"claude_code"` | Model runtime: `"claude_code"` or `"open_code"`. The default also honors the `SWE_DEFAULT_RUNTIME` env var when no `runtime` is passed in `config` — set it on the deployment so callers don't need to plumb a config through. |
657+
| `runtime` | `"claude_code"` | Model runtime: `"claude_code"`, `"open_code"`, or `"codex"`. The default also honors the `SWE_DEFAULT_RUNTIME` env var when no `runtime` is passed in `config` — set it on the deployment so callers don't need to plumb a config through. |
615658
| `models` | `null` | Flat role-model map (`default` + role keys below). Without a caller-supplied value, the `SWE_DEFAULT_MODEL` env var is used as the default for all roles — set it on the deployment to pin a model without code changes. Caller `models.default` or per-role keys still win. |
616659
| `max_coding_iterations` | `5` | Inner-loop retry budget |
617660
| `max_advisor_invocations` | `2` | Middle-loop advisor budget |
@@ -655,6 +698,17 @@ Minimal:
655698
}
656699
```
657700

701+
Codex:
702+
703+
```json
704+
{
705+
"runtime": "codex",
706+
"models": {
707+
"default": "gpt-5.3-codex"
708+
}
709+
}
710+
```
711+
658712
Fully customized:
659713

660714
```json

docker-compose.local.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,15 @@ services:
1919
- PORT=8003
2020
# Callback URL for control plane to reach this agent
2121
- AGENT_CALLBACK_URL=http://localhost:8003
22+
- SWE_DEFAULT_RUNTIME=${SWE_DEFAULT_RUNTIME:-claude_code}
23+
- SWE_DEFAULT_MODEL=${SWE_DEFAULT_MODEL:-}
24+
- SWE_CODEX_AUTH_MODE=${SWE_CODEX_AUTH_MODE:-auto}
25+
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
2226
ports:
2327
- "8003:8003"
2428
volumes:
2529
- workspaces:/workspaces
30+
- ${HOME}/.codex:/root/.codex
2631
extra_hosts:
2732
- "host.docker.internal:host-gateway"
2833

docker-compose.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,15 @@ services:
1919
- NODE_ID=swe-planner
2020
- PORT=8003
2121
- AGENT_CALLBACK_URL=http://swe-agent:8003
22+
- SWE_DEFAULT_RUNTIME=${SWE_DEFAULT_RUNTIME:-claude_code}
23+
- SWE_DEFAULT_MODEL=${SWE_DEFAULT_MODEL:-}
24+
- SWE_CODEX_AUTH_MODE=${SWE_CODEX_AUTH_MODE:-auto}
25+
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
2226
ports:
2327
- "8003:8003"
2428
volumes:
2529
- workspaces:/workspaces
30+
- ${HOME}/.codex:/root/.codex
2631
depends_on:
2732
- control-plane
2833
deploy:
@@ -33,6 +38,7 @@ services:
3338
context: .
3439
dockerfile: Dockerfile
3540
command: ["python", "-m", "swe_af.fast"]
41+
env_file: .env
3642
environment:
3743
- AGENTFIELD_SERVER=http://control-plane:8080
3844
- NODE_ID=swe-fast
@@ -45,10 +51,14 @@ services:
4551
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
4652
- GOOGLE_API_KEY=${GOOGLE_API_KEY:-}
4753
- OPENCODE_MODEL=${OPENCODE_MODEL:-}
54+
- SWE_DEFAULT_RUNTIME=${SWE_DEFAULT_RUNTIME:-claude_code}
55+
- SWE_DEFAULT_MODEL=${SWE_DEFAULT_MODEL:-}
56+
- SWE_CODEX_AUTH_MODE=${SWE_CODEX_AUTH_MODE:-auto}
4857
ports:
4958
- "8004:8004"
5059
volumes:
5160
- workspaces:/workspaces
61+
- ${HOME}/.codex:/root/.codex
5262
depends_on:
5363
- control-plane
5464

docs/ARCHITECTURE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ SWE-AF orchestrates 22 specialized agents across four phases. Each agent is a re
422422

423423
Every build now uses a single V2 model contract:
424424

425-
- `runtime`: `claude_code` or `open_code`
425+
- `runtime`: `claude_code`, `open_code`, or `codex`
426426
- `models`: flat role map (`default` + explicit role keys)
427427

428428
Supported role keys:
@@ -442,3 +442,4 @@ Runtime defaults:
442442
|---|---|---|
443443
| `claude_code` | `sonnet` | `qa_synthesizer=haiku` |
444444
| `open_code` | `minimax/minimax-m2.5` | none |
445+
| `codex` | `gpt-5.3-codex` | none |

docs/CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Thanks for contributing to SWE-AF.
66

77
- Python 3.12+
88
- AgentField control plane (`af`)
9-
- Access to an AI runtime used by your run (`claude_code` or `open_code`)
9+
- Access to an AI runtime used by your run (`claude_code`, `open_code`, or `codex`)
1010

1111
## Local setup
1212

docs/SKILL.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ curl -X POST http://localhost:8080/api/v1/execute/async/swe-planner.build \
7575

7676
| Key | Values | Description |
7777
|-----|--------|-------------|
78-
| `runtime` | `"claude_code"`, `"open_code"` | AI backend to use |
78+
| `runtime` | `"claude_code"`, `"open_code"`, `"codex"` | AI backend to use |
7979
| `models.default` | model ID string | Default model for all agents |
8080
| `models.coder` | model ID string | Override for coder role |
8181
| `models.qa` | model ID string | Override for QA role |
@@ -148,6 +148,12 @@ curl -X POST http://localhost:8080/api/v1/execute/async/swe-planner.build \
148148
2. Model provider credentials configured in OpenCode (e.g., `OPENAI_API_KEY` for z.ai)
149149
3. Model ID format matches what OpenCode expects
150150

151+
## Requirements for codex Runtime
152+
153+
1. Codex CLI installed and in PATH.
154+
2. For ChatGPT subscription auth: run `codex login` on the host, set `SWE_CODEX_AUTH_MODE=chatgpt` or `auto`, and leave `OPENAI_API_KEY` unset for the agent process.
155+
3. For OpenAI API-platform billing: set `SWE_CODEX_AUTH_MODE=api_key` and `OPENAI_API_KEY`.
156+
151157
## Monitoring
152158

153159
```bash

docs/deployment.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ cp .env.example .env
3535
| `OPENAI_API_KEY` | OpenAI API key |
3636
| `GOOGLE_API_KEY` | Google Gemini API key |
3737

38+
**For Codex CLI runtime:**
39+
40+
| Variable | Purpose |
41+
|---|---|
42+
| `SWE_CODEX_AUTH_MODE` | `auto`, `chatgpt`, or `api_key`; defaults to `auto` in Docker |
43+
| `OPENAI_API_KEY` | Required only when `SWE_CODEX_AUTH_MODE=api_key` |
44+
3845
**Optional:**
3946

4047
| Variable | Purpose | Default |
@@ -51,6 +58,7 @@ cp .env.example .env
5158
| `agentfield` | 0.1.67+ | Python SDK (includes opencode v1.4+ fix) |
5259
| `claude-agent-sdk` | 0.1.20+ | Claude runtime |
5360
| opencode CLI | 1.4+ | Only if using `open_code` runtime (see Known Issues) |
61+
| Codex CLI | latest | Installed in the Docker image; required on host only to run `codex login` for ChatGPT subscription auth |
5462

5563
## Quick Start
5664

@@ -68,6 +76,8 @@ This starts:
6876
- **swe-agent** on `:8003` — SWE-AF full pipeline (`swe-planner` node)
6977
- **swe-fast** on `:8004` — SWE-AF fast mode (`swe-fast` node)
7078

79+
To use Codex with a ChatGPT subscription, run `codex login` on the host before starting Docker and leave `OPENAI_API_KEY` unset for this process. The compose files mount `~/.codex` into both agent containers. To use OpenAI API billing instead, set `SWE_CODEX_AUTH_MODE=api_key` and `OPENAI_API_KEY`.
80+
7181
### Agent Only (connect to existing control plane)
7282

7383
If you already have an AgentField control plane running:

requirements-docker.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ pydantic>=2.0
77
claude-agent-sdk==0.1.20
88
hax-sdk>=0.2.0
99
python-dotenv>=1.0
10+
# cryptography 48.0.0 currently crashes with SIGILL on some Linux/aarch64
11+
# Docker hosts when AgentField imports Ed25519 for DID registration.
12+
cryptography<46

0 commit comments

Comments
 (0)