codex¶
codex (the OpenAI Codex CLI, @openai/codex) is a
terminal coding agent. Like opencode, Cline,
reasonix, and pi, it is pure path-discovery in wrapper mode — it
reads AGENTS.md and skills from fixed locations under its home dir ($CODEX_HOME, default
~/.codex) and needs no flags. So the project instructions stay in place and everything else
is a bind.
Wrapper resolution¶
| Source | Injected at |
|---|---|
project AGENTS.md |
(not injected — read natively, work-dir→root walk) |
project .agents/skills/ |
<root>/.codex/skills/ |
global ~/.config/agents/AGENTS.md (+ optional AGENTS.codex.md overlay) |
~/.codex/AGENTS.md |
global ~/.config/agents/skills/ |
~/.codex/skills/ |
- Project instructions — codex walks from the work dir up to the project root collecting
AGENTS.mdand folds a limited slice into the first turn, so the project-rootAGENTS.md— the agent-neutral source, already in place — is read natively. agedum injects nothing for it (and never could, since the rootAGENTS.mdis git-tracked). - Global instructions — codex reads a user-scope rules file from its home dir at boot
(
$CODEX_HOME/AGENTS.md). So the globalAGENTS.mdis bound to~/.codex/AGENTS.md— base merged with an optionalAGENTS.codex.mdoverlay. (codex prefers anAGENTS.override.mdif one exists; agedum writes the base name, so define overrides via the overlay, not a separate file.) The home dir honours$CODEX_HOME. - Skills — codex auto-discovers
SKILL.mdfolders from~/.codex/skills/(global) and./.codex/skills/(project). Each skill carriesname/descriptionfrontmatter — the neutral source shape — compiled with theSKILL.codex.mdoverlay and bound to those two dirs. extra_args: none — codex discovers everything from disk; the model and custom endpoint ride-m/-cflags built in provider mode.
agedum --wrapper codex -- codex # drive codex with the same source
agedum --wrapper codex --dry-run -- codex # show what would be injected
The sandbox makes ~/.codex writable automatically
codex persists session, log, and history state under ~/.codex/. Under a
write-confinement launch that dir is made writable as the
nearest existing ancestor of the injected ~/.codex/AGENTS.md (the same mechanism that
keeps ~/.claude writable for Claude) — so no sandbox.readWrite entry is needed, as long
as a global AGENTS.md (or the generated flash agent file) is injected.
Provider config¶
codex selects its model with -m and reads its API key from a conventional env var, so a
provider config maps to plain flags. codex has no --base-url flag, but its -c
key=value override (each value parsed as TOML, winning over ~/.codex/config.toml) can
define a custom provider inline — so a custom OpenAI-/Anthropic-compatible endpoint needs no
generated config file.
Built-in / OpenAI provider (no baseUrl)¶
config key |
Effect |
|---|---|
model |
-m <model> |
secretEnv value |
exported into the child (requiredEnv); codex's default openai provider reads OPENAI_API_KEY |
This launches codex -m gpt-5.5 against codex's built-in openai provider (or whatever
~/.codex/config.toml selects). No key flag is appended and no secret lands in argv —
agedum exports secretEnv under its own name via the normal
required-env path. The token is masked in --dry-run.
Custom endpoint (baseUrl)¶
A custom endpoint becomes a [model_providers.agedum] block passed via -c overrides and
selected with -c model_provider=agedum. model_provider cannot be one of codex's reserved
built-in ids (openai, ollama, lmstudio, amazon-bedrock), so agedum names it agedum.
config key |
Effect |
|---|---|
baseUrl |
the endpoint → model_providers.agedum.base_url; its presence switches on the custom provider |
model |
the upstream model id served there → -m <model> |
chatCompletions |
true ⇒ the endpoint speaks Chat Completions, so agedum interposes a translation proxy — see below |
wireApi |
codex's wire protocol for a Responses endpoint — emitted only when set (else codex's default applies). chat is rejected by recent codex (removed Feb 2026); use chatCompletions for a chat endpoint |
secretEnv |
referenced as model_providers.agedum.env_key (its name, never its value) |
codexConfig |
a table of arbitrary codex config keys → -c key=<toml> overrides (bool bare, int/float bare, everything else quoted). Carries the model metadata codex can't learn from a translated endpoint — chiefly model_context_window (the context-meter denominator, since the /models probe is answered empty) and model_supports_reasoning_summaries / model_reasoning_summary (so codex renders the reasoning the proxy surfaces — see chat proxy) |
codexModelCatalog |
a table ({contextWindow, displayName?, description?}) that makes codex fully recognise the custom model — see model catalog |
The key never lands on disk or in argv — codex reads it from the env var named by
env_key, which agedum exports from the env file at launch and
masks in --dry-run.
Chat-Completions endpoints (DeepSeek etc.) — the translation proxy¶
Recent codex (≥ Feb 2026) speaks only the OpenAI Responses API — wire_api = "chat" is
rejected outright. DeepSeek and most OpenAI-compatible providers serve only Chat
Completions, so codex cannot reach them directly. Setting chatCompletions: true makes
agedum interpose a localhost ResponsesToChatProxy for the session (the
FoldProxy sibling): codex's base_url is pointed at the proxy, which translates
codex's POST /responses into a /chat/completions call upstream and the streamed Chat
deltas back into the Responses SSE events codex consumes (text, function calls, and — for a
thinking model that streams delta.reasoning_content, e.g. Kimi K2.7 — a reasoning output
item streamed as reasoning-summary events, so codex renders the model's thinking).
{
"harness": "codex",
"slug": "codex-deepseek",
"secretEnv": "DEEPSEEK_API_KEY",
"config": {
"baseUrl": "https://api.deepseek.com/v1",
"chatCompletions": true,
"model": "deepseek-v4-pro"
}
}
→ launches codex -c model_provider="agedum" -c model_providers.agedum.base_url="<proxy>" -c
model_providers.agedum.env_key="DEEPSEEK_API_KEY" -m deepseek-v4-pro, where <proxy> is the
proxy's ephemeral 127.0.0.1 address (codex, inside the bwrap namespace, shares the host
loopback). No wire_api is sent — codex speaks Responses to the proxy. The proxy forwards
codex's Authorization: Bearer <key> header to the upstream unchanged.
Proxy scope + watchpoints
The proxy covers the codex loop verified live against DeepSeek — streamed text and
function-call (tool) turns, single and multi-turn, on both deepseek-v4-pro and
deepseek-v4-flash — and against Kimi K2.7 for reasoning display (the model's
reasoning_content rendered by codex as a thinking summary). Known limits: (1) the stateful
reasoning round-trip across tool turns is not replayed on input (inbound
reasoning items are dropped) — the response-side reasoning display above is separate and
works; revisit if a provider errors on missing reasoning_content; (2) codex's GET /models
metadata probe is answered with an empty list, so codex logs a benign "metadata not found"
warning and falls back to default metadata — set the real window (and reasoning-summary
support) explicitly with codexConfig (model_context_window,
model_supports_reasoning_summaries), which overrides the fallback; (3) codex rejects a skill
whose description exceeds 1024 chars and skips it — trim long agedum skill descriptions
to surface them under codex.
Model catalog (codexModelCatalog)¶
A custom model isn't in codex's bundled catalog, so codex logs "model metadata … not found",
falls back to default metadata, and — crucially — can't populate its context-usage meter (the
X/262k readout), because the meter reads the window from the catalog entry, not from the
model_context_window override. codexConfig alone therefore can't light up the meter.
codexModelCatalog closes that gap. When set, agedum runs the installed codex debug
models to read codex's own catalog, clones its first entry (so the required, version-specific
base_instructions and capability flags are correct), renames it to this config's model, applies
the table's overrides, writes a one-model catalog to ~/.codex/agedum-model-catalog.json, and
passes -c model_catalog_json=<that file>. codex then recognises the model — warning gone, context
meter live.
codexModelCatalog key |
Effect |
|---|---|
contextWindow |
context_window + max_context_window on the cloned entry — the meter denominator |
displayName |
the entry's display_name (defaults to model) |
description |
the entry's description (optional) |
{
"harness": "codex",
"slug": "codex-kimi-code",
"secretEnv": "KIMI_API_KEY",
"config": {
"baseUrl": "https://api.kimi.com/coding/v1",
"chatCompletions": true,
"model": "kimi-for-coding",
"codexModelCatalog": { "contextWindow": 262144, "displayName": "Kimi K2.7 (Code)" }
}
}
Graceful + robust
Cloning from the live codex debug models keeps base_instructions correct across codex
versions — nothing codex-internal is hardcoded. If codex can't be queried (absent, error,
unparseable output), agedum skips the catalog and codex uses its fallback metadata — the
launch never fails on it. The generated catalog carries only the one custom model, so pair it
with a launcher that pins -m <model> (the other bundled models aren't selectable under it).
Custom subagents (subagentModel, codexAgents, codexProjectAgents)¶
codex has no global "route all subagents to a fast model" knob (openai/codex#19482) and no
inline agent config. What it does have is custom subagents — standalone TOML files under
~/.codex/agents/ (personal) or .codex/agents/ (project) the primary delegates to on request.
agedum binds agent definitions into those dirs three ways:
subagentModel: <id>— sugar for one fastflashworker; agedum generates~/.codex/agents/flash.tomlrunning that model (the template below).codexAgents: <dir>— bind every*.tomlin<dir>(a path under the providers root) into~/.codex/agents/<name>.toml(personal scope — available in every project).codexProjectAgents: <dir>— same, into.codex/agents/<name>.toml(project scope — only in the launch's working tree; overrides a personal agent of the same name).
Source files are bound verbatim, except agedum injects a default sandbox_mode = "workspace-write"
when the source omits it (matching agedum's write-confined launch; an explicit sandbox_mode is
passed through). Two agents resolving to the same target — e.g. a flash.toml source colliding
with subagentModel's flash — is a hard error.
{
"harness": "codex",
"slug": "codex-deepseek-flash",
"secretEnv": "DEEPSEEK_API_KEY",
"config": {
"baseUrl": "https://api.deepseek.com/v1",
"model": "deepseek-v4-pro",
"codexAgents": "codex/agents"
}
}
→ the same deepseek-v4-pro launch as above, plus every *.toml in
<providers-root>/codex/agents/ bound under ~/.codex/agents/. The flash worker as a source
file:
name = "flash"
description = "Fast, low-cost worker for routine, well-scoped subtasks. Delegate mechanical work here to keep the primary model free for harder reasoning."
developer_instructions = """
You are a fast, cost-efficient worker. Carry out the delegated subtask directly and return a concise, complete result; prefer doing the work over deliberating.
"""
model = "deepseek-v4-flash"
# source omits sandbox_mode → agedum injects sandbox_mode = "workspace-write"
inert unless the primary delegates
codex spawns subagents only when explicitly asked ("spawn an agent to do X"), and it
has no global default that routes work to one automatically. So a bound agent gives the
primary a delegate to choose, but its model is not used unless you (or the primary's
plan) invoke it — unlike opencode's agentOptions or pi's subagents.agentOverrides, which
route built-in roles. This is the most faithful mapping codex's subagent model allows today;
revisit if codex#19482 lands a default-model knob. Every agent file is bound through the
normal launcher path, so a project-scope target is refused over a git-tracked path (it
must be untracked + gitignored).
--prompt/--run. codex takes a positional prompt to seed an interactive session; the
exec subcommand runs it once non-interactively and exits. So --prompt seeds the TUI
(codex "<text>") and --run runs once and exits (codex exec "<text>"). See the
prompt-seeding table.