Codex (codex)
Output
AGENTS.md # canonical entry-point pointer body (written by sync)
.codex/agents/<name>.toml # one TOML per agent (Codex CLI's native path)
.agents/skills/<name>/SKILL.md # one folder per skill (the path Codex CLI scans)
.agents/skills/<name>/agents/openai.yaml # optional, when x-codex provides UI/policy/deps
.codex/config.toml # when settings or MCP entries exist
.codex/hooks.json # when hook entries exist
.codex/rules/default.rules # opt-in, from outputs.codex.exec-policies
.codex/prompts/<name>.md # opt-in via outputs.codex.commands-dir (deprecated by Codex)
Rules: unscoped rules inline into the root
AGENTS.md. A rule withscope: services/paymentsinstead reachesservices/payments/AGENTS.md. Aglobsfield alone does not create a directory scope. Remove legacyoutputs.codex.rules-fileoverrides before using scoped rules. See scoped context for selector and runtime limits.Agents: Codex custom agents use one TOML file per agent with
name,description, anddeveloper_instructions, plus optional session config such asmodel,model_reasoning_effort,sandbox_mode, andnickname_candidates, set underx-codex. A generictools: [Read, Bash, ...]list is not emitted because Codex definestoolsas a config table, not a tool allowlist. Sync reports the dropped field. Usex-codex.toolsfor native settings such asweb_searchandview_image. Use a per-targetmodelmap when another CLI's model name, such assonnet, must not reach Codex.Skills: Codex skills layout, one folder per skill under
.agents/skills/(the directory Codex scans from the cwd up to the repo root) with a requiredSKILL.md(frontmattername+description, plus body). A scoped source skill moves the native directory under that scope: for exampleskills/services/api/review/SKILL.mdbecomesservices/api/.agents/skills/review/SKILL.md.import codexrestores the scope and bundled assets.When the spec carries
x-codex.interface,x-codex.policy, orx-codex.dependencies, anagents/openai.yamlis also written for UI customization and policy declarations. Amp reads the root path, so identical emitted bytes dedupe and enabling both targets is safe. A stale managed tree at the pre-v0.43.codex/skills/default is swept on sync.Hooks: land in
.codex/hooks.json(override viaoutputs.codex.hooks-file). Hooks route byeventfrontmatter (SessionStart,SubagentStart,UserPromptSubmit,PreToolUse,PermissionRequest,PostToolUse,PreCompact,PostCompact,Stop,SubagentStop,SessionEnd,Interrupt) into per-event arrays withmatcherandcommand.Optional
timeout,statusMessage,commandWindows,additionalContextLimit, andasyncpass through and surviveimport codex.asyncruns the hook in the background instead of blocking the session. An explicitadditionalContextLimit: 0is preserved, since Codex uses it to pass complete hook context.import codexalso reads hooks straight out of a hand-authored.codex/config.toml, in the vendor's own documented inline shape:[[hooks.<event>]]carriesmatcheralone, and a nested[[hooks.<event>.hooks]]array carries the command fields (learn.chatgpt.com/docs/hooks, "Equivalent inline TOML in config.toml"). A flat[[hooks.<event>]]table withmatcherandcommandon the same table also still decodes, for configs written before this shape was added. The vendor has never documented that flat form (#669).A hook spec with
type: mcp_toolcalls a tool on an already-connected MCP server instead of running a shell command. learn.chatgpt.com/docs/hooks says it "sends structured arguments directly to the tool and uses the same trust review and output contract as a command hook."serverandtoolare required;input(an argument-template object) is optional, and it sharestimeout/statusMessagewith the command shape. It emits as{type, server, tool, input, timeout, statusMessage}in the samehooks.json, andimport codexreads it back from there (#693).Exec policies: opt-in. Set
outputs.codex.exec-policies(inline list) oroutputs.codex.exec-policies-file(external YAML) to write.codex/rules/default.rulesin Codex's Starlarkprefix_rule(...)form. Unset writes nothing.MCP: lands in
.codex/config.toml. Servers emit as[mcp_servers.<name>]. Stdio servers usecommand/args/env/cwdplus the mixedenv_varsarray, whose entries are names or{name, source}objects withsourceset tolocalorremote. A spec'sdisabled: truewritesenabled = false. HTTP/SSE servers useurl/bearer_token_env_var/http_headers/env_http_headers/auth(oauthorchatgpt)/http_headers_helper(a local command printing header JSON, documented for a locally connected HTTP server only).A server name that TOML cannot carry as a bare key is quoted. Codex CLI 0.152.0 widened the accepted server-name charset to include
:,@,/, and.for package-style names such asnpm:@modelcontextprotocol/server-sequential.thinking(openai/codex#41700), and this adapter quotes those too so one such name does not invalidate the whole file (#706). Import stores a slash-bearing name in one percent-encoded YAML filename and keeps the exact name inside the spec, so import followed by sync is lossless (#711).These fields carry no transport restriction and emit on either shape:
enabled_tools/disabled_tools(learn.chatgpt.com/docs/config-file/config-reference, #661), plusrequired,startup_timeout_sec,startup_timeout_ms(the vendor's own millisecond alias for the same startup timeout; set one or the other, #735),tool_timeout_sec,default_tools_approval_mode, andexperimental_environment.disabled_toolsapplies afterenabled_tools.Field Default Meaning requiredfalseFail startup or resume if this enabled server cannot initialize. startup_timeout_sec/startup_timeout_ms10/10000Server startup timeout. tool_timeout_sec60Per-tool execution timeout. default_tools_approval_modeunset auto,prompt,writes, orapprove, unless a per-tool override exists.experimental_environmentunset localorremote.remotestarts a stdio server through a remote executor; the vendor documents HTTP remote placement as not yet implemented.scopes,oauth_resource(the RFC 8707 resource parameter), and an[mcp_servers.<id>.oauth]sub-table,{client_id, callback_url, callback_port}, authenticate to an MCP HTTP server and land on the http/sse shape alongsideauth(#693).A
toolsmap emits the vendor's per-tool sub-tables,[mcp_servers.<name>.tools.<tool>], whose keys pass through verbatim. The vendor documents two today:output_token_limit("Token budget for one MCP tool's output, before the standard 20% serialization allowance", shipped in Codex v0.153.0) and a per-tool approval override; the table gains entries without warning, so nothing is mapped key by key. A tool name that TOML cannot carry as a bare key is quoted.These sub-tables are written last in the server's table, because a TOML sub-table header ends its parent: a server-level scalar emitted after one would be read as a key of the tool. The whole block was dropped in silence before #678. All of these fields survive
import codex. The project-tier config.toml is managed (overwritten each sync); put unmanaged Codex config in~/.codex/config.toml.Settings: the last portable
modelvalue writes to.codex/config.toml.outputs.codex.config.modelwins over the portable value. A captured.agnostic-ai/overlays/codex.config.tomlremains the highest-precedence layer for backward compatibility, so an importedmodelthere wins over both and is never duplicated.Commands: not emitted by default. Codex loads custom prompts from
~/.codex/prompts/only (no project-level discovery) and deprecates them in favor of skills, so a project-tier prompts tree would never be read;syncprints a coverage note instead and sweeps a stale managed.codex/prompts/tree. Setoutputs.codex.commands-dirto emit the legacy layout anyway.
Config keys
| Key | Default | Notes |
|---|---|---|
outputs.codex.agents-dir | .codex/agents | override to .agents/agents for the community shared layout |
outputs.codex.skills-dir | .agents/skills | the path Codex scans |
outputs.codex.shared-subagents | true | emits the per-skill tree at skills-dir; set false to skip codex skill emission |
outputs.codex.commands-dir | unset | set to e.g. .codex/prompts to emit the deprecated project prompts layout |
outputs.codex.mcp-file | .codex/config.toml | |
outputs.codex.hooks-file | .codex/hooks.json | |
outputs.codex.rules-file | unset | writes legacy concatenated rules and skips the pointer-body write |
outputs.codex.exec-policies / outputs.codex.exec-policies-file | unset | write .codex/rules/default.rules |
Codex config
The outputs.codex.config block declares first-class .codex/config.toml global keys, written into the project-tier config on each sync. A portable Settings spec can set the same project model; outputs.codex.config.model wins when both exist. Keys not listed here belong in the user-level ~/.codex/config.toml, which Codex merges last.
outputs:
codex:
config:
model: o4-mini
sandbox: workspace
approval-policy: on-failure
model-reasoning-effort: high
model-reasoning-summary: auto
history-persistence: project
notify: ["python3", "/etc/codex/notify.py"]
profiles:
work:
model: o4-mini
sandbox: workspace-write
approval-policy: on-failure
oss:
model: gpt-oss-20b
model-provider: ollama
| Field | Type | Notes |
|---|---|---|
model | string | Model identifier Codex uses for this project. |
sandbox | string | Sandbox profile (e.g. workspace). |
approval-policy | string | When Codex asks for approval: never, on-failure, or always. |
model-reasoning-effort | string | Reasoning effort for o-series models: low, medium, high. |
model-reasoning-summary | string | Reasoning summary verbosity: auto, concise, detailed. |
history-persistence | string | Conversation history scope: project, global, or none. |
notify | string array | External program Codex invokes on session events. First element is the executable; rest are arguments. |
profiles | map | Named [profiles.<name>] blocks. Each entry overrides top-level fields when Codex runs with --profile <name>. Supported keys: model, sandbox, approval-policy, model-reasoning-effort, model-reasoning-summary, model-provider. |
model-providers | map | Named [model_providers.<id>] blocks declaring backends Codex can call. Supported keys: name, base-url, wire-api, api-key-env, env-key. Reference an id from profiles.<name>.model-provider. |
Codex exec-policies
outputs.codex.exec-policies (list) or outputs.codex.exec-policies-file (path to a YAML list) declares Codex CLI's Starlark exec-policy DSL, rendered into .codex/rules/default.rules on sync. Each entry allow- or forbid-lists a shell command prefix.
outputs:
codex:
exec-policies:
- pattern: ["composer", "test"]
decision: allow # allow | forbidden | prompt
justification: Composer scripts are project entrypoints.
match: ["composer test", "composer test -- --filter Foo"]
- pattern: ["rm", "-rf", "/"]
decision: forbidden
justification: Never remove the filesystem root.
| Field | Required | Notes |
|---|---|---|
pattern | yes | Shell command prefix tokens (["composer", "test"]). Becomes the prefix_rule(pattern = [...]) argument. |
decision | yes | One of allow, forbidden, prompt. |
justification | no | Free-form comment emitted above the rule as a # line. |
match | no | Example matches rendered as commented # match: ... lines below the rule. Documentation only; Codex CLI ignores them. |
For many policies, keep them in a separate YAML file and point exec-policies-file: ./.agnostic-ai/codex.exec-policies.yaml. Inline entries render first, then file entries. Order matters: Codex evaluates rules top-down.
agnostic-ai import codex against a project that ships .codex/rules/default.rules captures every prefix_rule(...) call into .agnostic-ai/overlays/codex.exec-policies.yaml. The codex emitter auto-loads that overlay when no inline list and no explicit exec-policies-file is set, so the round-trip is byte-content-preserving without extra config.
The file is written only when at least one policy is declared. Otherwise nothing under .codex/rules/ is created.
The codex emitter also reads .agnostic-ai/overlays/codex.config.toml (captured by agnostic-ai import codex) and prepends its body before the spec-derived [mcp_servers.*] sections. The overlay carries every other .codex/config.toml key the user has configured (model, sandbox, approval_policy, notify, [history], [profiles.*], [model_providers.*], ...) so wiping .codex/ between import and sync no longer drops them. For model, precedence from low to high is portable Settings spec, outputs.codex.config.model, captured overlay. The overlay also wins any other conflict with outputs.codex.config.*; the lower value is dropped to keep the TOML valid.
Import
agnostic-ai import codex walks the project for AGENTS.md at any depth and reads the rest of the Codex tree:
| Source | Becomes |
|---|---|
AGENTS.md (split on ## headings) | <rules>/<slug>.md per section |
AGENTS.md (no headings) | single <rules>/<projectname>.md |
<dir>/AGENTS.md (nested) | <rules>/<slug>.md with inferred globs: <dir>/** |
## Conventions / ## Agents / ## Skills wrapper sections | unwrapped: their ### children become the rules |
Single-line italic (_text_) immediately under a rule heading | extracted into the rule's description (and removed from the body) |
.codex/agents/*.toml and .agents/agents/*.toml | <agents>/<name>.md |
.agents/skills/<name>/SKILL.md (+ agents/openai.yaml, asset folders) | <skills>/<name>/SKILL.md (+ nested assets, exec bits preserved) |
.codex/config.toml [[hooks.<event>]] | <hooks>/<event>-<hash8>.yaml (one spec per entry) |
.codex/config.toml [mcp_servers.<name>] | <mcps>/<name>.yaml |
.codex/config.toml remaining keys (model, sandbox, approval_policy, notify, [history], [profiles.*], [model_providers.*], …) | .agnostic-ai/overlays/codex.config.toml (hooks + mcp_servers stripped) |
.codex/prompts/*.md | <commands>/<name>.md (byte-identical copy, so user-authored prompts round-trip) |
Slug collisions across files are deduplicated (style.md, style-2.md). The walk skips hidden directories, the configured source directories, node_modules/, and vendor/.
sync -t codex prepends the overlay before the spec-derived sections, so every captured key survives a .codex/ wipe. When the overlay and outputs.codex.config.* declare the same key, the overlay wins and the first-class key is dropped to avoid a TOML duplicate-key error. Keys set in only one place pass through unchanged. The exec-policies overlay is captured the same way.
Verify
- Install:
npm install -g @openai/codex(quickstart);codex --versionto confirm PATH. - Check the tree:
agnostic-ai sync -t codex, thenls .codex/agents/ .agents/skills/,test -f .codex/config.toml && head -1 .codex/config.toml,test -f .codex/hooks.json && jq '.hooks | keys' .codex/hooks.json. First line of config.toml must be the# Generated by agnostic-aiprovenance comment. - Validate syntax:
toml-test .codex/config.tomlandjq empty .codex/hooks.jsonshould both exit0. codex run "list one rule from this project". Codex picks upAGENTS.md, the agents, and skill folders. Look forloaded N agents/loaded N skills.- Trigger a hook by firing the targeted
event(e.g. anEditfor aPostToolUsehook); thecommandappears in the hook log. codex mcp listshows every[mcp_servers.<name>]. Disabled servers appear with the disabled flag.
The audit issue #329 tracks this smoke checklist; close its "Real CLI smoke" box only after every step passes against the live Codex CLI build in the linked PR.