1. 为什么要在本地折腾 Codex 自定义 Agent
Codex 这个工具刚出来的时候,大部分人就是拿它当个命令行版的代码补全用——敲个codex进去,问两句,拿点代码片段走人。但真正把它用起来的人会发现,默认配置下的 Codex 其实是个“半成品”:模型走的是云端默认通道,Agent 行为完全由官方预设控制,你没法告诉它“我们这个项目用 pnpm 不用 npm”“提交信息必须走 Conventional Commits”“别碰 migrations 目录”。这些约束如果每次对话都靠嘴说,效率低不说,还容易漏。
所以本地自定义 Agent 和模型配置这件事,本质上解决的是三个问题:第一,让 Codex 知道你的项目规矩;第二,让 Codex 用你想用的模型通道;第三,当多个配置源打架时,你得清楚谁说了算。这三个问题分别对应三个核心概念——AGENTS.md、config.toml里的 model provider 配置、以及配置优先级规则。
我前后在三个不同规模的项目里配过 Codex,从个人小工具到十几人的协作仓库都试过。踩过的坑包括但不限于:AGENTS.md写了但没生效、TOML 里 provider 名字写错导致请求直接 404、项目级配置被用户级配置悄悄覆盖、以及最坑的——多个AGENTS.md嵌套时到底读哪个。这篇文章就把这些东西一次性讲透,从目录结构到字段含义,从优先级规则到排查手法,尽量做到你照着抄就能跑起来。
适合谁看?如果你已经在用 Codex CLI,但还停留在“默认配置能用就行”的阶段,这篇能帮你把工具真正变成团队资产;如果你刚开始接触 Codex,建议先把基础安装跑通再回来看配置部分,不然容易一头雾水。下面所有内容都基于本地配置文件的实际行为,不涉及任何网络通道的特殊操作,纯粹讲配置本身。
2. Codex 本地配置的整体设计与目录结构
2.1 三层配置模型:用户级、项目级、会话级
Codex 的配置体系是典型的三层结构,理解这三层是后面所有优先级讨论的基础。
用户级配置放在你的 home 目录下,路径通常是~/.codex/config.toml。这一层的作用是定义“我这个人在所有项目里都想要的默认行为”——比如我默认用哪个模型、默认的推理强度、默认的审批策略。它跟具体项目无关,换仓库也生效。
项目级配置放在仓库根目录,核心是两个东西:AGENTS.md和可选的.codex/config.toml。AGENTS.md是给 Agent 看的“项目说明书”,.codex/config.toml是项目级的参数覆盖。这一层的作用是“这个项目特有的规矩”,比如这个仓库用 Python 3.12、测试命令是pytest -x、禁止修改vendor/目录。
会话级配置是你在单次运行 Codex 时通过命令行参数临时指定的,比如codex --model gpt-5-codex或者-c key=value这种覆盖。它优先级最高,但只对当前这次会话生效,退出就没了。
这三层的设计逻辑很清晰:越靠近具体上下文的配置,优先级越高。用户级是“我的习惯”,项目级是“项目的规矩”,会话级是“这次特殊”。搞混这三层,就会出现“我明明在项目里写了配置怎么不生效”这种问题——大概率是被用户级覆盖了,或者你写的位置根本不在 Codex 的搜索路径里。
2.2 为什么用 TOML 而不是 JSON 或 YAML
Codex 选 TOML 作为主配置格式,这个选择挺讲究的。JSON 不支持注释,配置里想写句“这行是给 CI 用的”都没地方放;YAML 缩进敏感,一个 tab 和空格的混用就能让你排查半小时。TOML 刚好卡在中间:有明确的 section 语法[model_providers.xxx],支持注释,对多层级配置的表达比 JSON 直观得多。
举个实际例子,你要配一个自定义 provider,TOML 里是这样:
[model_providers.local_gateway] name = "Local Gateway" base_url = "http://127.0.0.1:8080/v1" env_key = "LOCAL_GATEWAY_KEY" wire_api = "responses"同样的东西用 JSON 写,你得嵌套三层对象,还没法注释说明wire_api为什么选responses。用 YAML 写,env_key那行的缩进错一格就静默失效。所以 TOML 在这个场景下确实是最优解,尤其是当你的配置需要多人协作维护时,可读性和容错性都更好。
2.3 AGENTS.md 的定位:给 Agent 的“项目 README”
很多人第一次看到AGENTS.md会以为是给人类看的文档,其实它是专门给 Agent 读的指令文件。它的内容和普通 README 有本质区别:README 解释“这个项目是什么”,AGENTS.md规定“你在这个项目里该怎么干活”。
一个典型的AGENTS.md会包含这些内容:项目技术栈和版本约束、构建和测试命令、代码风格要求、禁止操作清单、以及一些项目特有的约定(比如“所有 API 变更必须同步更新docs/api.md”)。它的写法直接影响 Agent 的行为质量——写得越具体,Agent 越少犯低级错误。
我见过最常见的错误是把AGENTS.md写成营销文案,什么“本项目致力于打造业界领先的解决方案”,这种内容对 Agent 零价值。有效的写法是命令式的、可验证的,比如“运行测试用pnpm test -- --runInBand,不要用npm test”,Agent 一看就知道该执行什么。
3. TOML 配置核心字段与模型接入实操
3.1 config.toml 的完整字段拆解
先给一份我实际在用的用户级config.toml骨架,然后逐字段讲:
model = "gpt-5-codex" model_provider = "local_gateway" approval_policy = "on-request" sandbox_mode = "workspace-write" model_reasoning_effort = "medium" [model_providers.local_gateway] name = "Local Gateway" base_url = "http://127.0.0.1:8080/v1" env_key = "LOCAL_GATEWAY_KEY" wire_api = "responses" request_max_retries = 3 stream_max_retries = 2model字段指定默认模型名,这个字符串必须和 provider 那边认识的模型标识一致,写错了请求会直接失败。model_provider指向下面[model_providers.xxx]里的某个 section 名,注意这里填的是 section 名(local_gateway),不是name字段的值。
approval_policy控制 Agent 执行命令前是否需要你确认,常见值有untrusted、on-failure、on-request、never。sandbox_mode控制文件系统权限,workspace-write表示只能写工作区,read-only更严格。这两个字段直接决定 Agent 的“自由度”,配错了要么天天弹确认烦死你,要么权限过大出事故。
model_reasoning_effort是推理强度,low/medium/high三档。这个字段对成本和延迟影响很大,日常改代码用medium够用,复杂重构再上high。
3.2 自定义 model provider 的接入步骤
接入一个自定义 provider 分四步,我按顺序说,每步都标注容易出错的地方。
第一步,确定 base_url 和 wire_api。base_url是服务端点,通常以/v1结尾。wire_api有两个常见值:chat对应传统的 chat completions 接口,responses对应较新的 responses 接口。这两个不能混——如果你的服务端只实现了 chat completions,你写responses就会收到 404 或者格式错误。判断方法很简单:看服务端文档里暴露的路径是/v1/chat/completions还是/v1/responses。
第二步,配置密钥的环境变量。env_key填的是环境变量名,不是密钥本身。比如你写env_key = "LOCAL_GATEWAY_KEY",那 Codex 启动时会去读LOCAL_GATEWAY_KEY这个环境变量的值作为鉴权 token。这样做的好处是密钥不落盘到配置文件里,避免误提交。设置方法:
export LOCAL_GATEWAY_KEY="your-key-here"Windows 上用setx LOCAL_GATEWAY_KEY "your-key-here",注意 setx 设置后要新开终端才生效。
第三步,在 config.toml 里声明 provider。就是上面那段[model_providers.local_gateway]。section 名随便起,但要和model_provider字段对应上。
第四步,验证。跑一个最简单的请求,看是否通。如果报鉴权错误,先echo $LOCAL_GATEWAY_KEY确认环境变量读到了;如果报 404,检查base_url和wire_api的组合;如果报连接超时,确认服务端在监听。
3.3 参数计算:重试次数与超时怎么定
request_max_retries和stream_max_retries这两个参数很多人直接抄默认值,其实值得算一下。重试的本质是用延迟换成功率,但重试太多会把一次失败放大成多次无效请求。
我的经验公式是:重试次数 = 可接受的最大延迟 / 单次请求平均耗时 - 1。假设你的服务端单次请求平均 2 秒,你能接受用户最多等 10 秒,那重试次数就是10/2 - 1 = 4。但如果你用的是流式输出,stream_max_retries要单独设,因为流中断后重试的成本更高,一般设 1 到 2 就够。
还有一个隐藏坑:重试和幂等性。如果你的请求不是幂等的(比如 Agent 触发了写操作),重试可能导致重复执行。所以request_max_retries在涉及写操作的场景下要谨慎,宁可设小一点让失败快速暴露。
提示:改完 config.toml 后不需要重启任何服务,Codex 每次启动会重新读取。但环境变量是进程级的,改了要新开终端。
4. AGENTS.md 的写法与多文件优先级
4.1 一份能真正约束 Agent 的 AGENTS.md 模板
直接上模板,这是我目前在用的结构,按这个写基本不会漏:
# AGENTS.md ## 项目概览 - 技术栈:Node.js 20 + TypeScript 5.4 + pnpm - 包管理器:pnpm(禁止使用 npm 或 yarn) ## 常用命令 - 安装依赖:pnpm install - 运行测试:pnpm test - 类型检查:pnpm typecheck - 构建:pnpm build ## 代码规范 - 所有导出函数必须有 JSDoc 注释 - 禁止使用 any,必要时用 unknown 加类型守卫 - 提交信息遵循 Conventional Commits ## 禁止操作 - 不要修改 migrations/ 目录下的历史文件 - 不要直接编辑 dist/ 产物 - 不要提交 .env 文件 ## 项目约定 - API 变更必须同步更新 docs/api.md - 新增依赖前先在 PR 描述里说明理由这份模板的关键在于可执行性。每一条都是 Agent 能直接判断对错的,而不是“请保持代码优雅”这种没法验证的废话。特别是“禁止操作”那一节,能挡掉大量 Agent 自作主张的修改。
4.2 多级 AGENTS.md 的搜索与合并规则
这是最容易踩坑的部分。Codex 支持多级AGENTS.md,搜索顺序大致是:从当前工作目录向上逐级查找,直到仓库根目录,同时还会读用户级目录下的全局AGENTS.md。
合并规则是就近覆盖:越靠近当前工作目录的AGENTS.md优先级越高,同名字段或同类指令以近的为准。举个例子,仓库根目录的AGENTS.md说“测试用pnpm test”,但packages/api/AGENTS.md说“测试用pnpm test:api”,那你在packages/api/目录下跑 Codex 时,生效的是后者。
这个机制的好处是支持 monorepo 里不同子包有不同规矩。坏处是当你不确定当前目录在哪一层时,容易搞不清哪份配置生效。排查方法很简单:在目标目录下跑 Codex,直接问它“你现在读到了哪些 AGENTS.md 文件”,它会告诉你实际加载的路径。
注意:全局
AGENTS.md和项目级AGENTS.md的合并是叠加而非替换,项目级不会清空全局的指令,而是追加。所以全局文件里别写太具体的项目规则,否则会污染所有项目。
4.3 指令冲突时的处理策略
当两份AGENTS.md给出矛盾指令时,Codex 的行为是“就近优先”,但实际表现有时会含糊。我的做法是主动消除冲突,而不是依赖优先级去猜。
具体做法:在子目录的AGENTS.md里显式声明“本目录覆盖根目录的以下规则”,把冲突项列清楚。比如根目录说“用 npm”,子目录说“本目录用 pnpm,覆盖根目录的包管理器规则”。这样即使优先级机制有边界情况,Agent 也能从文字上明确知道该听谁的。
另一个技巧是把通用规则放全局,把项目规则放根目录,把子包特例放子目录,形成清晰的层级。避免在根目录写“除了 packages/api 之外都用 X”这种反向描述,Agent 处理否定条件的准确率明显低于正向描述。
5. 配置优先级规则与冲突排查
5.1 优先级从高到低的完整链条
把前面散落的信息整合成一条完整链条,从高到低:
- 命令行参数(
--model、-c key=value)——最高,只影响当前会话 - 会话级环境变量——比如临时 export 的 provider key
- 项目级
.codex/config.toml——仓库内的参数覆盖 - 项目级
AGENTS.md——就近的优先于上层的 - 用户级
~/.codex/config.toml——个人默认 - 用户级全局
AGENTS.md——个人全局指令 - 内置默认值——最低
记住一个原则:参数类配置(model、provider)走 TOML 链,指令类配置(行为约束)走 AGENTS.md 链,两条链独立生效,互不覆盖。很多人以为项目级 TOML 能覆盖用户级 AGENTS.md,这是错的,它们管的是不同维度。
5.2 用 -c 参数做临时覆盖的正确姿势
-c是排查配置问题的利器。语法是-c key=value,key 支持点号路径。比如临时换模型:
codex -c model="gpt-5" -c model_reasoning_effort="high"临时换 provider:
codex -c model_provider="another_gateway"这个用法的价值在于隔离变量。当你怀疑是配置问题而不是服务问题时,用-c显式指定一遍,如果通了,说明是配置文件里的值有问题;如果还不通,说明问题在服务端或网络层。这比反复改配置文件再重启高效得多。
提示:
-c的值如果是字符串,某些 shell 下需要引号包裹,尤其是含空格或特殊字符时。稳妥起见统一加引号。
5.3 配置不生效的五种典型原因
按我踩坑的频率排序:
| 现象 | 最可能原因 | 排查方法 |
|---|---|---|
| 改了 TOML 没反应 | 改的不是生效的那份 | 确认路径,用户级 vs 项目级 |
| provider 报 404 | base_url 或 wire_api 不匹配 | 对照服务端实际路径 |
| 鉴权失败 | env_key 对应的环境变量没设 | echo $VAR_NAME确认 |
| AGENTS.md 没约束力 | 文件位置不在搜索路径 | 问 Codex 读到了哪些文件 |
| 项目配置被覆盖 | 用户级有同名配置 | 检查~/.codex/config.toml |
这张表基本覆盖了 90% 的配置问题。我的习惯是每次改完配置先跑一个最小验证,别等正式用的时候才发现没生效。
6. 实操全流程:从零配一套可用的本地 Agent
6.1 环境准备与安装确认
先把基础环境确认一遍。Codex CLI 装好后,跑codex --version确认版本。然后确认配置目录存在:
ls -la ~/.codex/如果没有这个目录,手动建一个。接着确认你的 provider 服务端在跑,用 curl 探一下:
curl -s http://127.0.0.1:8080/v1/models -H "Authorization: Bearer $LOCAL_GATEWAY_KEY"能返回模型列表说明服务端和鉴权都正常。这一步别跳过,很多“配置问题”其实是服务端根本没起来。
6.2 写用户级 config.toml
按第 3 节的骨架写,重点确认三个字段:model、model_provider、以及对应 provider section 里的base_url和wire_api。写完存盘,跑一次codex看能否正常对话。这一步通了再往下走,别一次配太多。
6.3 写项目级 AGENTS.md
在仓库根目录建AGENTS.md,按 4.1 的模板填。填完在仓库里跑 Codex,问它“这个项目的测试命令是什么”,看它答得对不对。答错说明文件没被读到,检查文件名大小写和位置。
6.4 验证优先级
故意制造一个冲突来验证优先级:用户级 TOML 里设model = "A",项目级.codex/config.toml里设model = "B",然后跑 Codex 问它当前用什么模型。如果答 B,说明项目级覆盖生效;如果答 A,说明项目级配置没被读到,检查.codex/目录位置。
这个验证做完,你对整套优先级机制就有实感了,后面遇到问题能快速定位。
7. 常见问题与排查技巧实录
7.1 请求失败类问题的排查顺序
遇到请求失败,按这个顺序排查,从外到内:
- 服务端是否在监听(
curl探活) - 鉴权是否通过(检查环境变量)
base_url和wire_api是否匹配- 模型名是否被服务端认识
- 重试和超时参数是否合理
这个顺序的逻辑是“先确认链路通,再确认参数对”。很多人一上来就改配置,结果发现是服务端没起来,白折腾。
7.2 AGENTS.md 不生效的定位方法
最直接的方法是在 Codex 会话里问它:“列出你当前加载的所有 AGENTS.md 文件路径。”它会返回实际读到的文件列表。如果列表里没有你写的那份,就是位置问题;如果有但指令没执行,就是写法问题——大概率是描述太模糊,Agent 没法判断。
另一个技巧是把关键约束写成祈使句加具体命令,比如“运行测试必须用pnpm test”,比“测试请使用 pnpm”约束力强得多。
7.3 多项目切换时的配置隔离
如果你同时维护多个项目,用户级配置要尽量“中性”,别塞太多项目特定内容。项目特定的东西全部下沉到项目级AGENTS.md和.codex/config.toml。这样切换项目时不会互相污染。
我的做法是用户级只保留模型和 provider 这类通用参数,行为约束一律放项目级。这样即使我在十个仓库之间跳,每个仓库的规矩都是自洽的。
7.4 一份速查表
| 问题 | 快速检查 |
|---|---|
| 模型不对 | -c model="xxx"临时覆盖验证 |
| provider 不通 | curl 探活 + 检查 env_key |
| 指令不生效 | 问 Codex 读了哪些 AGENTS.md |
| 配置被覆盖 | 对比用户级和项目级同名项 |
| 重试太频繁 | 调小 request_max_retries |
这张表贴在手边,大部分问题五分钟内能定位。
8. 一些配置之外的实操心得
配了这么多套环境,有几个体会是文档里不会写的。第一,配置要版本化。项目级的AGENTS.md和.codex/config.toml一定要提交到仓库,这样团队里每个人拉下来就是一致的,避免“我这能跑你那不能跑”。用户级的配置则不要提交,那是个人习惯。
第二,别追求一次配到位。我见过有人花一下午写了个几百行的AGENTS.md,结果 Agent 反而因为指令太多而抓不住重点。正确做法是先配最小可用集,用一段时间发现哪类错误反复出现,再针对性加约束。配置是迭代出来的,不是设计出来的。
第三,优先级机制要主动验证。别假设它按你想的方式工作,用 6.4 那个冲突测试法定期验证一遍。尤其是升级 Codex 版本后,优先级规则可能有微调,验证一次心里有底。
最后分享一个小技巧:把常用的-c覆盖组合写成 shell alias,比如alias codex-fast='codex -c model_reasoning_effort="low"',日常快速改代码用这个,复杂任务再用默认配置。这样既省 token 又省时间,实测下来很稳。