Codex 的 TOML 转换:为什么正文要走 basic string

2026-08-09

agency-agents 这个仓库本身没什么玄机:它是 255 个带 YAML frontmatter 的 agent markdown,分在 17 个 division 目录里,另外配了一套脚本,把这批 markdown 渲染成 16 种 AI 编码工具各自认识的格式,再拷进对应配置目录。

多数工具的目标格式还是 markdown,改改 frontmatter 就完事。Codex 是少数几个例外之一:它要的是 TOML。而 TOML 是结构化配置格式,不像 markdown 那样「往下贴就行」,所以 scripts/convert.sh 在这里做了一个专门的处理——把整段正文塞进一个 TOML basic string。这篇就沿着这条链路走一遍。

先分清链路上的两个脚本

install.sh 头部注释写得很清楚:它从 integrations/ 读取已经转换好的文件,拷到每个工具对应的配置目录;如果 integrations/ 缺失或过期,先跑 scripts/convert.sh。默认情况下 install.sh 会在集成文件缺失时自动调 convert,可以用 --no-convert 关掉。

所以完整链路是这样的:

源 agent markdown  →  convert.sh 渲染  →  integrations/  →  install.sh 拷贝到目标目录

integrations/产物目录,不是源目录divisions.json_note 里把它和 strategy/examples/scripts/ 一起排除在 division 之外。第一次读这个仓库的人很容易在 integrations/ 里改文件,改完下一次 convert 就被覆盖了——要改得回 division 目录里改源 markdown。

Codex 这一路的转换规则

tools.json 里 Codex 那一行是这样的:kebab 名 codex,label Codex,format codex-toml,installKind per-agent,用户级目标路径模板 .codex/agents/{slug}.tomlinstall.sh 的注释里写的落点是 ~/.codex/agents/——注意是家目录,不是当前目录。

convert.sh 为每个 agent 写出一个 TOML,只写最小必需字段:

name = "..."
description = "..."
developer_instructions = "<整个正文>"

源码注释给了理由:用 TOML basic string,是为了把源正文里的控制字符安全编码,避免产出非法 TOML。

这句话值得展开一下,因为它解释了「为什么别的工具不用这么麻烦」。Cursor 那一路的 .mdc 产物是 frontmatter 后面直接跟正文,正文里有什么就贴什么,不需要任何转义;opencode 那一路是 markdown 加 YAML frontmatter,正文同样是原样贴。这两种产物里,正文都待在「文档正文区」,不属于任何字段的值。

Codex 的 TOML 不一样:整段正文是 developer_instructions 这个键的。TOML basic string 用双引号包裹,值里的双引号、反斜杠、以及控制字符都不能原样出现——一旦出现,产出的就是一个语法非法的 TOML 文件。而源 agent markdown 里塞的是整段第二人称提示词,按花名册导出的正文词数统计,短的不到 200 词、长的超过 4600 词,中位数在 1800 词上下;里面带表情符号、带代码块、带各种标点,谁也没法保证绝不出现需要转义的字符。所以转换层必须把这段正文整体过一遍编码,而不是寄希望于「源文件写得干净」。

顺带一提,仓库在另一个层面上也在防同一类问题:lint-agents.sh 的第 0 道检查直接把 CRLF 行尾判成 ERROR,源码注释解释的理由是行尾多一个 \r 会让后面的 frontmatter 检查报出「missing frontmatter ---」这种让人摸不着头脑的错。源头拦一道、转换层再兜一道,是这个仓库里比较一致的做法。

命令怎么写

先说最该先跑的那一条。--dry-run 只打印安装计划、不写任何东西:

./scripts/install.sh --tool codex --dry-run

--tool codex 是选择器,把范围收到 Codex 这一个工具;不加选择器的裸调用是把所有 team 装到所有检测到的工具,而且在 TTY 下默认会进交互向导。往 ~/.codex/agents/ 这种全局目录写文件之前,先看一眼计划是很便宜的保险。

范围还可以继续收窄。三个选择器可以自由组合:

# 只装 engineering 这个 division 到 Codex
./scripts/install.sh --tool codex --division engineering

# 只装点名的几个 agent
./scripts/install.sh --tool codex --agent <slug>,<slug>

# 从文件读 agent 列表,每行一个 slug 或名字,支持 # 注释
./scripts/install.sh --tool codex --agents-file <你的清单文>

installKindper-agent,意思是每个 agent 渲染出一个文件——全量装就是 255 份产物同时进一个目录。全局配置目录里堆这么多文件是有代价的(上下文占用、工具启动时的加载、命名冲突),具体影响我们没有测过,不给数字;但用 --division / --agent 先收窄,是成本更低的起手式。

几个行为开关按需加:

# 集成文件缺失时不自动跑 convert.sh
./scripts/install.sh --tool codex --no-convert

# 用符号链接代替拷贝,改动会自动传播
./scripts/install.sh --tool codex --link

# 覆盖安装目录(单一目标)
./scripts/install.sh --tool codex --path <你的目>

# 列出后退出
./scripts/install.sh --list tools

安装路径也可以走环境变量。install.sh 注释里列的可覆盖变量在硬编码默认值之前被检查,Codex 对应的是 CODEX_AGENTS_DIR。要把产物放到别处,改这个变量和用 --path 是两条不同的路子,别同时用两套。

平台方面,脚本注释写的支持范围是 Linux、macOS(需要 bash 3.2+)、Windows Git Bash / WSL。Windows 用户注意:这是 bash 脚本,PowerShell 和 cmd 里直接跑不起来,得在 Git Bash 或 WSL 里执行。

以上为按官方参数语义组合的示例,未逐项实测,以官方文档与 ./scripts/install.sh --help 的实际输出为准。

产出物长什么样

有依据可以说的就这几条:产物是 TOML 文件,一个 agent 一个,文件名用 agent 的 slug,落在 ~/.codex/agents/ 下(tools.json 的用户级模板是 .codex/agents/{slug}.toml);文件里只有 namedescriptiondeveloper_instructions 三个键,正文整体作为 developer_instructions 的值经 basic string 编码写入。

至于终端上会打印什么、Codex 里这些 agent 怎么显示、加载时机是什么——我们没有安装或运行过其中任何 agent,也没有跑过这两个脚本,这些一律不写。tools.json_note 自己也说了:目录本身不携带任何 app 发布状态,渲染器覆盖面是消费方自己的事。

装完检查哪几处

  1. 文件数量对不对得上选择器。数一下 ~/.codex/agents/ 下的 .toml 个数,跟你 --division / --agent 圈定的范围对一遍。数量对不上,多半是选择器没生效、走成了全量。
  2. TOML 是否合法。这是这一路最该验的一处:拿任意一个 TOML 解析器读一遍产物,能解析就说明 basic string 编码这一步做对了。正文里越是有花活的 agent 越值得抽来验。
  3. 字段是不是只有三个。多出来的键说明版本或转换器和你手上的文档对不上了。
  4. 源文件先过 lint。你自己新增或改过的 agent,装之前跑 ./scripts/lint-agents.sh [file ...],尤其是行尾——CRLF 是 ERROR 级。要提醒的是 lint 检查的是结构与内容存在性,不评估提示词好不好用,推荐章节缺失只是 WARN、不会让检查失败。
  5. 别把 scope 搞混install.sh 注释里,codexclaude-codegemini-clicopilot 这些落在家目录,而 opencodecursoraiderwindsurf 四个写的是当前目录。在错误的目录里执行,这四个会把产物撒进你正好待着的那个项目。

什么情况别这么装

  • 只想试一两个 agent 的,别全量装。用 --agent 点名,或者干脆把 agent markdown 当参考资料读——README 里把「当参考资料用」列为三种使用方式之一。
  • 目标工具不是 per-agent 的,这套「一 agent 一文件」的心智模型直接失效。Aider 的 CONVENTIONS.md 和 Windsurf 的 .windsurfrulesroster:所有 agent 合并成一个文件,所以在那两个工具里没法只装某一个 agent。Hermes 更特殊,是 plugin——一个构建出来的产物,不能按 agent 渲染成字符串,只能走 CLI。
  • 要在产物上做定制的,先想清楚拷贝还是链接。--link 用符号链接,改动会自动传播;但反过来,你在 ~/.codex/agents/ 里手改的东西,下一次 convert 覆盖源产物时会怎样,取决于你改的是哪一头。真要长期定制,改 division 目录里的源 markdown 更稳。
  • 指望装完就自动生效的,这一路给不了保证。Codex 怎么消费 developer_instructionstools.json 并没有承诺,我们也没有验证过;仓库这一侧的契约只到「格式与落点」为止。同一个仓库里还有个更直白的反例:Cursor 那一路的 .mdcalwaysApply: falseglobs: "" 写死了,转换出来的规则默认不会自动生效,得在 Cursor 里按需引用——装完发现「没反应」多半就是这个。
  • 看到 TOML 就以为通用的。Mistral Vibe 也用 TOML,但它的 format 是 vibe-toml,产物是 .vibe/agents/{slug}.toml 加一份 .vibe/prompts/{slug}.md,是两个文件。tools.json_noteformat 的定义是:同一个 format 名保证渲染出字节级相同的输出——codex-tomlvibe-toml 是两个名字,就是两套东西,别互相类推。

延伸阅读


本文依据 agency-agents 官方仓库(github.com/msitarzewski/agency-agents)的 README、tools.jsondivisions.jsonscripts/ 下的安装与校验脚本整理,核对日 2026-08-09。本文内容为仓库源码与文档口径,我们没有安装或运行过其中任何 agent。目录、脚本与安装路径随上游更新而变动,请以仓库最新内容与 ./scripts/install.sh --help 的实际输出为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。