Skip to main content
The codex configuration section controls how Symphony launches and interacts with the Codex app-server for agent execution.

Configuration

WORKFLOW.md
codex:
  command: codex --config shell_environment_policy.inherit=all --config model_reasoning_effort=xhigh --model gpt-5.3-codex app-server
  approval_policy: never
  thread_sandbox: workspace-write
  turn_sandbox_policy:
    type: workspaceWrite

Fields

command
string
default:"codex app-server"
Shell command to launch the Codex app-server process.The runtime launches this command via bash -lc <command> in the workspace directory.Common patterns:
# Basic
command: codex app-server

# With model and config flags
command: codex --model gpt-5.3-codex --config model_reasoning_effort=xhigh app-server

# With shell environment inheritance
command: codex --config shell_environment_policy.inherit=all app-server
Required: Must be non-empty after trimming. Validated on startup and before each dispatch.
approval_policy
string | object
Codex AskForApproval policy for command/file approval requests.Supported values depend on your Codex app-server version. Common options:
  • "never": Auto-approve all requests
  • "always": Require approval for every action (blocks unattended runs)
  • Object form for fine-grained control:
    approval_policy:
      reject:
        sandbox_approval: true
        rules: true
        mcp_elicitations: true
    
For unattended orchestration, use "never" or an auto-approve policy. Symphony does not implement approval UI.
To inspect supported values for your Codex version:
codex app-server generate-json-schema --out ./schema
cat schema/v2/ThreadStartParams.json
thread_sandbox
string
Codex SandboxMode for the app-server thread.Common values:
  • "workspace-write": Allow writes within workspace directory
  • "full-access": No filesystem restrictions
  • "read-only": Read-only filesystem access
Consult your Codex schema for supported modes:
codex app-server generate-json-schema --out ./schema
cat schema/v2/ThreadStartParams.json
turn_sandbox_policy
object
Codex SandboxPolicy for each turn execution.Defines filesystem, network, and process isolation for agent tool execution.Common structure:
turn_sandbox_policy:
  type: workspaceWrite
  writableRoots:
    - /absolute/path/to/workspace
  readOnlyAccess:
    type: fullAccess
  networkAccess: false
Symphony automatically injects the workspace path into writableRoots when type: workspaceWrite.Default policy from source:
%{
  "type" => "workspaceWrite",
  "writableRoots" => [workspace_path],
  "readOnlyAccess" => %{"type" => "fullAccess"},
  "networkAccess" => false,
  "excludeTmpdirEnvVar" => false,
  "excludeSlashTmp" => false
}
turn_timeout_ms
integer
default:3600000
Maximum duration for a single Codex turn in milliseconds (1 hour default).If a turn exceeds this timeout, the worker exits with a timeout error and schedules a retry.Example values:
  • 1800000 = 30 minutes
  • 3600000 = 1 hour (default)
  • 7200000 = 2 hours
read_timeout_ms
integer
default:5000
Timeout for synchronous request/response messages during session startup (5 seconds default).Applies to:
  • initialize request
  • thread/start request
  • Other startup handshake messages
Does not apply to turn streaming (use turn_timeout_ms instead).
stall_timeout_ms
integer
default:300000
Maximum idle time before a running agent is considered stalled (5 minutes default).The orchestrator tracks the last Codex event timestamp for each running session. If no events arrive within this window, the session is terminated and retried.Set to 0 or negative to disable stall detection.From source:
# Reconciliation stall detection
elapsed_ms = now_ms - (last_codex_timestamp || started_at)
if elapsed_ms > codex_stall_timeout_ms() and codex_stall_timeout_ms() > 0 do
  terminate_worker(issue_id)
  schedule_retry(issue_id)
end

App-Server Protocol

Symphony communicates with Codex using the app-server JSON-RPC protocol over stdio.

Session Startup Handshake

{"id":1,"method":"initialize","params":{"clientInfo":{"name":"symphony","version":"1.0"},"capabilities":{}}}
{"method":"initialized","params":{}}
{"id":2,"method":"thread/start","params":{"approvalPolicy":"never","sandbox":"workspace-write","cwd":"/abs/workspace/path"}}
{"id":3,"method":"turn/start","params":{"threadId":"thread-123","input":[{"type":"text","text":"<rendered-prompt>"}],"cwd":"/abs/workspace/path","title":"ABC-123: Issue Title","approvalPolicy":"never","sandboxPolicy":{"type":"workspaceWrite"}}}

Turn Streaming

The agent runner reads line-delimited JSON messages from stdout:
  • turn/completed → Success, check issue state for continuation
  • turn/failed → Failure, schedule retry
  • turn/cancelled → Failure, schedule retry
  • Other events → Update orchestrator state (tokens, rate limits)

Continuation Turns

After a successful turn, if the issue is still active:
{"id":4,"method":"turn/start","params":{"threadId":"thread-123","input":[{"type":"text","text":"Continuation guidance:\n\n- The previous Codex turn completed normally..."}],"cwd":"/abs/workspace/path","title":"ABC-123: Issue Title","approvalPolicy":"never","sandboxPolicy":{"type":"workspaceWrite"}}}
The same threadId is reused, so the agent has full context from prior turns.

Sandboxing Strategies

Allow writes only within the issue workspace:
codex:
  thread_sandbox: workspace-write
  turn_sandbox_policy:
    type: workspaceWrite
Symphony injects the workspace path automatically.

Full Access (High Trust)

No filesystem restrictions:
codex:
  thread_sandbox: full-access
  turn_sandbox_policy:
    type: fullAccess
Use with caution. The agent can read/write anywhere the process user has permissions.

Custom Writable Roots

Allow writes to specific directories:
codex:
  thread_sandbox: workspace-write
  turn_sandbox_policy:
    type: workspaceWrite
    writableRoots:
      - /path/to/workspace
      - /path/to/shared/cache

Approval Handling

Symphony auto-approves requests when approval_policy: never:
# From app_server.ex
def handle_approval_request(state, request_id, _approval_request) do
  response = %{
    "id" => request_id,
    "result" => %{"approved" => true}
  }
  Port.command(state.codex_port, [Jason.encode!(response), "\n"])
  {:noreply, state}
end
User input requests always fail the run (no interactive UI):
def handle_turn_input_required(state, _message) do
  send_port_exit(state, :turn_input_required, "Turn requested user input")
  {:noreply, state}
end

Configuration Reloading

Codex configuration changes are applied dynamically:
  • command: Affects newly launched agent processes
  • approval_policy, thread_sandbox, turn_sandbox_policy: Affect new sessions
  • Timeout values: Affect new turns and reconciliation cycles
Running agents continue with their original configuration.

Examples

Standard Workflow

codex:
  command: codex app-server
  approval_policy: never
  thread_sandbox: workspace-write
  turn_sandbox_policy:
    type: workspaceWrite

High-Reasoning Model

codex:
  command: codex --config model_reasoning_effort=xhigh --model gpt-5.3-codex app-server
  approval_policy: never
  thread_sandbox: workspace-write
  turn_timeout_ms: 7200000  # 2 hours

Shell Environment Inheritance

codex:
  command: codex --config shell_environment_policy.inherit=all app-server
  approval_policy: never
  thread_sandbox: workspace-write

Aggressive Timeouts

codex:
  command: codex app-server
  approval_policy: never
  thread_sandbox: workspace-write
  turn_timeout_ms: 1800000   # 30 minutes
  stall_timeout_ms: 120000   # 2 minutes
  • agent - Control turn budgets and concurrency
  • workspace - Configure workspace root and safety
  • hooks - Run scripts before/after agent execution

Build docs developers (and LLMs) love