Codex のようなエージェントのスキルに手を加えるとき、その変更で本当に改善したのか、単に挙動が変わっただけなのかを見極めるのは簡単ではありません。あるバージョンは速く感じられ、別のバージョンは安定しているように見えます。ところが、いつの間にかスキルが起動しなくなったり、必要な手順を飛ばしたり、余計なファイルを残したりすることがあります。
スキルの本質は、LLM 向けにプロンプトと指示を整理してまとめたものです。スキルを継続的に改善する最も確実な方法は、LLM アプリケーションで使う他のプロンプトと同じように評価することです。
Evals(evaluations の略、評価)では、モデルの出力と、そこに至るまでの手順が意図どおりかを確認します。「前よりよくなった気がする」といった感覚に頼らず、次のような具体的な問いに答えられます。
- エージェントはスキルを呼び出しましたか?
- 想定したコマンドを実行しましたか?
- 重視している規約に沿った成果物を生成しましたか?
具体的には、プロンプト → 実行記録(トレース + 成果物)→ 少数のチェック項目 → 継続的に比較できるスコア、という流れで評価します。
実際、エージェントのスキル評価は軽量なエンドツーエンドテストによく似ています。エージェントを実行して何が起きたかを記録し、少数のルールに照らして結果を採点します。
この記事では、Codex でこの評価を行う手順を紹介します。まず成功条件を定め、次に決定論的なチェックと評価基準(ルーブリック)に基づく採点を加えることで、改善とリグレッションの両方を把握できるようにします。
1. スキル作成前の成功条件の定義
スキルを書く前に、何をもって「成功」とするかを、実際に測定できる形で書き出します。チェック項目を次のように分けると整理しやすくなります。
- 成果の目標: タスクは完了しましたか? アプリは動作しますか?
- 手順の目標: Codex はスキルを呼び出し、想定したツールを使って指定の手順に従いましたか?
- スタイルの目標: 成果物は指定した規約に沿っていますか?
- 効率の目標: 不要なコマンドの実行や過剰なトークン消費など、無駄な処理をせずに完了しましたか?
項目は、必ず満たすべき条件に絞ります。最初からすべての好みをルールに落とし込む必要はありません。特に重視する挙動を押さえておきます。
この記事では、デモアプリをセットアップするスキルを評価します。たとえば「npm install を実行したか」「package.json を作成したか」は具体的に判定できます。これらのチェックと、スタイルに関するルーブリックを組み合わせて、規約やレイアウトも評価します。
こうして項目を組み合わせるのには理由があります。最後に一度だけ合否を判定するよりも、項目ごとに素早く結果が得られる方が、どこでリグレッションが起きたかを早期に把握できます。
2. スキルの作成
Codex のスキルは、SKILL.md ファイルを含むディレクトリです。ファイルの冒頭には YAML フロントマター(name、description)を置き、続けてスキルの挙動を定める指示を Markdown で記述します。必要に応じてリソースやスクリプトも含められます。名前と説明は、見た目以上に重要です。Codex は主にこの 2 つを手がかりに、そもそもスキルを呼び出すかどうか、そしていつ 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.
---
## 使用する場面
UI を手早く試したり、問題を再現したりするために、新しいデモアプリが必要な場合に使います。
## 作成するアプリ
Vite で React と TypeScript を使ったアプリを作成し、Tailwind を設定してください。構成は必要最小限にしてください。
セットアップ後のプロジェクト構成:
- src/
- main.tsx(エントリーポイント)
- App.tsx(UI のルートコンポーネント)
- components/
- Header.tsx
- Card.tsx
- index.css(Tailwind のインポート)
- index.html
- package.json
スタイル要件:
- コンポーネントは TypeScript で記述
- 関数コンポーネントのみを使用
- スタイルには Tailwind のクラスを使用(CSS モジュールは使用不可)
- 追加の UI ライブラリは使用不可
## 手順
1. Vite の React TS テンプレートでプロジェクトのひな形を生成します。
npm create vite@latest demo-app -- --template react-ts
2. 依存関係をインストールします。
cd demo-app
npm install
3. Vite プラグインを使って Tailwind をインストールし、設定します。
- npm install tailwindcss @tailwindcss/vite
- vite.config.ts に Tailwind プラグインを追加します。
- src/index.css の内容を次の 1 行に置き換えます。
@import "tailwindcss";
4. 必要最小限の UI を実装します。
- Header:アプリのタイトルと短いサブタイトルを表示
- Card:再利用可能なカード用コンテナとして実装
- App:Header と Card 2 枚を表示し、各 Card に仮のテキストを配置
## 完了条件
- npm run dev で開発サーバーが正常に起動すること
- package.json が存在すること
- src/components/Header.tsx と src/components/Card.tsx が存在すること
このサンプルスキルでは、実装方針をあえて細かく指定しています。制約が明確でなければ、評価のよりどころがないためです。
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 \
'$setup-demo-app スキルを使って、このディレクトリにプロジェクトを作成してください。'
最初の手動実行では、正しさの検証よりも、想定から外れるケースの発見に重点を置きます。抜けていた npm install の追加、Tailwind 設定の修正、起動条件の説明の明確化など、ここで手動で直した箇所はすべて、今後の評価項目の候補です。大規模な評価に進む前に、意図した挙動をテストで確かめられるようにしておきます。
4. 少数のプロンプトによるリグレッションの早期検出
評価を役立てるために、大規模なベンチマークは必要ありません。スキルが 1 つなら、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
"--sandbox",
"workspace-write", // Allow changes inside the example repository
prompt,
],
{ encoding: "utf8" }
);
if (res.error) throw res.error;
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.completed" &&
e.item?.type === "command_execution" &&
e.item.status === "completed" &&
e.item.exit_code === 0 &&
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";
const { exitCode, stderr } = runCodex(prompt, tracePath);
if (exitCode !== 0) {
throw new Error(`Codex exited with status ${exitCode}: ${stderr}`);
}
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 スキーマに準拠させられます。
小規模なルーブリック用スキーマ
まず、確認したい項目をまとめた小さなスキーマを定義します。たとえば、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 \
"demo-app リポジトリが次の要件を満たすか評価してください。
- Vite、React、TypeScript を使ったプロジェクトが存在すること
- @tailwindcss/vite を使って Tailwind が設定され、CSS で tailwindcss をインポートしていること
- src/components に Header.tsx と Card.tsx があること
- コンポーネントはすべて関数コンポーネントで、スタイルには Tailwind のユーティリティクラスを使い、CSS モジュールを使っていないこと
チェック ID には vite、tailwind、structure、style を使い、ルーブリックに沿った評価結果を JSON で返してください。" \
--output-schema ./evals/style-rubric.schema.json \
-o ./evals/artifacts/test-01.style.json
ここで役立つのが --output-schema です。解析や比較が難しい自由記述のテキストの代わりに、構造が決まった JSON オブジェクトが得られます。評価ハーネスを使えば、多数の実行結果を同じ方法で採点できます。
後からこの評価スイートを CI に組み込む場合も、Codex GitHub Action は --output-schema を codex-args 経由で渡す方法を明示的にサポートしています。自動化したワークフローでも、同じ形式の構造化出力を必須にできます。
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を指定し、ルーブリックに沿って構造化された結果を返す評価を加えると、スタイルや規約への適合を一貫して採点できます。 - 実際の失敗をもとにテスト範囲を広げます。 手動で修正した箇所は、チェックを追加する手がかりです。テストに組み込み、その後もスキルが正しく動作することを確かめます。