feat: Implement Gym agent server for Codex CLI agent harness.#2095
Merged
Conversation
|
🌿 Preview your docs: https://nvidia-preview-ffrujeri-feat-codex-agent.docs.buildwithfern.com/nemo/gym Here are the markdown pages you've updated: |
Signed-off-by: Felipe Vieira Frujeri <ffrujeri@nvidia.com>
Signed-off-by: Felipe Vieira Frujeri <ffrujeri@nvidia.com>
ffrujeri
force-pushed
the
ffrujeri/feat/codex-agent
branch
from
July 21, 2026 21:32
1e1ec18 to
a6f53f3
Compare
Signed-off-by: Felipe Vieira Frujeri <ffrujeri@nvidia.com>
ffrujeri
marked this pull request as ready for review
July 22, 2026 16:48
ananthsub
previously approved these changes
Jul 23, 2026
ananthsub
left a comment
Contributor
There was a problem hiding this comment.
LGTM! a few small comments inline, but overall this is great!
…eam error, effective model name, npm prefix -g, doc multi-turn collapse). Signed-off-by: Felipe Vieira Frujeri <ffrujeri@nvidia.com>
ananthsub
approved these changes
Jul 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Integrates the OpenAI Codex CLI (
codex exec) as a built-in NeMo Gym agent harness, mirroring the Claude Code agent's architecture (#1336, #1603). Any Gym environment can now evaluate models inside the Codex harness — against OpenAI directly, or against any backend Gym serves via a model server. Implements #1389 (part of #1042).The central constraint: Codex removed
wire_api = "chat"(openai/codex#7782) and only speaks the Responses API over SSE. Where Claude Code needed a Messages ↔ Responses mapping (/v1/messages), Codex needs Gym's native format plus a streaming envelope and tolerance for its wire dialect. So this PR has two parts:Core seam — every Gym model server speaks the streaming Responses dialect (
nemo_gym/responses_streaming.py, dispatch route onSimpleResponsesAPIModel):/v1/responsesrequests are unchanged (strict validation, same 422 shape).stream: truerequests are sanitized onto the strict params model and the complete response is re-emitted as synthesized SSE (response.created→response.output_item.doneper item →response.completed— the minimal sequence streaming clients require, verified against codex-cli 0.144.4).client_metadata,prompt_cache_key), flattensnamespacetool specs to<ns>__<tool>functions (and splits the names back intonamespace+nameon the way out — the wire shape Codex's tool router requires), hoists function tools out of code-modeadditional_toolscarriers, hoists leading developer messages intoinstructions(strict chat backends admit exactly one system message at position 0), and prunes SDK-unknown nested fields via genericextra_forbiddenretry (e.g.reasoning.context).responses_converter.py:instructionswas silently dropped in Responses → ChatCompletions conversion — now mapped to a leading system message (also fixes the latent loss of Claude Code's system prompt on the vLLM path); wheninstructionsis present, the leading system/developer run is folded into that single system message.Agent —
responses_api_agents/codex_agent/:codex exec --json --ephemeral --skip-git-repo-checkper request with a fresh generatedCODEX_HOME(config.toml) and scratch--cdworkdir, both removed afterwards.env_keyauth — nocodex login/auth.json needed.model_serverref (with/ng-rollout/<id>capture correlation) or directopenai_base_url.approval_policy=never, analytics/history/update-checks off,web_searchdisabled,features.multi_agent/code_modeoff.sandbox_modeconfigurable (defaultdanger-full-access, mirroring the Claude agent's skip-permissions default — Gym envs provide their own isolation).model: null+model_serverwrites agym-policy-modelplaceholder to keep classic function tools (Gym servers substitute their configured model anyway)./seed_sessionmetadata becomes a per-rollout streamable-HTTPmcp_serversentry with the session token onhttp_headers.CODEX_HOME/skills/from the run-levelskills_ref(native discovery, no--bareanalog needed).--max-turnsequivalent): kill on timeout → empty response → verify still runs (reward 0). Codex runs in its own process group and timeout kills the whole group — the npm shim's vendored-binary child otherwise survives and holds the stdout pipe open past the kill.agent_message/reasoning→ messages with<think>prefixes;command_execution/mcp_tool_call/file_change/web_search/todo_list→function_call+function_call_outputpairs;turn.completed→ usage incl. cached/reasoning details). A run ending on buffered reasoning is surfaced as a think-tagged message (vLLM reasoning parsers sometimes route the final answer there).extra_configdeep-merge knob for arbitraryconfig.tomlcontent; per-rollout Gym MCP entries win name collisions.Also: composed showcase configs
reasoning_gym_codex_agent[_model_server].yaml, README(s), agent-skills docs updated with the Codex staging location.Test plan
Unit tests pass (full suite
1270 passed, 31 skipped; ruff + pre-commit clean). New coverage: ~45 agent tests (command/config/TOML generation,CODEX_HOMEstaging + cleanup, timeout/process-group kill, MCP config, rollout correlation, skills forwarding, event parsing) and ~28 core tests (sanitizer, SSE synthesis, dispatch route incl. non-streaming strict-validation regression, converterinstructionsmapping).Unit tests mock
asyncio.create_subprocess_exec; the real CLI and full stack were smoke-tested against codex-cli 0.144.4 with a vLLM-dialect model server (inference-apiQwen3.6-35B backend):stream: true+ codex bookkeeping fields against a livevllm_modelserver returnsresponse.created/output_item.done/response.completedSSE.codex execagainst the Gym model server — model calledexec_command, Codex executed/bin/bash -lc 'echo $((17*23))'→391, multi-turn replay (reasoning + function_call + output) validated,turn.completedusage reported.function_callrouted to the MCP tool with the per-rollout session token, result returned to the model.gym env start --resources-server reasoning_gym/reasoning_gym_codex_agent_model_server --model-type vllm_model+gym eval run --no-serve ... --limit 2→ 2/2 rollouts with rewards,turns_used, token usage, and aggregate metrics.Smoke test details (reproducible)
env.yaml(any OpenAI-compatible chat endpoint):gym env start \ --resources-server reasoning_gym/reasoning_gym_codex_agent_model_server \ --model-type vllm_model gym eval run --no-serve \ --agent reasoning_gym_codex_agent_model_server \ --input resources_servers/reasoning_gym/data/example.jsonl \ --output codex_rollout.jsonl --limit 1Standalone CLI seam check (no resources server) — take the model server URL from the
gym env startlog:Limitations
turn.completedusage reporting;turns_usedcounts assistant messages.execdropped with a warning), but the supported path is classic function tools via explicit model naming.reasoning_gym_codex_agent*configs areverified: falsepending reward-profile baselining.Closes #1389.