我一直很喜歡 Claude 的 Agent 設計,因為它的心智模型很直覺:先定義一個角色,再決定要讓它接手主對話,或在背景當一個專職 subagent。角色本身不需要跟著使用方式重寫一次。
後來我把同一套工作方式搬到 Codex,才發現事情沒有那麼單純。它不是少了 Agent,而是把不同情境的載入入口拆開了。也正是這個差異,逼我重新理解 Agent 設定的 Single Source of Truth 到底該放在哪裡。
Claude:一份角色定義,兩種使用方式
Claude 的設計很直接:同一份 Markdown agent file,既可以被叫成 subagent,也可以用 claude --agent <name> 讓主 session 套用該角色。官方文件 也把這兩種用法放在同一份 custom subagent 定義下。
這種設計讓我很容易建立直覺:一個 Agent 就是一份角色定義。角色要做什麼、回報長什麼樣子、哪些事情該交給它,集中在同一個地方;主 session 與 subagent 只是使用它的兩種方式。
Codex:角色與主 session 走不同入口
Codex 現在也有 custom agents,能以 ~/.codex/agents/*.toml 定義專職 subagent,並設定 model、sandbox、MCP 或 skills;但主 session 走的是另一條入口:codex --profile <name> 會載入對應的 profile configuration layer。Codex 的 custom agents 文件與 profiles 文件把這兩件事分開定義。
所以 Codex 的差異不只在 TOML 與 Markdown,而在誰會讀這份資料、什麼時候讀。Claude 原生把主 session 與 subagent 收在同一種 agent file;Codex 則把 custom agent 和 profile 分成兩條載入路徑。
如果只看檔案格式,很容易得到一個煩人的結論:同一個角色似乎得在 Claude 寫一次、在 Codex 的 custom agent 寫一次、再為 Codex profile 補一次。但真正該問的不是「我要維護幾個檔案」,而是「哪些內容真的應該由同一個地方定義」。
兩個目錄,兩種載入方式
把兩邊的個人設定目錄排在一起看,結構其實很像:都是 runtime 根目錄底下有一個 agents/ 資料夾。Codex 多出來的,是和 agents/ 同一層的 profile 檔。
Claude~/.claude/└── agents/ └── reviewer.mdCodex~/.codex/├── agents/│ └── reviewer.toml└── reviewer.config.tomlClaude 會讓 reviewer.md 同時服務兩種情境:主 session 以 claude --agent reviewer 套用角色,或由目前對話把它派成 subagent。Codex 則明確分工:agents/reviewer.toml 是被派出去的 custom agent;reviewer.config.toml 則在 codex --profile reviewer 時疊加到主 session 設定上。
profile 本質上是 Codex 的命名設定層,不是官方定義的 Agent 檔。不過如果我把 reviewer 這個 profile 當成「以 reviewer 角色開一個主 session」的入口,它就剛好補上了 custom agent 沒有處理的那一半。
同一個角色,格式怎麼對照?
兩邊不會逐欄完全相同,但角色真正需要的骨架很接近:名字、什麼時候該用它、核心工作指令,以及它能載入的能力。以下只留下會影響設計判斷的欄位:
| 重點 | Claude | Codex |
|---|---|---|
| 作為 subagent | 讀取 ~/.claude/agents/reviewer.md | 讀取 ~/.codex/agents/reviewer.toml |
| 作為主 session | 仍讀取 ~/.claude/agents/reviewer.md | 讀取 ~/.codex/reviewer.config.toml |
| 使用指令 | claude --agent reviewer | codex --profile reviewer |
| 格式 | Markdown + YAML frontmatter | TOML |
| 角色名稱 | name | name |
| 何時使用 | description | description |
| 角色靈魂與工作項目 | Markdown 正文 | developer_instructions |
| Skills 載入(選用) | skills frontmatter 預載指定 Skills | [[skills.config]] 可在 config layer 設定;custom agent 未指定時繼承 parent session |
這也是我後來不再把它理解成「兩套 Agent」的原因。兩邊都在描述同一個角色意圖,只是 Claude 把主 session 與 subagent 收進一份 Markdown;Codex 將 custom agent 與主 session profile 拆成兩條 runtime 載入路徑。
檔案不同,角色不必重寫
前面兩種檔案都必須存在,因為 Claude 與 Codex 的載入方式不同。Markdown、TOML、model、權限與 profile 都是各自 runtime 的事情,硬把它們合成同一個檔案沒有意義。
但角色的工作原則不該跟著重寫。像是它負責什麼、怎麼回報、什麼結果才算完成,這些是 Agent 的共同工作規格,和 Claude 或 Codex 無關。我把這一層獨立成 shared contract,讓兩邊的 runtime 定義都參考同一份規格。
換句話說:Claude Markdown 與 Codex TOML 是兩個入口;角色意圖才是應該只有一份的東西。Codex 的 profile 與 custom agent 若有相同設定,可以用 symlink 減少複製,但那只是一座橋,不代表兩個入口從此完全相同。
SSOT 不只是一份角色規格
除了角色本身,跨工具還會共用另一種資料:有哪些 Agent、各自擅長什麼、哪些工作不該派給它。這些名單與 routing 如果散在不同工具裡,新增或調整角色時就很容易漏改。
所以我把 roster、capabilities、excludes、default account 這類 metadata 收進 registry.yaml,讓需要它的工具都讀同一份。registry 不負責描述 Agent 的完整語氣或每一條工作指令;它只回答控制端最需要的問題:有哪些角色、各自適合做什麼、哪些工作不該派給它。
這就是我現在理解的 Single Source of Truth:不是把所有東西塞進一個萬用檔案,而是讓每一種資料各自只有一個負責定義的位置。角色規格有 shared contract,名單與 routing 有 registry,Claude 與 Codex 的啟動細節則留在自己的 runtime 檔案裡。