Codex

View as Markdown

Use this guide to observe local Codex CLI sessions and local Codex GUI or app sessions that honor the same local config and gateway routing. Cloud or remote Codex tasks are partial or unsupported for local gateway LLM capture because the local gateway cannot observe provider traffic that never reaches the machine.

Requirements

Install codex-cli 0.143.0 or newer. Persistent installation requires one discovered, enabled, and trusted handler for each of the 10 events in the supported Codex hook schema: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PermissionRequest, SubagentStart, SubagentStop, Stop, PreCompact, and PostCompact. Persistent setup also requires the features.hooks flag, plugin app-server metadata, and Relay’s built-in OpenAI endpoint override. The installer checks the Codex version before it changes any files.

Codex requires hooks to be trusted before they run. nemo-relay install codex uses the Codex app-server API to trust and verify only the exact hooks owned by nemo-relay-plugin@nemo-relay-local. Source-marketplace or manually installed hooks can still require review. This behavior is tracked upstream as openai/codex#21639.

ChatGPT Voice Calls

ChatGPT voice call setup uses a different request format from the public API. Codex selects that format from its call-creation URL.

For persistent installs, Relay checks the sign-in mode in auth.json next to config.toml. It also honors forced_login_method. For ChatGPT sign-in, it sets experimental_realtime_webrtc_call_base_url to the local Relay URL ending in /backend-api/codex. This lets Codex send JSON to /realtime/calls. Relay forwards that JSON without changing it. API-key installs keep the public API format. Relay preserves any call-creation URL you set yourself.

For nemo-relay codex, Relay applies this setting only to the launched process. An installer-generated voice URL follows the temporary gateway. Explicit URLs in your settings, selected profile, or command-line options take priority. Relay leaves the saved configuration unchanged.

For a personal persistent install, update Relay, run nemo-relay install codex --force, and restart Codex Desktop to apply the new setting. Replacing the binary alone does not update Codex’s saved configuration.

If your credentials are stored only in a keyring, Relay cannot read the sign-in mode from auth.json. For ChatGPT sign-in, set this option at the top level of your user-level Codex configuration. Replace the address if your gateway uses a different port.

experimental_realtime_webrtc_call_base_url = "http://127.0.0.1:47632/backend-api/codex"

For a managed daemon deployment, apply the manual setting described in Managed Codex Configuration. The personal-install command does not update a managed deployment.

WebRTC sidebands keep Codex’s separate destination settings. The default sideband can connect directly to OpenAI. This call-creation setting does not route the audio through Relay.

Multi-Agent Delegation Traces

In Codex multi-agent v2, the Responses service encrypts the message argument for spawn_agent, send_message, and followup_task. The parent response contains ciphertext in the tool call. Codex stores that value as encrypted_content, forwards it in an agent_message, and lets the Responses service decrypt it for the recipient model.

When the parent and child requests both pass through the Relay gateway, Relay can record the parent tool name, task name, and encrypted message. It can also record the sender and recipient routing fields in the child request and the child’s response. It cannot recover the delegated task from the ciphertext. Relay uses native agent and thread identities to link parent and child work under a shared codex-task scope. Links can be missing when Codex does not provide the identities or lifecycle events needed to match the work. ATOF, ATIF, OpenTelemetry, and OpenInference output can still show the activity they observe, but none of these formats can reconstruct the missing plaintext.

OpenAI introduced this transport in openai/codex#26210. Refer to openai/codex#28058 for the upstream auditability discussion and NVIDIA/NeMo-Relay#425 for a Relay reproduction.

Relay asks Codex to use the readable legacy path for managed sessions. When the selected model and runtime support that path, new traces retain the delegated task text. Relay applies the setting automatically:

  • nemo-relay codex and nemo-relay run -- codex pass the process-local Codex override features.multi_agent_v2.enabled=false.
  • nemo-relay install codex writes the equivalent nested setting to ~/.codex/config.toml. On uninstall, Relay restores the earlier enabled value or, when none existed, removes only the value it added. It preserves other multi_agent_v2 settings and leaves later user overrides in place.

Treat this setting as a preference, not proof that the legacy path is active. A higher-precedence Codex config override can re-enable v2, and the selected model can request v2 even when this setting is false. Before you rely on readable delegation text, check the captured payload: v2 uses ciphertext in the parent tool call and encrypted_content in the recipient request.

Relay cannot decrypt a v2 payload. The setting applies to new Codex sessions and cannot recover ciphertext already stored in a trace. It changes the application-level inter-agent message format. It does not disable or weaken TLS between Codex, Relay, and the upstream endpoint.

Transparent Run

Use the wrapper for no-install local observability:

nemo-relay codex

Pass Codex arguments after --:

nemo-relay codex -- exec "Summarize this repository."

This shortcut is equivalent to nemo-relay run -- codex. The wrapper starts a gateway on a dynamic 127.0.0.1 port, enables Codex hooks with CLI config overrides, injects hook commands that embed that gateway URL, and points Codex at a temporary nemo-relay-openai provider alias while preserving Codex’s OpenAI auth path. Exact trust for the generated hooks and process-local disable entries for the known local and source Relay plugin hook identities are supplied in the same CLI layer. Relay does not replace or rewrite the selected Codex profile. If a Relay plugin is already enabled, its MCP process authenticates, borrows, and monitors that exact dynamic gateway. Only the wrapper-owned hooks remain enabled for that process. Those hooks authenticate the wrapper gateway before sending lifecycle payloads.

Inspect what would be launched without starting Codex:

nemo-relay run \
--dry-run \
--print \
-- codex

Place Codex --config overrides before the exec subcommand:

nemo-relay codex -- --config 'model_reasoning_effort="high"' exec "Summarize this repository."

Codex accepts overrides before or after exec, but treats overrides after exec as command-specific and gives them higher precedence than root-level overrides, which can conflict with --config settings that Relay provides.

Persistent Plugin Install

Install the persistent plugin for Codex. It should start normally and load NeMo Relay from its plugin marketplace:

nemo-relay install codex

The installer creates a local marketplace, installs nemo-relay-plugin@nemo-relay-local, enables Codex hooks, and routes Codex’s built-in openai provider through a plain URL on the managed loopback gateway. It appends a managed authentication assignment to $CODEX_HOME/.env (normally ~/.codex/.env), which Codex reads at startup. The OpenAI-Project header carries a namespaced Relay proof. Relay validates and removes the proof before request observability or upstream forwarding. Existing project metadata is preserved. Credentials are not embedded in the generated URL. This preserves openai as the provider identity recorded in new Codex sessions, so sessions created before and after installing Relay stay in the same provider history. The installer retains the nemo-relay-openai alias only to resume sessions created by earlier Relay versions. It then uses the existing nemo-relay binary on PATH. It does not install a plugin-local Relay binary.

Quit and restart Codex after installation, refresh, or removal so it reloads both configuration and startup environment. No special launcher or shell export is required. Existing .env settings are preserved. Uninstall removes Relay’s managed assignment. Do not edit that assignment directly. Legacy capability URLs and dedicated Relay authentication headers remain accepted for older clients.

If your config explicitly selects another provider, installation switches it to the built-in openai provider while Relay is installed and restores your selection on uninstall.

The built-in openai provider currently attempts a WebSocket connection before falling back to HTTPS. Because Relay does not support that WebSocket transport, starting a Codex session can add several seconds of delay and emit connection errors before the first request succeeds over HTTPS. Codex does not currently provide a supported setting to disable this attempt for the built-in provider.

The required plugin MCP process is a lightweight lifecycle client. When it launches, it starts or reuses a detached Rust nemo-relay --bind 127.0.0.1:47632 sidecar. It completes MCP initialization only after /healthz authenticates the Relay identity and confirms its version and bootstrap protocol. The compatibility check binds a one-way fingerprint that includes relevant environment values into a per-user bootstrap proof. It does not return credentials or configuration secrets. A different effective persistent configuration fingerprint does not by itself prevent reuse. Clients that share the same user bootstrap state can reuse a healthy local sidecar, so the fingerprint is not the gateway ownership boundary. Dynamic plugin manifests, artifacts, and signatures included in this identity must be regular files. Relay limits the complete activation snapshot—including adjacent runtime files and a copied managed Python environment—to 100,000 filesystem entries, 512 MiB, and a maximum directory traversal depth of 128. When startup reports an activation snapshot budget error, remove unrelated files from the manifest or load-target directory, flatten deeply nested directories, or reduce the managed Python environment before retrying. Concurrent Codex and Claude Code processes can share the sidecar. Each MCP client sends a heartbeat while its stdio connection is open. The heartbeat defaults to 3 seconds and is shortened automatically when necessary to remain below the gateway idle timeout. Set NEMO_RELAY_PLUGIN_HEARTBEAT_INTERVAL_SECS to a positive integer shorter than the idle timeout to override it. The sidecar exits after 300 seconds without activity by default. Relay does not install a wrapper, launch agent, system user service, scheduled task, login item, or persistent supervisor.

When a newly launched MCP client detects a verified Relay-owned gateway from a different Relay version, it stops that old sidecar and starts a matching one. This intentionally interrupts MCP clients still attached to the old gateway. Configuration-only fingerprint differences neither replace nor reject a healthy authenticated gateway.

After upgrading Relay, refresh every Relay-managed coding-agent integration before starting a new Codex session:

nemo-relay integrations refresh

The command retires old MCP generations, replaces the authenticated sidecar, and requires affected hosts to reconnect. For one Codex install, nemo-relay install codex --force remains the focused repair command. Manual MCP configurations are not changed.

To explicitly stop the managed shared gateway, first close active MCP clients, then run:

nemo-relay --bind 127.0.0.1:47632 gateway stop

The command resolves the process listening at that exact loopback address, verifies that its executable is named nemo-relay, and stops it. It can only stop a gateway running on the local machine. Remote gateway processes are not supported. An open MCP client can restart the managed gateway as part of its one allowed recovery.

nemo-relay gateway start starts the gateway with the same server configuration as a bare daemon invocation. nemo-relay gateway stop stops that gateway at the default daemon endpoint, 127.0.0.1:4040. Pass the same --bind value to both commands for a custom endpoint. On Unix, add --force to terminate the process immediately instead of requesting a graceful shutdown. Windows always terminates the gateway process tree.

On Windows, Relay requests Job Object breakaway when the host job permits it. When breakaway is unavailable and the host permits nested assignment, the gateway remains scoped to the host job and the usual 300-second idle reuse window can end early. If the host rejects nested assignment, persistent bootstrap stops and explains the conflict instead of running without process-tree cleanup guarantees.

Persistent plugin mode loads system and user Relay configuration only. It does not load a project’s .nemo-relay files. The sidecar starts in the user Relay configuration directory, so relative exporter paths are deterministic. When configuration belongs with a repository, use nemo-relay --config path/to/config.toml run. Relay selects its sibling plugins.toml automatically.

The generated MCP entry forwards variable names, never values, for provider credentials, Relay runtime settings, common OpenTelemetry, AWS, proxy, and certificate settings. It also forwards approved NEMO_RELAY_, OTEL_, and AWS_ names that exist during installation and credential variable names referenced by user observability configuration. Rerun nemo-relay install codex --force when doctor reports a missing forwarded variable.

The installer derives a per-user client proof for managed provider requests and embeds it in the installed OpenAI base URL. Relay validates and removes that capability before middleware, observability, or upstream forwarding. The secret-bearing Codex configuration uses an owner-only mode on Unix and a protected owner/System DACL on Windows.

Installer-owned hook commands pin http://127.0.0.1:47632 and the install-generation fence. Hooks wait for the MCP-owned gateway and forward each canonical payload once. They never launch or recover Relay. An ambient NEMO_RELAY_GATEWAY_URL cannot split hook traffic from the required MCP-managed gateway.

If user configuration, forwarded credentials, or the Relay version changes while an old sidecar is still running, the new MCP client explains the compatibility conflict instead of silently reusing the wrong process. Running nemo-relay install codex --force retires a sidecar owned by the prior install through Relay’s private shutdown handshake before it refreshes the plugin. An ordinary reinstall leaves an existing fenced Codex installation unchanged. Use --force to replace it safely.

An existing install without MCP generation fencing cannot be retired safely, even when Codex reports that the plugin is no longer registered, because an already-running MCP process can outlive registration. If upgrade or uninstall reports a missing MCP generation marker, close all Codex clients and standalone nemo-relay mcp processes, then run:

codex plugin remove nemo-relay-plugin@nemo-relay-local
codex plugin marketplace remove nemo-relay-local

Remove codex-marketplace and codex.json from the install directory named in the error, then retry nemo-relay install codex --force to install a fenced generation. If removal was the original goal, run nemo-relay uninstall codex immediately afterward. The fenced reinstall lets Relay remove provider and hook trust state in the same transaction.

To check for the installed plugin, enter:

nemo-relay doctor --plugin codex

To remove the plugin, enter:

nemo-relay uninstall codex

Refer to Coding Agent Installation for install directories, shared-sidecar behavior, rollback behavior, and source marketplace notes.

Configure Transparent Runs

Create $XDG_CONFIG_HOME/nemo-relay/config.toml (or ~/.config/nemo-relay/config.toml) for user defaults:

[upstream]
openai_base_url = "https://api.openai.com/v1"
[agents.codex]
command = "codex"

Then configure observability with nemo-relay plugins edit or the XDG user plugins.toml:

version = 1
[[components]]
kind = "observability"
enabled = true
[components.config.atif]
enabled = true
output_directory = ".nemo-relay/atif"

Run nemo-relay run --agent codex to use the configuration. The ATIF files from this example are written under the project at .nemo-relay/atif.

Configure the Persistent Plugin

Create ~/.config/nemo-relay/config.toml, or $XDG_CONFIG_HOME/nemo-relay/config.toml when XDG_CONFIG_HOME is set, for persistent provider defaults. Use nemo-relay plugins edit to write user-scoped observability configuration. For example, set the ATIF output directory to atif:

version = 1
[[components]]
kind = "observability"
enabled = true
[components.config.atif]
enabled = true
output_directory = "atif"

The persistent sidecar uses the same system-over-user configuration model and starts in the user Relay configuration directory. The relative output directory in this example resolves to $XDG_CONFIG_HOME/nemo-relay/atif, or ~/.config/nemo-relay/atif when XDG_CONFIG_HOME is not set.

Standalone Gateway

Use the long-running gateway only when you want Codex running outside the wrapper:

nemo-relay --bind 127.0.0.1:4040

Then edit ~/.codex/config.toml and configure local Codex to use a gateway provider alias instead of overriding the reserved built-in openai provider:

model_provider = "nemo-relay-openai"
[model_providers.nemo-relay-openai]
name = "NeMo Relay OpenAI"
base_url = "http://127.0.0.1:4040/v1"
wire_api = "responses"
requires_openai_auth = true
supports_websockets = false

After saving the file, restart the Codex GUI or app so it reloads the provider configuration. For CLI usage, start a new codex process.

Some Codex GUI or app versions appear to scope visible conversation history by the active provider configuration. If existing conversations disappear after switching model_provider to nemo-relay-openai, the history has not been removed if it returns after restoring the previous provider configuration. Use this standalone provider alias only while capturing gateway telemetry, or prefer the persistent installation, which preserves the built-in openai provider identity. See the upstream Codex history visibility discussion for context.

Local Codex GUI or app sessions have the same support level only when they read the same local hook/plugin config and provider routing. Cloud tasks may still emit some lifecycle hooks, but complete LLM lifecycle capture requires model traffic to pass through the gateway.

Captured Events

The generated hook set includes SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PermissionRequest, SubagentStart, SubagentStop, Stop, PreCompact, and PostCompact. Both compaction hooks emit canonical compaction marks. Relay requires the Codex app-server to discover exactly one enabled, trusted handler for every generated event. PostToolUseFailure, Notification, and SessionEnd are not in the Codex 0.143 plugin hook schema, so Relay does not generate undiscoverable handlers for them. Codex work exports a codex-task agent scope rather than a session-length span. Parent turns and delegated agents share this exported ancestor. A child’s codex-turn scope sits under its delegated agent, and owned model and tool calls sit under that child turn. Native child agent_id hooks and authenticated child session aliases preserve ownership when a parent turn ends first. Native parent thread identity keeps waiting and synthesis calls attributed to the parent.

A parent Stop closes the task when no delegated agents remain. Outstanding children keep the task open for the next parent synthesis prompt, even if they finish before that prompt arrives. Children with no open work are closed after 30 seconds without child activity. Open child tools receive a one-hour inactivity allowance so long-running commands can finish without progress hooks. After that allowance, missing completion hooks are treated as abandoned work and the child and its tools close. Unrelated parent activity does not renew either timeout. In-flight gateway requests protect their owning child without preventing cleanup of unrelated stale children. A task awaiting parent synthesis also keeps the sidecar active until the task completes or its idle timeout expires. An idle timeout or gateway shutdown closes unfinished work. After completion, a new prompt in the same Codex session opens a separate task and trace.

Relay maps delivered events to agent, turn, subagent, tool, and mark lifecycle events. Prompt and stop payloads also provide LLM correlation context.

Relay emits an observed skill.load mark when it sees a complete file read through cat, bat, batcat, or PowerShell Get-Content. The final path component must equal SKILL.md under an ASCII case-insensitive comparison. Relay also recognizes one supported wrapper around that reader: sh -c, bash -c, bash -lc, zsh -c, zsh -lc, fish -c, fish --command=..., powershell -Command, or pwsh -Command. Skill-load detection is best effort. Relay must be able to observe a supported request shape. If Relay doesn’t emit a mark, that doesn’t prove that the skill wasn’t loaded. Refer to Automatic Skill-Load Marks for the complete criteria.

The transparent wrapper passes hook entries as Codex CLI config overrides and sets features.hooks=true for that launched process. It also disables the known installed Relay hook identities in the same process-local state, without changing persistent trust or enablement. Persistent install writes .codex/config.toml with features.hooks = true, and the installed plugin’s hooks/hooks.json remains the sole persistent Relay hook source. During upgrades, the installer removes legacy Relay groups from .codex/hooks.json while preserving unrelated hooks and backups.

Smoke Test

Run a small Codex prompt that starts a session and uses one tool. Then check hook forwarding directly:

curl -f http://127.0.0.1:4040/healthz
printf '{"session_id":"smoke-codex","hook_event_name":"sessionStart"}' \
| NEMO_RELAY_GATEWAY_URL=http://127.0.0.1:4040 nemo-relay hook-forward codex --fail-closed

The response should match Codex hook semantics. For most lifecycle events it is an empty JSON object.

Verify Export

Complete a Codex turn and confirm that Agent Trajectory Interchange Format (ATIF) exists at the path for the selected workflow.

For a transparent project run, enter:

ls .nemo-relay/atif

For a persistent plugin configured with the relative user-scoped path above, enter:

ls "${XDG_CONFIG_HOME:-$HOME/.config}/nemo-relay/atif"

The Codex plugin schema does not expose SessionEnd, so the gateway finalizes ATIF when the bounded codex-task closes. A parent Stop closes a task with no remaining delegated agents. Idle timeout and gateway shutdown finalize unfinished tasks. Unless you change filename_template, the file is named nemo-relay-atif-<task-scope-id>.json. It contains the parent turns and delegated work observed within that task. Separate tasks in the same Codex session produce separate trajectories. The native Codex session ID is retained in extra.nemo_relay.session_id. If the file is missing, confirm features.hooks = true, hook config loading, and that plugins.toml enables the ATIF exporter with a writable output_directory.

Troubleshoot LLM Lifecycle

If agent/tool events exist but LLM spans are missing, the active Codex provider is not pointing at the gateway for the active Codex process. If only GUI sessions are missing spans, confirm the GUI is using local provider configuration rather than a remote execution path.

If LLM spans exist but attach to the session instead of a subagent, pass x-nemo-relay-subagent-id on gateway requests or include shared conversation_id, generation_id, or request_id values in both hook payloads and provider requests.

Cold-Start Limitation

Codex can request /models during provider discovery, before it launches plugin MCP servers. Codex retries the pre-MCP request if it fails. The required MCP server still blocks the captured turn and /responses request until the shared Relay gateway is ready.