ARTICLE · 995396
Skill和普通文档有什么区别:从 Codex 源码看 skill 的作用
我之前一直在想,skill 到底有没有必要。即便没有 skill,一个项目已经可以在 AGENTS.md 里放索引,再指向更详细的文档、脚本和外部工具说明。模型照着索引读取文件、执行脚本,看起来也能完成同样的工作。
沿着 Codex 的源码看下来,我的结论是:skill 的价值是“文件目录结构协议 + 与协议耦合的 agent harness”所实现的渐进式展开。协议规定 SKILL.md、frontmatter、references/、scripts/ 和 assets/ 怎样组织;harness 据此发现 skill、构建 catalog、处理显式注入和调用观测,模型再根据 catalog 完成隐式选择。
技术领域有过很多类似的协议化、标准化工作,比如不同的编程语言、Kubernetes。编程语言先约定语法和语义,语言实现、工具链和业务代码因此有了共同基础;Kubernetes 抽象出 Pod、Deployment、Service 等核心资源并约定 API schema,大家统一用 YAML 描述期望状态,控制器和周边工具据此工作。
没有 skill,只用 AGENTS.md + 项目文档 + 脚本 也能表达相同的工作流。但要让模型事先知道有哪些可选工作流,所有入口都得预先写进随正文注入的 AGENTS.md 索引;新增或移除入口也要修改这份索引。
这套协议让 Codex 实现了 skill 级别的渐进式展开和目录级插拔:harness 自动发现符合约定的 skill,先只向模型提供 name、description 和 source locator。Skill 被选中后才加载完整 SKILL.md;显式路径由 harness 注入,隐式路径由模型读取。附属资料再由模型根据正文按需处理。增加或移除 skill 目录时,catalog 随之更新。
只服务当前仓库、可选流程又不多时,AGENTS.md 通常够用;需要管理大量可选流程、跨项目复用或动态接入时,skill 才更有价值。
分析范围
本文基于 OpenAI Codex 的 rust-v0.145.0 源码。
术语约定
AGENTS.md 的链路是最简单的
Codex 把 AGENTS.md 当作项目级指令。agents_md.rs 记录的发现过程如下:
从当前工作目录向上寻找项目根; 再从项目根走回当前工作目录; 读取沿途的 AGENTS.override.md、AGENTS.md或配置的候选文件; 按从外到内的顺序拼接正文;
不越过项目根。
read_agents_md()。拼接后的文本最终以 user context 的形式进入模型,外层标记为 # AGENTS.md instructions ... <INSTRUCTIONS>,见 user_instructions.rs。
这条链路可以压缩成:
项目根 → 当前工作目录逐级发现 → 读取预算内正文 → 拼接 → 注入模型上下文因此,AGENTS.md 适合放当前目录树中始终生效的约束,例如构建命令、代码风格、安全要求和验证步骤。
Skill 的发现是程序化的
AGENTS.md 正文与 skill catalog 走两条独立的上下文链路。Codex 源码中的 skills_are_not_appended_to_agents_md 测试固定了这个边界。
按约定扫描
这里的“发现”由 Codex 宿主端在模型推理前程序化完成。Rust loader 先按固化规则确定哪些文件有资格进入 skill catalog,模型随后再做相关性判断。
这套逻辑分为两步。第一步,skill_roots() 根据配置层、当前工作目录和 plugin 状态算出一组扫描根:
项目配置目录下的 skills; 从项目根到当前目录沿途的 .agents/skills; 用户目录下的 $HOME/.agents/skills;为兼容旧版本保留的 $CODEX_HOME/skills; Codex 随安装包提供、缓存到 $CODEX_HOME/skills/.system的 system skills;
管理员、plugin 和运行时额外配置的 skill roots。
第二步,discover_skills() 对每个根执行有界目录遍历。代码固化了扫描深度、目录数、文件数、隐藏目录和符号链接策略。
遍历结果中只有 basename 精确等于 SKILL.md 的文件会成为候选。随后 parse_skill_file() 读取正文和可选元数据,解析成功后生成 SkillMetadata。
扫描根、入口文件名、frontmatter 和附加元数据共同构成文件目录结构协议。这套协议给 loader 提供了稳定的探测边界:符合约定的目录可以在模型推理前被枚举、校验和缓存,其他 Markdown 继续按普通文档处理。
AGENTS.md 中的链接由模型根据自然语言判断对应哪个工作流、什么时候读取。Skill 把 name、description 和位置等信息变成结构化数据,loader 可以在模型推理前完成注册。
发现和调用是两个阶段。固化的 loader 逻辑只回答“有哪些合法 skill、它们的元数据是什么”;当前任务使用哪个 skill,由显式 mention 或模型判断决定。
解析结构化元数据
每个 SKILL.md 的 YAML frontmatter 至少要提供有效的 description,name 可以省略并由目录名推导。
Codex 会校验字段和长度。无效的非 system skill 会形成可见错误,相关代码在 parse_skill_frontmatter_metadata_inner()。
Skill 目录还可以带 agents/openai.yaml,其中包含三类可选元数据:
interface:展示名称、简述、图标、颜色和默认 prompt;
dependencies:工具依赖;
policy:例如是否允许 implicit invocation。
这些字段会进入专门的 SkillMetadata、SkillInterface、SkillDependencies 和 SkillPolicy 数据结构,见 model.rs。
Codex 由此得到一个可以枚举、校验、过滤和展示的 skill 元数据目录。
Agent harness 怎样消费已发现的 skill
Codex 在 agent harness 中为 skill 做了专用处理,把文件目录结构约定转成一套可缓存、可注入、可路由和可观测的机制。
这条主链路可以概括为:
文件目录结构约定 → SkillsService(发现、缓存与预热) → HostSkillsSnapshot(保存本机发现结果) → TurnSkillsContext(装配当前 turn 可用的 skill) → developer skill catalog(模型先看到的元数据目录) ├─ 显式指定 → mention 路由 → <skill> 正文注入 └─ 隐式触发 → 模型按 description 判断 → 工具读取 SKILL.md → 普通工具执行 → invocation 记录、对话 history 与长期 memory 管理生命周期由宿主持有。ThreadManager 创建独立的 SkillsService。session 初始化时,Codex 根据配置和 plugin roots 预热 skill snapshot,并与 AGENTS.md 刷新并行执行,见 warm_plugins_and_skills_for_session_init() 及其调用处。
每个 turn 又会按当时的 cwd、配置、plugin 和执行环境文件系统请求 snapshot,再放进 TurnSkillsContext,相关装配位于 turn_context.rs。宿主由此为模型准备好本轮可用 skill 的结构化视图。
App server 中的 SkillsWatcher 监听 skill 目录变化,清理缓存并发送 SkillsChanged。目录监听与后续 turn 的 snapshot 装配共同支持目录级插拔。
SkillsExtension 是把 skill 逻辑接入 agent harness 的专用组件。它把 catalog 放进模型可见的上下文,并处理配置变化和 invocation 记录。
执行阶段走普通工具。Harness 保证同一个 skill 在一个 turn 内只记录一次 invocation。Skill 正文作为当前 turn 的临时上下文传给模型;memory writer 会f="https://github.com/openai/codex/blob/rust-v0.145.0/codex-rs/memories/write/src/phase1.rs#L466-L473" data-draft-type="text-link">排除完整的 <skill>...</skill> 片段,长期记忆只接收其余内容。
为什么需要渐进式展开
Anthropic 在 2025 年的 Equipping agents for the real world with Agent Skills 中,把 progressive disclosure 称为 Agent Skills 保持灵活和可扩展的核心设计原则,并用“目录、章节、附录”解释三个层级:启动时只加载所有 skill 的 name 和 description;任务匹配后读取完整 SKILL.md;更细的参考文件只在具体场景需要时读取。Agent Skills 规范的概览 将这三个阶段概括为 discovery、activation 和 execution。
它首先解决的是上下文成本。全量加载时,常驻上下文需要容纳所有 skill 的正文;渐进式展开把它压缩为“全部 skill 的短元数据 + 当前 skill 的正文 + 当前任务需要的附属资料”。附属资料留在文件系统中,一个 skill 可以携带远多于单次上下文容量的内容,模型只读取当前任务需要的部分。这样既为用户任务保留更多上下文,也减少互斥或低频说明带来的 token 消耗。Anthropic 的文章也明确建议把很少同时使用的内容拆到不同文件中。
这种取舍在计算机系统中很常见。程序把当前调用直接需要的局部状态放在栈帧中,体量更大或生命周期更长的数据放在堆中,通过引用访问;计算机体系结构又用寄存器、缓存、内存和存储组成层次,只把当前工作集放进更快、容量更小的层级。Skill 的 catalog、SKILL.md 和附属资料采用相似的组织思路:把有限上下文留给当前任务,其余内容保留为可寻址、可按需读取的外部资料。
先注入 catalog,再按需读取正文
选中 skill 前,Codex 只渲染名称、描述和 source locator,形成 available skills catalog。
render.rs 为这份目录设置了独立预算:如果模型提供了 context window,skill 元数据默认最多使用其中的 2%;没有窗口信息时使用 8,000 字符的默认预算。描述过长时先缩短描述,仍然放不下时再省略条目并产生警告。
Codex harness 会把这份 catalog 渲染成一段 developer 消息,随本轮请求发送给模型,模型由此知道当前有哪些可用 skill,见 available_skills_instructions.rs。如果启用了 skill 使用说明,Codex 还会附上以下规则:
用户点名某个 skill,或者任务明显匹配其描述时,应在当前 turn 使用它; 选中后要完整读取 SKILL.md; 相对路径按 skill 目录解析; 只读取当前任务需要的 references、scripts 和 assets; 每个 turn 重新选择需要使用的 skill。
渐进式展开止于 skill 边界
Codex 的结构化展开止于 skill 一级:SKILL.md 先被程序化发现,匹配当前任务时再被整体加载。其余内容走 agent harness 的普通工具链;模型根据 SKILL.md 判断需要哪些 references/、scripts/ 和 assets/,再调用文件或 shell 工具读取、查看或执行,处理方式与普通项目文档和脚本相同。
一个 skill 内的子流程共用同一组 name、description 和 invocation。具备一定复杂度,并且需要独立进入 catalog、匹配、启停或观测的流程,应拆成独立 skill。因此,我倾向于使用扁平、细粒度的薄 skill:每个 skill 独立按需加载;多个 skill 共用的说明只在项目文档或脚本中维护一份,以减少文档复制和信息冗余。
路径一:显式指定
显式指定由 harness 确定性处理。用户通过 UI 选择 skill 时,输入里会出现结构化的 UserInput::Skill;文本中的 $skill-name 也会被解析。
collect_explicit_skill_mentions() 会:
优先按结构化输入中的路径匹配; 过滤已经禁用的 skill; 对路径去重;
对重名 skill 避免含糊匹配;
再处理文本中的 $skill-name。
匹配成功后,build_skill_injections() 读取完整 SKILL.md,记录这次 invocation,并把正文包装成:
<skill><name>...</name><path>...</path>...完整 SKILL.md 正文...</skill>这个片段以 user context 进入当前 turn。集成测试 user_turn_includes_skill_instructions 直接检查了请求中是否同时包含 skill 名称、路径和正文。
路径二:隐式触发
用户未显式指定 skill 时,模型根据 catalog 中的 description 自行判断,再主动读取 SKILL.md。Codex 注入的 developer instructions 明确要求使用与任务明显匹配的 skill,见 SKILLS_HOW_TO_USE_WITH_ABSOLUTE_PATHS。
当前生产链路可以写成:
用户任务 + available skills catalog → 模型在普通推理中比较任务与 name / description ├─ 判断匹配 → 通过普通工具读取 SKILL.md → 按正文行动 └─ 判断不匹配 → 不加载该 skill当前生产接口只提供 catalog 和普通工具,相关性判断留在模型内部,harness 再根据后续工具调用观测结果。源码没有定义 skill=A, confidence=0.83 一类结构化结果、独立 evaluator 模型或统一置信度阈值。description 写得是否准确,直接影响这次普通模型决策。
源码里的自动 selector 当前运行在 shadow mode。SkillsExtension 先生成用于正文注入的 selected_entries,再运行 shadow selection。排名不会写回 selected_entries,也不会进入 prompt;它只保存在当前 turn 的实验状态中,等模型实际使用 skill 后交给 record_invocation() 记录是否命中和排在第几位。配置结构也明确将其定义为“不改变 prompt 内容”的 shadow selector,见 config.rs。
实验使用四种廉价的词法排序方法:
weighted lexical:统计词和短语的重合,并让 name 的权重高于 short description 和 description;
fielded BM25:分别计算三个字段的 BM25 分数,同时考虑词频、字段长度和词的稀有程度;
character n-gram:比较二到五个字符组成的片段,对局部拼写和词形差异更宽容;
multi-query lexical:把用户输入拆成多个句子或子句,分别排序后合并结果。
这项旁路实验不改变生产 skill 选择和 prompt,只增加本地排序时间与观测指标。它输出候选排名、耗时、候选缩减比例和命中情况,用来评估这些方法对模型实际 skill 使用的预测效果,见 ShadowSelectionExperiment。
模型读取 SKILL.md 或运行 scripts/ 后,detect_implicit_skill_invocation_for_command() 识别这次使用,maybe_emit_implicit_skill_invocation() 再通知 extension 并记录实际调用。任务开始前的选择仍由显式 mention 或模型完成。
工程边界:必须每次执行、漏掉一次就可能造成事故的规则,应放在始终可见的 AGENTS.md,或者下沉到 hook、CI、命令包装器等机械门禁。Implicit skill 更适合按任务启用的可选流程。
我准备尝试的项目组织设想
我准备按以下三层组织:AGENTS.md 保存粗粒度的项目索引,SKILL.md 保存按任务加载的工作流合同,项目文档和脚本保存共享内容的权威正文。AGENTS.md 和 skill 都可以长期维护;区别在于前者随适用目录的上下文注入,后者只在被选中后加载。
AGENTS.md:粗粒度的项目索引
放什么:最重要的项目级约束,以及构建、测试、发布、故障处理、权威文档和 skill 的入口。
原则:保持粗粒度和精简,只放模型在选择具体工作流前必须知道的内容;详细规则继续指向 skill、项目文档或脚本。
项目文档和脚本:共享内容的权威来源 放什么:完整设计、操作步骤、参数解释,以及负责执行或校验的脚本。 原则:AGENTS.md 和 skill 提供入口,人、Codex、CI 和其他工具统一引用这一来源。
扁平、细粒度的薄 skill:按任务加载的合同 拆分边界:具备一定复杂度、需要独立匹配、启停或观测的工作流,对应一个独立 skill。 内容:name 和 description 负责发现与匹配;SKILL.md 可以长期维护某类任务的规则、入口、执行顺序和验收标准;references/、scripts/ 和 assets/ 承载当前 skill 需要的执行材料。 加载边界:SKILL.md 是一级加载单位,其中的 Markdown 链接保持普通链接语义。必要文档要明确写成“执行前必须读取”,再由模型通过普通工具打开。 何时引入:工作流需要跨仓库复用;可选流程太多、持续占用上下文;需要 $skill 或 UI 选择;需要打包 references、scripts 和 assets;或者需要启停配置、依赖声明、更新监听与 invocation 观测。
Skill 不能解决的问题:稳定复杂工作流 和 信息治理
用工作流结构驱动 agent
在本文分析的版本里,Codex harness 固化的是 skill 的发现、加载和 invocation 观测。它把 SKILL.md 作为自然语言上下文交给模型,不会将其解析为由节点、连线、条件和信号组成的可执行工作流,也不会根据流程状态调度 agent。
控制方向仍然是 agent 驱动流程:模型读取 skill 和当前上下文,自行决定下一步及工具调用,harness 执行工具并返回结果,模型再继续推进。Skill 可以用自然语言描述顺序、循环、多级条件和多信号驱动关系,流程结构本身却不能决定何时进入下一节点、重复当前节点、等待信号或转入某个分支。上下文变长、分支增多或中途发生异常时,模型可能漏掉步骤、规约或退出条件。
大量skill治理
有可能发生的是,第三方 skill 各自携带一套说明和规约。同一条要求可能以不同措辞重复散落在多个 SKILL.md 中;不同 skill 也可能对工具选择、验证步骤或发布策略给出相互冲突的要求。随着 skill 本身变复杂、上下文规模膨胀,模型会更难判断不同表述是否语义等价、各自适用什么范围,以及发生冲突时谁应优先。
项目需要为这类信息建立权威来源和优先级。共用规约只在 AGENTS.md、项目文档、hook 或 CI 中维护一份,skill 引用这份统一定义。引入第三方 skill 时记录来源、版本和适用范围,并在启用前检查重复与冲突;发生冲突时,按项目预先声明的权威来源和机械门禁处理。