我一直很喜歡 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.md
Codex
~/.codex/
├── agents/
│ └── reviewer.toml
└── reviewer.config.toml

Claude 會讓 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 沒有處理的那一半。

同一個角色,格式怎麼對照?

兩邊不會逐欄完全相同,但角色真正需要的骨架很接近:名字、什麼時候該用它、核心工作指令,以及它能載入的能力。以下只留下會影響設計判斷的欄位:

重點ClaudeCodex
作為 subagent讀取 ~/.claude/agents/reviewer.md讀取 ~/.codex/agents/reviewer.toml
作為主 session仍讀取 ~/.claude/agents/reviewer.md讀取 ~/.codex/reviewer.config.toml
使用指令claude --agent reviewercodex --profile reviewer
格式Markdown + YAML frontmatterTOML
角色名稱namename
何時使用descriptiondescription
角色靈魂與工作項目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 檔案裡。