Codex のようなエージェントのスキルを繰り返し改良していると、本当に改善できているのか、単に振る舞いを変えているだけなのか、判断が難しくなります。あるバージョンは速く感じられ、別のバージョンは安定しているように見えても、いつの間にかリグレッションが入り込みます。スキルが呼び出されない、必要な手順が省略される、余計なファイルが残るといった問題です。
スキルの本質は、LLM 向けのプロンプトと指示を整理したものです。スキルを継続的に改善する最も確実な方法は、LLM アプリケーションで使うほかのプロンプトと同じように評価することです。
評価 は、英語の evaluations を略して evals とも呼ばれ、モデルの出力と、その出力に至る手順が意図どおりかどうかを確認します。「よくなった気がするか」という印象や感覚に頼るのではなく、次のような具体的な問いで確認できます。
- エージェントはスキルを呼び出しましたか?
- 想定したコマンドを実行しましたか?
- 重視する規約に沿った出力を生成しましたか?
具体的には、評価は「プロンプト → 実行記録(トレースと成果物)→ 少数のチェック → 継続的に比較できるスコア」という流れで行います。
実際、エージェントスキルの評価は軽量なエンドツーエンドテストによく似ています。エージェントを実行し、何が起きたかを記録して、少数のルールに基づいて結果を採点します。
この記事では、Codex でこの評価を行うためのわかりやすい手順を紹介します。まず成功の定義を決め、次に決定論的なチェックとルーブリックに基づく採点を加えることで、改善もリグレッションも明確に把握できるようにします。
1. スキル作成前の成功条件の定義
スキルそのものを書く前に、何をもって「成功」とするかを、実際に測定できる形で書き出します。チェック項目をいくつかのカテゴリに分けると整理しやすくなります。
- 成果の目標: タスクは完了しましたか?アプリは動作しますか?
- プロセスの目標: Codex はスキルを呼び出し、想定したツールと手順に従いましたか?
- スタイルの目標: 出力は指定した規約に従っていますか?
- 効率の目標: 無駄な試行錯誤(不要なコマンドの実行や過剰なトークン消費など)をせずに目標を達成しましたか?
項目数は少なく抑え、必ず合格すべきチェックに絞ります。目的は、あらゆる好みを最初からルール化することではなく、特に重視する振る舞いを押さえることです。
たとえば、この記事ではデモアプリをセットアップするスキルを評価します。npm install を実行したか、package.json を作成したか、といった具体的なチェックを行います。さらに、スタイルに関する構造化されたルーブリックを組み合わせて、規約への準拠やレイアウトも評価します。
この組み合わせには意図があります。最後に一度だけ合否を判定するのではなく、特定のリグレッションを早期に見つけるための、迅速で的を絞った指標が必要だからです。
2. スキルの作成
Codex のスキルは、SKILL.md ファイルを含むディレクトリです。このファイルには YAML フロントマター(name、description)と、その後にスキルの振る舞いを定義する Markdown 形式の指示を記述します。必要に応じてリソースやスクリプトも含めます。名前と説明は、一見するよりも重要です。Codex がスキルを 呼び出すかどうか 、そして SKILL.md の残りの内容をエージェントのコンテキストに いつ 追加するかを判断する主な手がかりとなります。名前や説明が曖昧だったり、内容を詰め込みすぎたりすると、スキルが安定して呼び出されなくなります。
最も手早く始めるには、Codex に組み込まれたスキルクリエイターを使います(これ自体もスキルです)。作成手順を案内してくれます。
$skill-creator
スキルクリエイターは、スキルの役割、呼び出すべきタイミング、指示のみで構成するかスクリプトを併用するかを質問します(基本的には指示のみの構成を推奨します)。スキルの作成について詳しくは、ドキュメントをご覧ください。
スキルのサンプル
この記事では、あえて最小限の例を使います。小さな React デモアプリを、予測可能で再現性のある方法でセットアップするスキルです。
このスキルでは、次の処理を行います。
- Vite の React + TypeScript テンプレートを使ったプロジェクトのひな形の作成
- 公式の Vite プラグイン方式による Tailwind CSS の設定
- 最小限で一貫したファイル構成の徹底
- 成功したかを容易に評価できる、明確な「完了の定義」の設定
以下の簡潔な草案を、次のいずれかに貼り付けて使えます。
.codex/skills/setup-demo-app/SKILL.md(リポジトリ単位)、または~/.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
このサンプルスキルでは、意図的に方針を明確に定めています。明確な制約がなければ、具体的に評価できるものがないためです。
3. 手動でのスキル呼び出しによる暗黙の前提の発見
スキルの呼び出しは SKILL.md の 名前 と 説明 に大きく左右されるため、まず setup-demo-app スキルが想定したタイミングで呼び出されるかを確認します。
初期段階では、実際のリポジトリや一時的な作業ディレクトリで、/skills スラッシュコマンドを使うか、$ プレフィックスを付けてスキルを指定し、明示的に呼び出して問題が起きる箇所を観察します。ここで、スキルがまったく呼び出されない、必要以上に呼び出される、実行されても意図した手順から外れるといった問題を洗い出します。
この段階では、速度や完成度の最適化は目指しません。次のような、スキルが暗黙に置いている前提を探します。
-
呼び出しに関する前提:「簡単な React デモをセットアップしてください」のように、
setup-demo-appを呼び出す はず のプロンプトで呼び出されないケースや、「Tailwind のスタイルを追加してください」といった、より一般的なプロンプトで意図せず呼び出されるケース -
環境に関する前提:スキルが空のディレクトリでの実行を前提としているケースや、
npmが利用可能で、ほかのパッケージマネージャーより優先されると想定しているケース -
実行に関する前提:エージェントが依存関係はインストール済みだと想定して
npm installを省略するケースや、Vite プロジェクトの作成前に Tailwind を設定するケース
こうした実行を繰り返し再現する準備ができたら、codex exec に切り替えます。自動化や CI 向けに設計されており、進捗を stderr にストリーミング出力し、最終結果だけを stdout に書き込みます。そのため、実行のスクリプト化、記録、確認が容易になります。
デフォルトでは、codex exec は制限付きのサンドボックスで実行されます。タスクでファイルへの書き込みが必要な場合は、--full-auto を付けて実行します。原則として、特に自動化する場合は、作業に必要な最小限の権限を使ってください。
基本的な手動実行の例は次のとおりです。
codex exec --full-auto \
'Use the $setup-demo-app skill to create the project in this directory.'
この最初の試行では、正しさの検証よりもエッジケースの発見を重視します。省略された npm install の追加、Tailwind 設定の修正、呼び出し条件の説明の明確化など、ここで手動で行う修正はすべて、今後の評価項目の候補になります。これにより、大規模な評価に進む前に、意図した振る舞いを確実に維持できるようにします。
4. 少数の的を絞ったプロンプトによるリグレッションの早期発見
評価を役立てるために、大規模なベンチマークは必要ありません。単一のスキルであれば、10~20 個のプロンプトがあれば、リグレッションの発見や改善の確認を早期に行えます。
小さな CSV から始め、開発中や利用中に実際の失敗に遭遇するたびに、少しずつ追加していきます。各行には、setup-demo-app スキルが 呼び出されるべきか 、 呼び出されるべきでないか を確認したい状況と、呼び出された場合の成功条件を記述します。
たとえば、最初の evals/setup-demo-app.prompts.csv は次のようになります。
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"
これらのケースでは、それぞれ少しずつ異なる点をテストしています。
-
明示的な呼び出し(
test-01)
このプロンプトでは、スキルを名前で直接指定します。依頼されたときに Codex がsetup-demo-appを呼び出せることと、スキルの名前、説明、指示を変更しても、直接指定による利用が損なわれないことを確認します。 -
暗黙的な呼び出し(
test-02)
このプロンプトでは、スキル名を挙げずに、最小限の React + Tailwind デモのセットアップという、スキルが対象とするシナリオを そのまま 記述します。SKILL.mdの名前と説明が、Codex が自らスキルを選択できるほど明確かどうかをテストします。 -
文脈を含む呼び出し(
test-03)
このプロンプトでは、対象領域のコンテキスト(Responses API)を追加していますが、必要な基本セットアップは同じです。多少のノイズを含む現実的なプロンプトでもスキルが呼び出され、作成されたアプリが想定した構成や規約に沿っていることを確認します。 -
陰性対照(
test-04)
このプロンプトでは、setup-demo-appを呼び出しては いけません 。「既存のアプリに Tailwind を追加してください」という、よくある関連した依頼ですが、スキルの説明(「React + Tailwind デモ」)に意図せず一致する可能性があります。should_trigger=falseのケースを少なくとも 1 つ含めることで、 偽陽性を検出しやすくなります。これは、ユーザーが既存のプロジェクトへの部分的な変更を求めているのに、Codex が過剰にスキルを選択し、新しいプロジェクトのひな形を作ってしまうケースです。
この組み合わせには意図があります。明示的に呼び出されたときの正しい動作を確認する評価と、ユーザーがスキルにまったく言及していない実際のプロンプトでも呼び出されることを確認する評価の両方が必要です。
スキルが呼び出されないプロンプトや、出力が期待から外れるケースなどの問題が見つかったら、新しい行として追加します。こうして、この小さな CSV は、setup-demo-app スキルが今後も正しく対応すべきシナリオを蓄積し、更新し続ける記録になります。
こうして、この小さなデータセットは、スキルが今後も正しく対応すべきことを蓄積し、更新し続ける記録になります。
5. 軽量な決定論的評価器の導入
評価工程の核となるのが、codex exec --json の使用です。これにより、最終出力が正しく見えるかどうかだけでなく、 実際に何が起きたかを評価ハーネスで採点できます。
--json を有効にすると、stdout は構造化されたイベントの JSONL ストリームになります。これにより、確認したい振る舞いに直接対応する決定論的なチェックを簡単に記述できます。たとえば、次のような項目です。
npm installを実行しましたか?package.jsonを作成しましたか?- 想定したコマンドを、想定した順序で実行しましたか?
これらのチェックは、意図的に軽量にしています。モデルによる採点を追加する前に、理由を説明できる結果をすばやく得られます。
最小限の Node.js ランナー
まずは、次のような方法で十分です。
- 各プロンプトについて
codex exec --json --full-auto "<prompt>"を実行 - 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", // 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")),
});
この方法の利点は、すべてが 決定論的で、デバッグできることです。
チェックに失敗した場合は、JSONL ファイルを開けば、何が起きたのかを正確に確認できます。すべてのコマンド実行が item.* イベントとして順番に記録されます。そのため、リグレッションの原因を説明し、修正しやすくなります。この段階で必要なのは、まさにそれです。
6. Codex とルーブリックに基づく採点による定性チェック
決定論的なチェックでは、 「基本的なことを実行したか?」 には答えられますが、 「意図した方法で実行したか?」には答えられません。
setup-demo-app のようなスキルでは、コンポーネントの構造、スタイリングの規約、Tailwind が意図した構成になっているかなど、多くの要件が定性的です。これらは、単純なファイルの存在チェックやコマンドの実行回数だけでは捉えきれません。
実用的な解決策は、評価パイプラインの第 2 段階として、モデルを使ったチェックを追加することです。
- セットアップ用スキルを実行(コードがディスクに書き込まれます)
- 生成されたリポジトリに対して 読み取り専用のスタイルチェック を実行
- ハーネスで一貫して採点できる 構造化された応答 を要求
Codex は --output-schema でこの仕組みを直接サポートしています。これにより、最終応答の形式を、定義した JSON Schema に制約できます。
小規模なルーブリックスキーマ
まず、重視するチェック項目をまとめた小規模なスキーマを定義します。たとえば、次のように 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
}
このスキーマによって、固定のフィールド(overall_pass、score、チェックごとの結果)が得られます。これらを組み合わせたり、差分を比較したり、継続的に追跡したりできます。
スタイルチェック用プロンプト
次に、2 回目の codex exec を実行します。ここでは リポジトリの検査だけ を行い、ルーブリックに準拠した 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
ここで --output-schema が役立ちます。解析や比較が難しい自由形式のテキストではなく、形式が予測できる JSON オブジェクトが得られるため、評価ハーネスで多数の実行結果を採点できます。
後でこの評価スイートを CI に移す場合も、Codex GitHub Action は codex-args 経由での --output-schema の指定を明示的にサポートしているため、自動化されたワークフローで同じ構造化出力を必須にできます。
7. スキルの成熟に合わせた評価の拡張
基本的なサイクルが整ったら、そのスキルにとって特に重要な観点で評価を拡張できます。小さく始めて、結果への確信が実際に高まる箇所にだけ、より詳細なチェックを追加していきます。
たとえば、次のようなチェックが考えられます。
-
コマンドの実行回数と無駄な試行の繰り返し: JSONL トレース内の
command_execution項目を数え、エージェントがループに陥ったり、コマンドを繰り返し実行したりするリグレッションを検出します。トークン使用量もturn.completedイベントから取得できます。 -
トークン予算:
usage.input_tokensとusage.output_tokensを追跡し、意図しないプロンプトの肥大化を見つけ、バージョン間で効率を比較します。 -
ビルドチェック: スキルの完了後に
npm run buildを実行します。これにより、処理全体の動作をより確実に確認でき、インポートの不具合やツールの構成ミスを検出できます。 -
実行時のスモークチェック:
npm run devで開発サーバーを起動してcurlでアクセスするか、軽量な Playwright チェックがすでにある場合はそれを実行します。動作への確信は高まりますが時間がかかるため、必要な場面に絞って使います。 -
リポジトリのクリーンな状態: 実行によって不要なファイルが生成されていないことと、
git status --porcelainの出力が空であること(または明示した許可リストに一致すること)を確認します。 -
サンドボックスと権限のリグレッション: 意図した範囲を超えて権限を昇格させなくても、スキルが引き続き動作することを確認します。自動化すると、デフォルトを最小権限にすることが特に重要になります。
基本的な進め方は一貫しています。まず、動作を説明できる高速なチェックから始め、リスクの低減につながる場合にだけ、時間や負荷のかかるチェックを追加します。
8. 重要なポイント
この小さな setup-demo-app の例は、「良くなった気がする」から「根拠を示せる」状態へ移る方法を示しています。エージェントを実行し、何が起きたかを記録して、少数のチェックで採点します。このサイクルができれば、一つひとつの調整の効果を確認しやすくなり、リグレッションも明確になります。重要なポイントは次のとおりです。
- 重要なことを測定します。 適切な評価によって、リグレッションが明確になり、失敗の理由を説明できるようになります。
- 検証可能な完了条件の定義から始めます。
$skill-creatorで土台を作り、成功かどうかを明確に判断できるようになるまで指示を具体化します。 - 実際の動作に基づいて評価します。
codex exec --jsonで JSONL を記録し、command_executionイベントに対する決定論的なチェックを作成します。 - ルールだけでは対応しきれない部分に Codex を使います。
--output-schemaを使って、ルーブリックに基づく構造化された評価の工程を追加し、スタイルや規約への準拠を安定して採点します。 - 実際の失敗をもとに評価範囲を広げます。 手作業での修正は、どれも改善の手がかりです。それをテストに変えることで、スキルが引き続き正しく動作するようにします。