如何系统评测 Agent Skills:从触发用例到自动化评分的完整指南
改了一版 Skill,究竟是真变好了,还是只在几次试跑中看起来更顺?这份 OpenAI 实操指南用六个步骤搭出完整评测闭环:定义成功、人工试跑、设计正反用例、检查 JSONL 轨迹,再用结构化 rubric 审查质量。文中保留全部代码,并提供可直接生成测试 Prompt 与检查清单的交互工具。
开发 Agent Skill 时,最普遍的困扰是改动往往凭感觉判断:换了一版提示词,某些用例看似更顺畅,但你很难确认改动是否真的变好,更难察觉倒退(Regression)——原本该触发的场景不再响应、关键执行步骤被遗漏,或者工作区残留了临时文件。摆脱这种主观推测的解决办法,是建立可比较的评测机制(Evals),用可重复执行、可比较的运行记录判断效果变化并及时发现回归。
Skill 本质上是一组组织有序的提示词与指令集(Prompts and Instructions)。针对 Skill 的评测,类似于面向智能体的轻量级端到端集成测试:输入目标 Prompt → 完整捕获 Agent 执行过程(Trace 轨迹与产生产物) → 运行针对性规则检查 → 输出可跨版本横向对比的分数。
将评测落到实处可以分为六个步骤:定义成功标准、写出可评测的 Skill、人工试跑并暴露隐藏假设、建立 10–20 条包含正反样本的微型评测集、用 JSONL 和确定性 grader 检查行为与产物,以及用只读 Codex 与 JSON Schema 做 rubric-based grading。在此基础上,再视需要引入成熟后的进阶监控。
步骤 1:定义成功标准
很多 Skill 难以评测的根源,在于一开始没有明确界定何为“执行成功”。如果只有模糊的期望,最终验收往往只能依赖随意的人工肉眼观察。第一步要做的是把成功标准清晰定义下来,拆解为聚焦在必过项(Must-pass)上的具体维度。
具体做法是在动笔编写或重构前,列出一份精简的检查清单,明确区分四个目标维度:
- 结果目标(Outcome goals):任务是否最终完成?构建的应用能否正常跑起来?
- 过程目标(Process goals):Agent 是否真正调用了对应的 Skill?是否按照预期顺序执行了指定工具和命令?
- 风格目标(Style goals):生成的文件结构、组件写法、样式方案是否符合既定技术规范?
- 效率目标(Efficiency goals):执行过程中是否存在无效重试(比如反复尝试同类错误命令,或者消耗了异常庞大的 Token)?
判断通过的标准在于硬性指标是否全部达标。例如在脚手架场景中,“是否执行了 npm install”和“是否生成了 package.json”属于过程与结果层面的硬性检查,必须严格通过;而“目录结构是否规整”“样式是否遵循约束”则作为风格标准逐项核对。
完成这步后,你会得到一份涵盖四个维度的结构化验收检查清单(Checklist)。它为后续编写测试断言提供了明确的靶心,评测的核心目的就是在早期暴露具体的回归信号,而不是给出一个模棱两可的综合合格率。
步骤 2:写出可评测的 Skill
要让评测能够运行,前提是 Skill 本身具有清晰可检验的行为边界。第二步要做的是编写出符合规范、包含明确验收条件的 Skill 骨架。
在 Codex 体系中,一个 Skill 本质上是一个包含 SKILL.md 文件的独立目录。文件开头必须包含由 name 和 description 组成的 YAML Front Matter,其后则是规范 Agent 行为的 Markdown 指令,以及可选的配套脚本或资源。
这里的 name 和 description 是整个 Skill 最核心的触发信号。Codex 正是依据这两项元数据,来判断当前任务是否应当调用该 Skill,并决定何时将 SKILL.md 中的完整正文注入到上下文窗口中。如果命名与描述过于宽泛或语义过载,Skill 就无法稳定激活。
如果从零开始,可以直接在终端调用官方提供的脚手架技能:
$skill-creator
脚手架会通过交互引导你明确 Skill 的职责、触发时机,以及属于纯指令型(Instruction-only)还是脚本辅助型(Script-backed)。这里的默认建议是优先采用纯指令型设计。
实战示例:统一规范的 Demo 搭建技能
我们以一个名为 setup-demo-app 的最小化实战 Skill 为例。它的职责是在统一的规范下,稳定初始化一个轻量 React 演示项目:采用 Vite 的 React + TypeScript 模板,通过官方 Vite 插件配置 Tailwind CSS,遵循精简的目录架构,并给出可直接核验的完成定义(Definition of Done)。
根据生效范围的不同,你可以将下方内容保存至两个路径之一:
- 仓库级(Repo-scoped):
.codex/skills/setup-demo-app/SKILL.md - 用户全局(User-scoped):
~/.codex/skills/setup-demo-app/SKILL.md
---
name: setup-demo-app
description: Scaffold a Vite + React + Tailwind demo app with a small, consistent project structure.
---
## When to use this
Use when you need a fresh demo app for quick UI experiments or reproductions.
## What to build
Create a Vite React TypeScript app and configure Tailwind. Keep it minimal.
Project structure after setup:
- src/
- main.tsx (entry)
- App.tsx (root UI)
- components/
- Header.tsx
- Card.tsx
- index.css (Tailwind import)
- index.html
- package.json
Style requirements:
- TypeScript components
- Functional components only
- Tailwind classes for styling (no CSS modules)
- No extra UI libraries
## Steps
1. Scaffold with Vite using the React TS template:
npm create vite@latest demo-app -- --template react-ts
2. Install dependencies:
cd demo-app
npm install
3. Install and configure Tailwind using the Vite plugin.
- npm install tailwindcss @tailwindcss/vite
- Add the tailwind plugin to vite.config.ts
- In src/index.css, replace contents with:
@import "tailwindcss";
4. Implement the minimal UI:
- Header: app title and short subtitle
- Card: reusable card container
- App: render Header + 2 Cards with placeholder text
## Definition of done
- npm run dev starts successfully
- package.json exists
- src/components/Header.tsx and src/components/Card.tsx exist
通过明确的步骤拆解与完成定义(Definition of done),这一步产出了一份结构规范的 SKILL.md 文件。判断它是否合格,关键看输出边界与完成条件是否清晰可核验;如果规则模糊多变,后续的自动化评测就会失去判定依据。
步骤 3:人工试跑并暴露隐藏假设
写好 SKILL.md 之后,不要急于编写大批量脚本,这一步要做的是在真实或临时沙盒环境中手动试跑,观察 Agent 在哪些环节偏离预期,从而暴露并修复 Skill 中未明说的隐藏假设。
你可以先在交互界面中通过 /skills 斜杠命令选择,或者直接在提示词中使用 $setup-demo-app 显式引用该技能。这一阶段的重点是系统排查出三类常见的隐藏假设:
- 触发假设(Triggering assumptions):本该触发的通用提问(例如“帮我搭一个快速 React demo”)未能命中;或者本不该触发的临近场景(例如已有项目提问“帮我给现存应用配个 Tailwind”)却被意外激活。
- 环境假设(Environment assumptions):Skill 默认当前处于完全干净的空目录,或者默认操作系统必然安装了
npm且比其他包管理器更优先。 - 执行假设(Execution assumptions):Agent 误以为依赖已经在全局装好而直接跳过了
npm install,或者在 Vite 项目还没初始化完成前就急着去配置 Tailwind。
当你准备把这些排查过程固化下来时,应当从交互式界面切换到无界面的自动化命令行工具 codex exec。该命令将执行进度流式输出到标准错误(stderr),而仅将最终产出输出到标准输出(stdout),适合脚本调度与 CI 集成。
由于脚手架搭建任务涉及磁盘文件的写入与创建,执行时需要显式附加 --full-auto 权限标识:
codex exec --full-auto \
'Use the $setup-demo-app skill to create the project in this directory.'
在自动化调用时,应遵循最小权限原则,仅授予完成任务所需的最低权限范围,并检查是否出现超出预期的权限升级。这一步不是要求首次试跑就无报错,而是要把偏离预期的地方记录下来。完成排查后,你会得到一组边界问题与可重复执行的调用命令,它们构成下一步评测集的核心来源。
步骤 4:建立 10–20 条包含正反样本的微型评测集
评测 Agent 不需要一上来就堆砌几百上千条庞大基准。对于单个独立 Skill,维持一套包含 10 到 20 条代表性用例的微型评测集,就足以在早期发现有价值的回归信号。
这一步要做的是把前期排查到的边界沉淀为测试用例。我们可以创建一个精简的 CSV 文件(例如 evals/setup-demo-app.prompts.csv),用表格来维护触发预期与具体 Prompt,后续遇到新问题时持续追加新行:
id,should_trigger,prompt
test-01,true,"Create a demo app named `devday-demo` using the $setup-demo-app skill"
test-02,true,"Set up a minimal React demo app with Tailwind for quick UI experiments"
test-03,true,"Create a small demo app to showcase the Responses API"
test-04,false,"Add Tailwind styling to my existing React app"
这四条用例分别代表了不可或缺的四类测试意图:
- 显式调用(Explicit invocation,如
test-01):提示词中明确带上了$setup-demo-app。它用于验证在用户明确指名时,底层调用链路是否畅通,确保后续修改配置时没有破坏基本激活能力。 - 隐式触发(Implicit invocation,如
test-02):完全不提 Skill 名称,只陈述该技能针对的标准使用场景。它用于验证 Front Matter 中的name和description是否足够清晰,能让模型在日常对话中自主选择该技能。 - 带上下文的复杂触发(Contextual invocation,如
test-03):加入了具体业务场景(例如接入 Responses API),引入现实世界中的轻度噪声。它用于验证在存在额外干扰信息时,模型能否依然准确提取脚手架需求并保持统一的代码结构。 - 负向控制样本(Negative control,如
test-04):用于防御误触发(False Positives)。用户询问的是“给已有项目添加 Tailwind 样式”,语义上与 Skill 描述高度相近,但此时绝不能触发新建脚手架动作。缺少负向控制用例,往往会导致 Agent 在用户修改既有代码时误覆盖整个工程。
将正向隐式调用与负向控制对照结合,判断通过的标准就是正向场景稳定激活且步骤完整,负向场景坚决不误触发。完成这步后,你会得到一份结构化的评测数据集文件(evals/setup-demo-app.prompts.csv)。
下面的方案生成器把这一步变成可以直接带走的模板:填入 Skill 名、目标任务、必须执行的命令和必须生成的文件,它会生成四类测试 Prompt、确定性检查清单和建议保存路径。
可带走工具
把你的 Skill 变成第一组可运行的 Eval
修改下面四项,生成器会同步改写四类测试 Prompt、硬规则检查和建议保存路径。默认示例对应正文里的 setup-demo-app。
生成结果
四类 Prompt
确定性检查
建议产物
修改字段即可生成自己的版本。
步骤 5:用 JSONL 和确定性 grader 检查行为与产物
要确凿验证 Agent 真的执行了底层步骤,而不是仅仅碰巧交出一份看似可用的代码,不能依赖主观推测。这一步要做的是通过确定性 grader 直接检验命令轨迹与磁盘产物。
实现这一点的关键是在运行 codex exec 时开启 --json 参数,标准输出(stdout)会转为按行分隔的 JSONL 结构化事件流。每一次底层 Shell 命令的调用,都会以 item.started 和 item.completed 事件清晰记录,且其 item.type 均为 command_execution。
自动化执行与检测脚本
借助这一机制,我们可以编写一个轻量的 Node.js 脚本 evals/run-setup-demo-app-evals.mjs。运行该脚本会依次执行 Agent、捕获轨迹、解析 JSONL 事件,并直接断言命令记录与磁盘文件是否存在:
// evals/run-setup-demo-app-evals.mjs
import { spawnSync } from "node:child_process";
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
import path from "node:path";
function runCodex(prompt, outJsonlPath) {
const res = spawnSync(
"codex",
[
"exec",
"--json", // 核心参数:输出结构化 JSONL 事件流
"--full-auto", // 授予必要的文件系统修改权限
prompt,
],
{ encoding: "utf8" }
);
mkdirSync(path.dirname(outJsonlPath), { recursive: true });
// --json 模式下 stdout 即为标准的 JSONL 格式内容
writeFileSync(outJsonlPath, res.stdout, "utf8");
return { exitCode: res.status ?? 1, stderr: res.stderr };
}
function parseJsonl(jsonlText) {
return jsonlText
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line));
}
// 确定性规则 1:Agent 是否真正执行过包含 npm install 的命令?
function checkRanNpmInstall(events) {
return events.some(
(e) =>
(e.type === "item.started" || e.type === "item.completed") &&
e.item?.type === "command_execution" &&
typeof e.item?.command === "string" &&
e.item.command.includes("npm install")
);
}
// 确定性规则 2:磁盘上是否真正创建了 package.json 文件?
function checkPackageJsonExists(projectDir) {
return existsSync(path.join(projectDir, "package.json"));
}
// 单用例测试流程示例
const projectDir = process.cwd();
const tracePath = path.join(projectDir, "evals", "artifacts", "test-01.jsonl");
const prompt =
"Create a demo app named demo-app using the $setup-demo-app skill";
// 步骤 1:调用 CLI 执行任务,并将轨迹固化到磁盘
runCodex(prompt, tracePath);
// 步骤 2:读取轨迹事件并执行断言
const events = parseJsonl(readFileSync(tracePath, "utf8"));
console.log({
ranNpmInstall: checkRanNpmInstall(events),
hasPackageJson: checkPackageJsonExists(path.join(projectDir, "demo-app")),
});
这套确定性检查的判断标准很直接:脚本输出中的 ranNpmInstall 与 hasPackageJson 等断言必须全部返回 true。运行后,你会得到一份保存在 evals/artifacts/test-01.jsonl 的完整轨迹产物以及清晰的布尔检验结果。如果某项检查失败,开发者无需猜测,可以直接打开 .jsonl 跟踪日志,按时间顺序查看记录下来的命令执行事件。
步骤 6:用只读 Codex 与 JSON Schema 做 rubric-based grading
确定性代码断言能有效确认基础动作是否完成,但难以评估生成内容是否符合代码风格规范。例如:代码是否全部采用了函数式组件?Tailwind 是否严格使用了原子类而不是混入了 CSS Modules?组件目录结构划分是否严谨?
这一步要做的是在确定性检查后,引入第二道基于预定义标准的只读模型审查环节。具体做法是使用 codex exec 的 --output-schema 参数,强制模型输出符合特定 JSON Schema 的结构化评审数据,做到只读审查而不修改代码。
定义评审标准 Schema
我们先创建一份评分标准契约文件 evals/style-rubric.schema.json。在这份 Schema 中,顶层包含总体是否通过(overall_pass)与总体量化分值(score),具体检查维度则由逐项的通过判定(pass)与评审说明(notes)组成:
{
"type": "object",
"properties": {
"overall_pass": { "type": "boolean" },
"score": { "type": "integer", "minimum": 0, "maximum": 100 },
"checks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"pass": { "type": "boolean" },
"notes": { "type": "string" }
},
"required": ["id", "pass", "notes"],
"additionalProperties": false
}
}
},
"required": ["overall_pass", "score", "checks"],
"additionalProperties": false
}
执行只读代码审查
脚手架生成完成后,再次调用 codex exec 指向该目录。此时不传 --full-auto 权限,只提供检查提示词与 Schema 路径,并将评分 JSON 存储到产物目录:
codex exec \
"Evaluate the demo-app repository against these requirements:
- Vite + React + TypeScript project exists
- Tailwind is configured via @tailwindcss/vite and CSS imports tailwindcss
- src/components contains Header.tsx and Card.tsx
- Components are functional and styled with Tailwind utility classes (no CSS modules)
Return a rubric result as JSON with check ids: vite, tailwind, structure, style." \
--output-schema ./evals/style-rubric.schema.json \
-o ./evals/artifacts/test-01.style.json
审查结果会写入结构化 JSON:overall_pass 表示总体是否通过,score 用于跨版本比较,每个检查项则提供 pass 与 notes。如果团队另行设定分数阈值,可以在评测脚本中增加对应判断;原文示例本身没有规定统一阈值。这一步会输出评审报告文件 evals/artifacts/test-01.style.json。
通过 --output-schema 约定的输出不再是难以自动化解析的自然语言,而是可直接在 CI 或自动化评测流水线中合并、对比和追踪的结构化数据。如果在 GitHub Actions 中运行,官方提供的 Codex Action 原生支持在 codex-args 中透传该参数。
成熟后的进阶监控
当基础的“执行-断言-评分”双层回路稳定运转后,评测体系可以随着技能复杂度提升进一步扩展。优先建立快速明确的行为反馈,随着测试深入再逐步补充更重、耗时更长的校验手段。
以下六个方向可以把基础评测逐步扩展到更真实的工程场景:
- 命令抖动与循环重试检查:统计 JSONL 日志中的
command_execution发生频次。如果某一版修改导致同类命令执行次数激增,说明 Agent 陷入了报错重试的循环内耗中。 - Token 消耗监控:从事件流的
turn.completed中提取usage.input_tokens与usage.output_tokens。这能直观发现提示词膨胀或上下文冗余,防止单次执行开销失控。 - 静态编译验证:在 Agent 交付后由评测系统追加执行
npm run build。这是关键的端到端硬指标,能有效发现隐蔽的代码语法错误、损坏的模块导入或工具链版本冲突。 - 运行时冒烟探测:执行
npm run dev启动服务,随后使用curl请求本地端口,或者运行极轻量的 Playwright 脚本确认界面挂载正常。这种测试较为耗时,建议针对高风险核心用例按需启用。 - 仓库纯净度检查:执行
git status --porcelain,断言执行完毕后工作区不存在非预期的多余配置文件或临时代码,保持代码库整洁。 - 沙盒与权限回退测试:验证 Skill 在未提权的基础受限沙盒中是否依然能顺畅工作,确认在迭代过程中没有出现超出预期的权限升级,也没有引入强制要求提权的操作命令。
结语
从只靠主观感受调整,转向保留轨迹、逐项检查和持续比较,能让 Agent Skill 的迭代建立在可靠的工程基础上。这套流程将“这版改动看起来挺顺手”的主观推测,替换成了结构化的事实依据:
从定义明确的成功标准开始,写出结构规范的 Skill;通过人工试跑摸清假设边界,把调试中暴露的问题沉淀为 10–20 条典型评测用例;底层用 JSONL 事件流与确定性 grader 校验命令与文件产物,上层借助 JSON Schema 规范模型的代码审查;待技能成熟后,再按需扩展编译校验与运行时监控。
有了可重复运行的评测基座,每次调整提示词或执行逻辑时,都能明确看出改动究竟带来了哪些改进,并及时发现意外的回归信号。通过真实问题持续扩充用例,Skill 才能在长期迭代中稳定保持既有能力。
如何系统评测 Agent Skills:从触发用例到自动化评分的完整指南
开发 Agent Skill 时,最普遍的困扰是改动往往凭感觉判断:换了一版提示词,某些用例看似更顺畅,但你很难确认改动是否真的变好,更难察觉倒退(Regression)——原本该触发的场景不再响应、关键执行步骤被遗漏,或者工作区残留了临时文件。
Skill 本质上是一组组织有序的提示词与指令集(Prompts and Instructions)。针对 Skill 的评测,类似于面向智能体的轻量级端到端集成测试:输入目标 Prompt → 完整捕获 Agent 执行过程(Trace 轨迹与产生产物) → 运行针对性规则检查 → 输出可跨版本横向对比的分数。
步骤 1–3:定义 4 维标准与清晰行为边界
很多 Skill 难以评测的根源,在于一开始没有明确界定何为“执行成功”。如果只有模糊的期望,最终验收往往只能依赖随意的人工肉眼观察。第一步要做的是把成功标准清晰定义下来,拆解为聚焦在必过项上的具体维度:
SKILL.md 中 Front Matter 的 name 与 description 决定。编写完骨架后,先通过 codex exec --full-auto 手动试跑,排查“触发假设”(意图未命中)、“环境假设”(误认目录已就绪)与“执行假设”(误认依赖已全局安装)三大暗坑,把边界转化为固定调用。
步骤 4:构建包含正反样本的微型评测集
评测 Agent 不需要一上来就堆砌几百上千条庞大基准。对于单个独立 Skill,维持一套包含 10 到 20 条代表性用例的微型评测集,就足以在早期发现有价值的回归信号。评测集必须涵盖四类互补意图:
Create a demo app named devday-demo using the $setup-demo-app skill
验证在用户明确指名时,底层调用链路畅通无阻,确保配置未被破坏。
Set up a minimal React demo app with Tailwind for quick UI experiments
验证元数据(name / description)是否准确,模型能否在自然提问中自主命中。
Create a small demo app to showcase the Responses API
引入业务噪声与轻度干扰,验证复杂背景下能否稳定抽离核心脚手架动作。
Add Tailwind styling to my existing React app
关键防御!语义高度临近但决不能触发全新脚手架,防止错误覆盖用户现有项目。
步骤 5–6:核心机制——确定性断言与只读 Schema 双层闭环
要确凿验证 Agent 真的执行了底层步骤,而不是仅仅碰巧交出一份看似可用的代码,不能依赖主观推测。单纯看文件无法察觉执行过程中的盲目重试,而单纯依靠大模型自由评分又容易产生幻觉与提示词漂移。解决之道是建立软硬解耦的双层评测流水线:
双层自动化评测流水线(Dual-Layer Pipeline)
以确定性日志断言核验底层动作硬指标,以只读 Schema 审查上层代码风格规范,形成机器可量化比对的客观报告。
运行该脚本会依次执行 Agent、捕获轨迹、解析 JSONL 事件,并直接断言命令记录与磁盘文件是否存在:通过 codex exec --json --full-auto 导出每一步事件。检查事件中是否存在 command_execution 且匹配 npm install;同时直接调用 Node.js 的 existsSync 检查 package.json 是否存在。任何一项不符直接判挂,零模型幻觉干扰。
具体做法是使用 codex exec 的 --output-schema 参数,强制模型输出符合特定 JSON Schema 的结构化评审数据,做到只读审查而不修改代码。调用时剥离 --full-auto 权限防止修改文件,模型审查组件是否为函数式、Tailwind 是否避免 CSS Modules,并输出 overall_pass、score 与逐项布尔值和说明。
成熟后的进阶监控流水线
优先建立快速明确的行为反馈,随着测试深入再逐步补充更重、耗时更长的校验手段。当基础的双层评测跑通后,可将测试体系逐步延伸到工业级防线:
统计 JSONL 中 command_execution 发生频次。同类命令执行次数骤增代表 Agent 陷入了报错重试的内耗。
从 turn.completed 事件中提取输入与输出 Token 用量,防止提示词修改引发上下文爆炸与成本失控。
Agent 交付后自动追跑 npm run build,拦截隐蔽的 TypeScript 类型报错、语法缺陷与损坏的依赖导入。
执行 npm run dev 并用轻量 Playwright 或 curl 请求本地端口,确认 UI 根节点能成功挂载渲染。
执行 git status --porcelain,断言任务结束后没有残留多余的临时日志或未清理的脏配置。
验证 Skill 在无提权的受限沙盒中是否依然能正常运转,确保版本迭代中未引入非预期的强制提权指令。
Testing Agent Skills Systematically with Evals | OpenAI Developers
A practical guide to turning agent skills into something you can test, score, and improve over time.
When you’re iterating on a skill for an agent like Codex, it’s hard to tell whether you’re actually improving it or just changing its behavior. One version feels faster, another seems more reliable, and then a regression slips in: the skill doesn’t trigger, it skips a required step, or it leaves extra files behind.
At its core, a skill is an organized collection of prompts and instructions for an LLM. The most reliable way to improve a skill over time is to evaluate it the same way you would any other prompt for LLM applications.
Evals (short for evaluations) check whether a model’s output, and the steps it took to produce it, match what you intended. Instead of asking “does this feel better?” (or relying on vibes), evals let you ask concrete questions like:
- Did the agent invoke the skill?
- Did it run the expected commands?
- Did it produce outputs that follow the conventions you care about?
Concretely, an eval is: a prompt → a captured run (trace + artifacts) → a small set of checks → a score you can compare over time.
In practice, evals for agent skills look a lot like lightweight end-to-end tests: you run the agent, record what happened, and score the result against a small set of rules.
This post walks through a clear pattern for doing that with Codex, starting from defining success, then adding deterministic checks and rubric-based grading so improvements (and regressions) are clear.
1. Define success before you write the skill
Before writing the skill itself, write down what “success” means in terms you can actually measure. A useful way to think about this is to split your checks into a few categories:
- Outcome goals: Did the task complete? Does the app run?
- Process goals: Did Codex invoke the skill and follow the tools and steps you intended?
- Style goals: Does the output follow the conventions you asked for?
- Efficiency goals: Did it get there without thrashing (for example, unnecessary commands or excessive token use)?
Keep this list small and focused on must-pass checks. The goal isn’t to encode every preference up front, but to capture the behaviors you care about most.
In this post, for example, the guide evaluates a skill that sets up a demo app. Some checks are concrete. Did it run npm install? Did it create package.json? The guide pairs those with a structured style rubric to evaluate conventions and layout.
This mix is intentional. You want fast, targeted signals that surface specific regressions early, rather than a single pass/fail verdict at the end.
2. Create the skill
A Codex skill is a directory with a SKILL.md file that includes YAML front matter (name, description), followed by the Markdown instructions that define the skill’s behavior and optional resources and scripts. The name and description matter more than they might seem. They’re the primary signals Codex uses to decide whether to invoke the skill at all, and when to inject the rest of SKILL.md into the agent’s context. If these are vague or overloaded, the skill won’t trigger reliably.
The fastest way to get started is to use Codex’s built-in skill creator (which itself is also a skill). It walks you through:
$skill-creatorThe creator asks you what the skill does, when it should trigger, and whether it’s instruction-only or script-backed (instruction-only is the default recommendation). To learn more about creating a skill, check out the documentation.
A sample skill
This post uses an intentionally minimal example: a skill that sets up a small React demo app in a predictable, repeatable way.
This skill will:
- Scaffold a project using Vite’s React + TypeScript template
- Configure Tailwind CSS using the official Vite plugin approach
- Enforce a minimal, consistent file structure
- Define a clear “definition of done” so success is straightforward to evaluate
Below is a compact draft you can paste either into:
.codex/skills/setup-demo-app/SKILL.md(repo-scoped), or~/.codex/skills/setup-demo-app/SKILL.md(user-scoped).
---
name: setup-demo-app
description: Scaffold a Vite + React + Tailwind demo app with a small, consistent project structure.
---
## When to use this
Use when you need a fresh demo app for quick UI experiments or reproductions.
## What to build
Create a Vite React TypeScript app and configure Tailwind. Keep it minimal.
Project structure after setup:
- src/
- main.tsx (entry)
- App.tsx (root UI)
- components/
- Header.tsx
- Card.tsx
- index.css (Tailwind import)
- index.html
- package.json
Style requirements:
- TypeScript components
- Functional components only
- Tailwind classes for styling (no CSS modules)
- No extra UI libraries
## Steps
1. Scaffold with Vite using the React TS template:
npm create vite@latest demo-app -- --template react-ts
2. Install dependencies:
cd demo-app
npm install
3. Install and configure Tailwind using the Vite plugin.
- npm install tailwindcss @tailwindcss/vite
- Add the tailwind plugin to vite.config.ts
- In src/index.css, replace contents with:
@import "tailwindcss";
4. Implement the minimal UI:
- Header: app title and short subtitle
- Card: reusable card container
- App: render Header + 2 Cards with placeholder text
## Definition of done
- npm run dev starts successfully
- package.json exists
- src/components/Header.tsx and src/components/Card.tsx existThis sample skill takes an opinionated stance on purpose. Without clear constraints, there’s nothing concrete to evaluate.
Because skill invocation depends so much on the name and description in SKILL.md, the first thing to check is whether the setup-demo-app skill triggers when you expect it to.
Early on, explicitly activate the skill, either via the /skills slash command or by referencing it with the $ prefix, in a real repository or a scratch directory, and watch where it breaks. This is where you surface the misses: cases where the skill doesn’t trigger at all, triggers too eagerly, or runs but deviates from the intended steps.
At this stage, you’re not optimizing for speed or polish. You’re looking for hidden assumptions the skill is making, such as:
- Triggering assumptions: Prompts like “set up a quick React demo” that should invoke
setup-demo-appbut don’t, or more generic prompts (“add Tailwind styling”) that unintentionally trigger it. - Environment assumptions: The skill assumes it’s running in an empty directory, or that
npmis available and preferred over other package managers. - Execution assumptions: The agent skips
npm installbecause it assumes dependencies are already installed, or configures Tailwind before the Vite project exists.
Once you’re ready to make these runs repeatable, switch to codex exec. It’s designed for automation and CI: it streams progress to stderr and writes only the final result to stdout, which makes runs easier to script, capture, and inspect.
By default, codex exec runs in a restricted sandbox. If your task needs to write files, run it with --full-auto. As a general rule, especially when automating, use the least permissions needed to get the job done.
A basic manual run might look like:
codex exec --full-auto \
'Use the $setup-demo-app skill to create the project in this directory.'This first hands-on pass is less about validating correctness and more about discovering edge cases. Every manual fix you make here, such as adding a missing npm install, correcting the Tailwind setup, or tightening the trigger description, is a candidate for a future eval, so you can lock in the intended behavior before evaluating at scale.
4. Use a small, targeted prompt set to catch regressions early
You don’t need a large benchmark to get value from evals. For a single skill, a small set of 10–20 prompts is enough to surface regressions and confirm improvements early.
Start with a small CSV and grow it over time as you encounter real failures during development or usage. Each row should represent a situation where you care whether the setup-demo-app skill does or does not activate, and what success looks like when it does.
For example, an initial evals/setup-demo-app.prompts.csv might look like this:
id,should_trigger,prompt
test-01,true,"Create a demo app named `devday-demo` using the $setup-demo-app skill"
test-02,true,"Set up a minimal React demo app with Tailwind for quick UI experiments"
test-03,true,"Create a small demo app to showcase the Responses API"
test-04,false,"Add Tailwind styling to my existing React app"Each of these cases is testing something slightly different:
- Explicit invocation (
test-01) This prompt names the skill directly. It ensures that Codex can invokesetup-demo-appwhen asked, and that changes to the skill’s name, description, or instructions don’t break direct usage. - Implicit invocation (
test-02) This prompt describes exactly the scenario the skill targets, setting up a minimal React + Tailwind demo, without mentioning the skill by name. It tests whether the name and description inSKILL.mdare strong enough for Codex to select the skill on its own. - Contextual invocation (
test-03) This prompt adds domain context (the Responses API) but still requires the same underlying setup. It checks that the skill triggers in realistic, slightly noisy prompts, and that the resulting app still matches the expected structure and conventions. - Negative control (
test-04) This prompt should not invokesetup-demo-app. It’s a common adjacent request (“add Tailwind to an existing app”) that can unintentionally match the skill’s description (“React + Tailwind demo”). Including at least oneshould_trigger=falsecase helps catch false positives, where Codex selects the skill too eagerly and scaffolds a new project when the user wanted an incremental change to an existing one.
This mix is intentional. Some evals should confirm that the skill behaves correctly when invoked explicitly; others should check that it activates in real-world prompts where the user never mentions the skill at all.
As you discover misses, prompts that fail to trigger the skill, or cases where the output drifts from your expectations, add them as new rows. Over time, this small CSV becomes a living record of the scenarios the setup-demo-app skill must continue to get right.
Over time, this small dataset becomes a living record of what the skill must continue to get right.
5. Get started with lightweight deterministic graders
This is the core of the evaluation step: use codex exec --json so your eval harness can score what actually happened, not just whether the final output looks right.
When you enable --json, stdout becomes a JSONL stream of structured events. That makes it straightforward to write deterministic checks tied directly to the behavior you care about, for example:
- Did it run
npm install? - Did it create
package.json? - Did it invoke the expected commands, in the expected order?
These checks are intentionally lightweight. They give you fast, explainable signals before you add any model-based grading.
A minimal Node.js runner
A “good enough” approach looks like this:
- For each prompt, run
codex exec --json --full-auto "<prompt>" - Save the JSONL trace to disk
- Parse the trace and run deterministic checks over the events
// evals/run-setup-demo-app-evals.mjs
import { spawnSync } from "node:child_process";
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
import path from "node:path";
function runCodex(prompt, outJsonlPath) {
const res = spawnSync(
"codex",
[
"exec",
"--json", // REQUIRED: emit structured events
"--full-auto", // Allow file system changes
prompt,
],
{ encoding: "utf8" }
);
mkdirSync(path.dirname(outJsonlPath), { recursive: true });
// stdout is JSONL when --json is enabled
writeFileSync(outJsonlPath, res.stdout, "utf8");
return { exitCode: res.status ?? 1, stderr: res.stderr };
}
function parseJsonl(jsonlText) {
return jsonlText
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line));
}
// deterministic check: did the agent run `npm install`?
function checkRanNpmInstall(events) {
return events.some(
(e) =>
(e.type === "item.started" || e.type === "item.completed") &&
e.item?.type === "command_execution" &&
typeof e.item?.command === "string" &&
e.item.command.includes("npm install")
);
}
// deterministic check: did `package.json` get created?
function checkPackageJsonExists(projectDir) {
return existsSync(path.join(projectDir, "package.json"));
}
// Example single-case run
const projectDir = process.cwd();
const tracePath = path.join(projectDir, "evals", "artifacts", "test-01.jsonl");
const prompt =
"Create a demo app named demo-app using the $setup-demo-app skill";
runCodex(prompt, tracePath);
const events = parseJsonl(readFileSync(tracePath, "utf8"));
console.log({
ranNpmInstall: checkRanNpmInstall(events),
hasPackageJson: checkPackageJsonExists(path.join(projectDir, "demo-app")),
});The value here is that everything is deterministic and debuggable.
If a check fails, you can open the JSONL file and see exactly what happened. Every command execution appears as an item.* event, in order. That makes regressions straightforward to explain and fix, which is exactly what you want at this stage.
6. Conduct qualitative checks with Codex and rubric-based grading
Deterministic checks answer “did it do the basics?” but they don’t answer “did it do it the way you wanted?”
For skills like setup-demo-app, many requirements are qualitative: component structure, styling conventions, or whether Tailwind follows the intended configuration. These are hard to capture with basic file existence checks or command counts alone.
A pragmatic solution is to add a second, model-assisted step to your eval pipeline:
- Run the setup skill (this writes code to disk)
- Run a read-only style check against the resulting repository
- Require a structured response that your harness can score consistently
Codex supports this directly via --output-schema, which constrains the final response to a JSON Schema you define.
A small rubric schema
Start by defining a small schema that captures the checks you care about. For example, create evals/style-rubric.schema.json:
{
"type": "object",
"properties": {
"overall_pass": { "type": "boolean" },
"score": { "type": "integer", "minimum": 0, "maximum": 100 },
"checks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"pass": { "type": "boolean" },
"notes": { "type": "string" }
},
"required": ["id", "pass", "notes"],
"additionalProperties": false
}
}
},
"required": ["overall_pass", "score", "checks"],
"additionalProperties": false
}This schema gives you stable fields (overall_pass, score, per-check results) that you can combine, diff, and track over time.
The style-check prompt
Next, run a second codex exec that only inspects the repository and emits a rubric-compliant JSON response:
codex exec \
"Evaluate the demo-app repository against these requirements:
- Vite + React + TypeScript project exists
- Tailwind is configured via @tailwindcss/vite and CSS imports tailwindcss
- src/components contains Header.tsx and Card.tsx
- Components are functional and styled with Tailwind utility classes (no CSS modules)
Return a rubric result as JSON with check ids: vite, tailwind, structure, style." \
--output-schema ./evals/style-rubric.schema.json \
-o ./evals/artifacts/test-01.style.jsonThis is where --output-schema is handy. Instead of free-form text that’s hard to parse or compare, you get a predictable JSON object that your eval harness can score across many runs.
If you later move this eval suite into CI, the Codex GitHub Action explicitly supports passing --output-schema through codex-args, so you can enforce the same structured output in automated workflows.
7. Extending your evals as the skill matures
Once you have the core loop in place, you can extend your evals in the directions that matter most for your skill. Start small, then layer in deeper checks only where they add real confidence.
Some examples include:
- Command count and thrashing: Count
command_executionitems in the JSONL trace to catch regressions where the agent starts looping or re-running commands. Token usage is also available inturn.completedevents. - Token budget: Track
usage.input_tokensandusage.output_tokensto spot accidental prompt bloat and compare efficiency across versions. - Build checks: Run
npm run buildafter the skill completes. This acts as a stronger end-to-end signal and catches broken imports or incorrectly configured tooling. - Runtime smoke checks: Start
npm run devand hit the dev server withcurl, or run a lightweight Playwright check if you already have one. Use this selectively. It adds confidence but costs time. - Repository cleanliness: Ensure the run generates no unwanted files and that
git status --porcelainis empty (or matches an explicit allow list). - Sandbox and permission regressions: Verify the skill still works without escalating permissions beyond what you intended. Least-privilege defaults matter most once you automate.
The pattern is consistent: begin with fast checks that explain behavior, then add slower, heavier checks only when they reduce risk.
8. Key takeaways
This small setup-demo-app example shows the shift from “it feels better” to “proof”: run the agent, record what happened, and grade it with a small set of checks. Once that loop exists, every tweak becomes easier to confirm, and every regression becomes clear. Here are the key takeaways:
- Measure what matters. Good evals make regressions clear and failures explainable.
- Start from a checkable definition of done. Use
$skill-creatorto bootstrap, then tighten the instructions until success is unambiguous. - Ground evals in behavior. Capture JSONL with
codex exec --jsonand write deterministic checks againstcommand_executionevents. - Use Codex where rules fall short. Add a structured, rubric-based pass with
--output-schemato grade style and conventions reliably. - Let real failures drive coverage. Every manual fix is a signal. Turn it into a test so the skill keeps getting it right.
