Codex CLI Integration

Purpose

The Codex backend enables fab to use OpenAI's Codex CLI as an agent backend. It translates fab's agent protocol into Codex's event-based JSONL format, manages sessions using Codex's thread ID resume flow, and converts Codex output back to fab's canonical StreamMessage format.

Non-goals:

Interface

Backend Interface

The CodexBackend implements the Backend interface:

Method Description
Name() Returns "codex"
BuildCommand(cfg) Creates exec.Cmd for codex exec or codex exec resume
ParseStreamMessage(line) Converts Codex events to StreamMessage
FormatInputMessage(content, sessionID) Formats submissions for stdin
HookSettings(fabPath) Returns nil (Codex uses built-in approval modes)

Command Invocation

New session:

codex exec --json --full-auto -c 'model_reasoning_effort="xhigh"' "initial prompt"

Resume session:

codex exec resume --json --full-auto -c 'model_reasoning_effort="xhigh"' <thread-id> "follow-up prompt"

Key flags:

JSONL Output Format

Codex outputs events as newline-delimited JSON:

Event Type Description
thread.started Session initialization with thread_id
turn.started New turn beginning
turn.completed Turn complete with usage stats
item.started Tool use beginning
item.completed Tool use or message complete
error Error event
warning Warning event

The item.completed events contain different item types:

Item Type Fields Description
reasoning text Agent reasoning/thinking
command_execution command, aggregated_output, exit_code, status Shell command execution
agent_message text Agent text response

Approval & Sandbox Modes

Codex uses built-in approval modes rather than external hooks. fab cannot intercept Codex tool executions for custom approval.

Approval modes (via --ask-for-approval or -a):

Mode Behavior
untrusted Only run "trusted" commands without approval
on-failure Run all, only ask if command fails
on-request Model decides when to ask
never Never ask for approval

Sandbox modes (via --sandbox or -s):

Mode Behavior
read-only Read-only access
workspace-write Write within workspace only
danger-full-access Full system access

Configuration

Configure Codex as the agent backend in ~/.config/fab/config.toml:

Per-Project Configuration

[[projects]]
name = "my-project"
remote-url = "git@github.com:user/my-project.git"
agent-backend = "codex"      # Fallback for planner and coding if not set
planner-backend = "codex"    # Backend for planning agents
coding-backend = "codex"     # Backend for coding agents

Global Default

[defaults]
agent-backend = "codex"

Backend Resolution Order

  1. planner-backend / coding-backend (if explicitly set)
  2. agent-backend (project-level fallback)
  3. [defaults].agent-backend (global default)
  4. "claude" (hardcoded default)

Verification

Run the Codex backend unit tests:

$ go test ./internal/backend/... -run Codex -v

Test the stream message parsing specifically:

$ go test ./internal/backend/... -run TestCodexBackend_ParseStreamMessage -v

Examples

Session Lifecycle

  1. Start session: fab spawns codex exec --json --full-auto -c ... "prompt"
  2. Capture thread ID: fab parses the thread.started event to extract thread_id
  3. Resume session: For follow-up messages, fab spawns a new process using codex exec resume --json --full-auto -c ... <thread-id> "follow-up"

Sample JSONL Output

{"type":"thread.started","thread_id":"019bac20-11a2-7061-9708-dda3b7642ac3"}
{"type":"turn.started"}
{"type":"item.completed","item":{"id":"item_0","type":"reasoning","text":"**Creating a new file...**"}}
{"type":"item.started","item":{"id":"item_1","type":"command_execution","command":"...","status":"in_progress"}}
{"type":"item.completed","item":{"id":"item_1","type":"command_execution","command":"...","exit_code":0,"status":"completed"}}
{"type":"item.completed","item":{"id":"item_2","type":"agent_message","text":"Created `hello.txt`."}}
{"type":"turn.completed","usage":{"input_tokens":8202,"cached_input_tokens":6400,"output_tokens":55}}

Mixed Backend Configuration

[defaults]
agent-backend = "claude"

[[projects]]
name = "openai-project"
remote-url = "git@github.com:user/openai-project.git"
agent-backend = "codex"

[[projects]]
name = "anthropic-project"
remote-url = "git@github.com:user/anthropic-project.git"
# Uses global default: claude

Gotchas

Decisions

Event-based protocol: Codex uses flat event messages rather than Claude Code's nested message structure. This requires event-type-specific conversion logic in ParseStreamMessage.

Process-per-turn: Codex requires a new process for each follow-up rather than stdin communication. This adds latency but simplifies state management since each process is independent.

No hook interception: Codex approval modes are built-in and cannot be overridden. fab passes --full-auto which uses workspace-write sandbox and on-request approval.

Paths