TL;DR
GPT-6のSolとLunaを使い、同じ仕様のWebアプリを3つの構成で3回ずつ作らせました。
- 計画とレビューをSol、実装をLunaにした分業構成は、今回の実験では隠しE2Eテスト合格率100%を、全部Solの47%のコストで達成した
- ただし分業は3構成の中で最も遅かった。主にSolのレビューと、その指摘を受けた修正の往復に時間がかかっている
- 全部Lunaは圧倒的に安く、今回の条件では最速だった。一方で、3回中1回で小さな仕様逸脱を取りこぼした
今回の結果だけを見ると、品質とコストのバランスでは分業構成が有力でした。
ただし、ここで言いたいのは「計画とレビューはSol、実装はLunaにすればよい」ということではありません。
タスクによって求められる品質、コスト、速度は異なります。重要なのは、それぞれのモデルの特性を踏まえて、どの工程にどのモデルを使うかを選ぶことだと考えています。
今回の分業構成は、その一例です。
実験の概要
比較した構成
| 構成 | 計画 | 実装・修正 | レビュー |
|---|---|---|---|
| D:全部Sol | Sol | Sol | Sol |
| E:全部Luna | Luna | Luna | Luna |
| F:分業 | Sol | Luna | Sol |
どの構成も工程は同じで、変えたのはモデルだけです。
- 計画:仕様書を読み、実装計画
PLAN.mdを書く - 実装:計画に沿ってアプリとテストを書く
- レビュー:仕様と実装を照合し、指摘を
REVIEW.mdに書く - 修正:指摘を直し、3に戻る(レビューは最大2回まで)
工程ごとに codex exec -m でモデルを指定し、スクリプトから1つずつ呼び出しました。工程間の引き継ぎはファイルだけで、会話の文脈は持ち越していません。Codexのサブエージェント機能を使わなかったのは、サブエージェントのモデル指定が効かない不具合が報告されていたためです。
題材
日用品の在庫管理Webアプリ「StockKeeper」(Vite + React + TypeScript)です。在庫カードに「残り◯日」と「在庫少」を表示する1ページアプリです。
仕様には、読み飛ばすと間違えやすい細部をわざと入れています。
- 残り日数は直近30日の平均から計算し、平均は丸めずに使う(丸めると42日が43日になる)
- 30日窓の境界(29日前は含み、30日前は含まない)
- 在庫少バッジは「隠す」のではなく「DOMに出さない」
- 表示形式は
残り45日に固定
評価方法
- 隠しE2Eテスト20件(Playwright):モデルには見せない受け入れテストで、画面を実際に操作して採点
-
ブラインドレビュー:構成を伏せたスクショとコードを、別社のモデル(Claude)が次の6観点で採点(各5点、30点満点)
- 見た目の分かりやすさ(在庫少・もうすぐ切れるが一目で分かるか)
- スマホ幅での使いやすさ
- 仕様への忠実さ(書かれていない機能の追加も減点)
- コードの読みやすさ(命名、コンポーネント分割、重複のなさ)
- 自作テストの質(重要なケースを押さえているか)
- 保守しやすさ(次の機能追加がしやすいか)
- コスト・時間:Codexのログのトークン数×単価で試算、時間は工程ごとに計測
実行環境
| 項目 | 内容 |
|---|---|
| ツール | codex-cli 0.157.1 |
| OS | macOS 27.0 |
| モデル | gpt-6-sol / gpt-6-luna |
| reasoning effort | high(全工程共通) |
| 回数 | 各構成3回、計9回(2026年9月28日実行) |
| 課金 | ChatGPTのサブスクリプション。コストはAPI料金に換算した試算値 |
インプット(モデルに渡したもの)
再現できるよう、モデルに渡したものをすべて載せます。
仕様書(SPEC.md)
SPEC.md の全文を開く
# StockKeeper 仕様書
日用品の在庫と消費ペースを管理し、「そろそろ買うべきもの」が一目で分かる Web アプリ。
1ページで完結し、データはブラウザの localStorage に保存する(サーバー不要)。
## 1. 技術スタック(固定)
- Vite + React + TypeScript
- テスト: Vitest
- UI ライブラリの追加は自由(ただし `npm install` だけで動くこと)
## 2. スクリプト
| コマンド | 動作 |
|---|---|
| `npm start` | 本番ビルドを行い、環境変数 `PORT`(既定 4173)のポートで配信する。ポートが使用中なら失敗すること(例: `vite build && vite preview --port $PORT --strictPort`) |
| `npm test` | 自作テストを実行する |
| `npm run typecheck` | `tsc --noEmit` 相当の型チェック |
## 3. 「今日」の扱い
- URL クエリ `?today=YYYY-MM-DD` があれば、その日付を「今日」とする
- なければ端末のローカル日付を「今日」とする
## 4. データ
アイテムは次の情報を持つ。保存先は localStorage のキー `stockkeeper:v1`(形式は自由)。
再読み込みしてもデータが残ること。
| 項目 | 制約 |
|---|---|
| 名前 | 前後の空白を除去して 1〜50 文字。大文字小文字を区別せず重複不可 |
| カテゴリ | `kitchen`(キッチン)/ `laundry`(洗濯)/ `bath`(バス)/ `other`(その他) |
| 在庫数 | 0 以上の整数 |
| しきい値 | 0 以上の整数。在庫数がこの値以下なら「在庫少」 |
| 消費履歴 | 「使った」を押した日付と量(1回につき 1) |
## 5. 残り日数の計算
- 集計期間は今日を含む直近 30 日(今日の 29 日前〜今日)
- 平均消費量 = 期間内の消費量の合計 ÷ 30
- 残り日数 = `floor(在庫数 ÷ 平均消費量)`。平均は丸めずに使う
- 期間内に消費がなければ残り日数は「なし」
## 6. 画面
以下の `data-testid` は自動テストで使うため、**必ず指定どおりに付けること**。見た目のデザインは自由。
### 6.1 追加フォーム(常に表示)
| 要素 | data-testid | 備考 |
|---|---|---|
| 名前入力 | `new-name` | |
| カテゴリ選択 | `new-category` | `<select>`。option の value は `kitchen` / `laundry` / `bath` / `other`、表示は日本語名。既定は `kitchen` |
| 在庫数入力 | `new-quantity` | 空欄なら 0 |
| しきい値入力 | `new-threshold` | 空欄なら 1 |
| 追加ボタン | `add-button` | |
| エラー表示 | `form-error` | 入力が不正なときだけ表示し、アイテムは追加しない |
- 不正な入力: 名前が空または 51 文字以上、名前の重複、在庫数やしきい値が負の数・小数
- 追加に成功したら、エラー表示を消し、名前入力を空にする
### 6.2 在庫一覧
| 要素 | data-testid | 備考 |
|---|---|---|
| カテゴリ絞り込み | `filter-category` | `<select>`。value は `all`(既定)と4カテゴリ |
| 並び替え | `sort` | `<select>`。value は `name`(既定)/ `daysLeft` |
| アイテムカード | `item-card` | 属性 `data-item-name` に名前を入れる |
| 空の表示 | `empty-state` | 表示対象のアイテムが0件のときだけ表示 |
カードの中身:
| 要素 | data-testid | 表示内容 |
|---|---|---|
| 名前 | `item-name` | 名前 |
| 在庫数 | `item-quantity` | 数字のみ(例: `3`) |
| 残り日数 | `item-days-left` | `残り45日` の形式。なしのときは `—` |
| 在庫少バッジ | `item-low-badge` | `在庫少`。在庫少のときだけ DOM に出す |
| 使ったボタン | `item-consume` | 在庫を1減らし、今日の日付で消費を記録。在庫0のときは無効(disabled) |
| 買い足したボタン | `item-restock` | 在庫を1増やす |
| 削除ボタン | `item-delete` | 確認なしで即削除。消費履歴も消す |
並び順:
- `name`: 名前を小文字にした昇順
- `daysLeft`: 残り日数の昇順。なしは最後。同じなら名前順
開発ルール(AGENTS.md)
全構成共通で、作業フォルダに置いたルールです。
AGENTS.md の全文を開く
# 開発ルール(全構成共通)
- 仕様は SPEC.md がすべて。書かれていないことを勝手に追加しない
- 技術スタックと data-testid は SPEC.md の指定から変えない
- `npm start` / `npm test` / `npm run typecheck` が必ず動く状態で終える
- 自作テストは Vitest で書く。テストを消したり skip して通すことは禁止
- 人間への質問はできない。曖昧な点は自分で判断し、理由を DECISIONS.md に1行で記録する
- `node_modules` と `dist` はコミットしない(.gitignore を用意する)
工程ごとのプロンプト
計画(plan)
あなたは設計担当です。SPEC.md と AGENTS.md を精読し、実装計画を PLAN.md に書いてください。コードは書かないでください。
PLAN.md に書くこと:
- ディレクトリ構成とファイルごとの責務
- 状態と localStorage の保存形式
- 画面の構成(コンポーネント分割)と data-testid の割り当て
- 残り日数・並び順の計算方法
- 仕様上まちがえやすい点の一覧(境界値、丸め、並び順、表示・非表示など)
- 自作テストで確認すべきケースの一覧
- 見た目の方針(配色、レイアウト、スマホ幅での見え方)
実装(implement)
あなたは実装担当です。SPEC.md、AGENTS.md、PLAN.md に従ってアプリを実装し、Vitest の自作テストも書いてください。
終了前に `npm install`、`npm test`、`npm run typecheck`、`npm run build` を実行し、すべて成功させてください。
レビュー(review)
あなたはレビュー担当です。SPEC.md の各項目と実装を1つずつ照合し、仕様違反・バグ・テスト不足を REVIEW.md に書いてください(既存の REVIEW.md は上書き)。
各指摘には「該当箇所」「仕様のどこに反するか」「修正方針」を書きます。必要なら `npm test` や `npm run build` を実行して確かめてください。
コードは修正しないでください。
指摘がなければ REVIEW.md の1行目に `LGTM` とだけ書いてください。
修正(fix)
あなたは実装担当です。REVIEW.md の指摘をすべて修正してください。SPEC.md と AGENTS.md のルールは引き続き守ってください。
終了前に `npm test`、`npm run typecheck`、`npm run build` を実行し、すべて成功させてください。
実行コマンド
工程ごとに、次のようにモデルを指定して呼び出しています。
codex exec -m gpt-6-sol -c 'model_reasoning_effort="high"' \
--json --skip-git-repo-check \
"$(cat prompts/plan.md)"
隠しテストの例
モデルには見せていない受け入れテストの1つです。丸めずに計算しているかを確かめます。
test('平均は丸めずに使う', async ({ page }) => {
await open(page, '2026-09-20');
await add(page, 'Round', { quantity: 17, threshold: 0 });
await consume(page, 'Round', 7);
await open(page);
await expect(days(page, 'Round')).toHaveText('残り42日'); // 0.23に丸めると43になる
});
実行前の予想
実験を回す前に、単価と各モデルの位置づけから次のように予想していました。
| 構成 | 品質(E2E合格率) | コスト | 時間 |
|---|---|---|---|
| D:全部Sol | 最も高い(90〜100%) | 最も高い | 中くらい |
| E:全部Luna | 最も低く、ばらつきも大きい | 桁違いに安い | 短い〜中くらい |
| F:分業 | DとEの間。Dに近づけるかが焦点 | Eより高いが、Dよりかなり安い | 最も長くなりやすい |
あわせて、モデルの差が出やすいのは次のような「仕様の細部」だろうと考えていました。
- 平均を丸めずに残り日数を計算する
- 30日窓の境界(29日前は含み、30日前は含まない)
- 在庫少バッジを「隠す」のではなく「DOMに出さない」
- 在庫0で「使った」ボタンを無効にする
結果
| 構成 | E2E合格率 | コスト(USD) | 時間(分) | ブラインド評価 |
|---|---|---|---|---|
| D:全部Sol | 100% | 1.57 | 20.2 | 23.0 / 30 |
| E:全部Luna | 91.7% | 0.06 | 14.5 | 22.3 / 30 |
| F:分業 | 100% | 0.74 | 25.1 | 23.7 / 30 |
※ 各構成3回の平均。ビルド成功率と型エラー数は全構成で100%・0件でした。
予想の答え合わせ
| 項目 | 予想 | 結果 | 判定 |
|---|---|---|---|
| Dの品質 | 最も高い | 100% | 的中 |
| Eの品質 | 最も低く、ばらつく | 91.7%(3回中1回だけ不合格) | 的中 |
| Fの品質 | DとEの間 | 100%(Dと同じ) | 良い方に外れ |
| コスト | D>F>E | 1.57 > 0.74 > 0.06 | 的中 |
| 時間 | Fが最も長い | F 25.1分が最長 | 的中 |
| 落ちるテスト | 丸めや30日窓などの計算 | 計算は全問正解。落ちたのは表示形式だけ | 外れ |
外れたのは2点です。分業の品質が全部Solに並んだこと、そして落ちた原因が「計算の落とし穴」ではなく「表示に足したアイコン1文字」だったことです。どちらも考察で詳しく見ていきます。
考察
1. 分業は隠しE2Eテスト100%を、全部Solの半額以下で達成した
Fは3回とも隠しE2Eテスト20件に全問合格し、平均コストはDの47%でした。言い換えると、全部Solに比べて約53%のコスト削減です。
ブラインド評価はDが23.0点、Eが22.3点、Fが23.7点で、3構成とも大きな差はありませんでした。Fが最も高い値ではあるものの、差は1〜2点程度であり、評価者が1モデルのみであることも踏まえると、順位そのものには大きな意味を持たせない方がよさそうです。
今回の結果でより重要なのは、FがDと同じく隠しE2Eテストを3回とも全問通過しながら、コストを半分以下に抑えられたことです。今回のタスクでは、強いモデルを全工程に使うのではなく、計画・レビューに集中して使う構成がうまく機能しました。
2. 分業のコストの約9割はSol
Fのコストをモデル別に分けると、次のようになりました(3回の合計)。
| モデル | 担当 | 時間(分) | コスト(USD) |
|---|---|---|---|
| Sol | 計画・レビュー | 31.6 | 2.00 |
| Luna | 実装・修正 | 43.8 | 0.22 |
実装・修正はLunaが担当していますが、コストの約9割は「考える」「確かめる」側のSolにかかっています。
今回の構成でさらにコストを下げるなら、実装モデルを変えるより、まずSolが担当する計画・レビュー側の使い方を見直す方が影響は大きそうです。特にレビューは、回数を減らす、観点を仕様違反に絞る、重要度の低い改善提案を抑える、といった工夫で時間とコストの両方を削れる可能性があります。
3. 分業が一番遅い主因は、Solレビューとその修正往復
Fは3回とも最後までLGTMになりませんでした。工程別の時間を比べると、違いがはっきりします。
| 構成 | レビュー(分) | 修正(分) |
|---|---|---|
| E:Lunaがレビュー | 9.2 | 5.0 |
| F:Solがレビュー | 26.0 | 16.7 |
※ 3回の合計
Solのレビューは、仕様違反がなくても次のような指摘を出してきます。
- 無効な日付指定のまま日付をまたいだ場合に、表示が更新されない
- 名前の重複を画面操作で確かめるテストがない
Fではレビューに26.0分、修正に16.7分かかっており、Eのレビュー9.2分、修正5.0分を大きく上回っています。
今回の実験では、分業が遅くなった時間の多くは、Solによるレビューと、その指摘を受けたLunaの修正に使われていました。Sol自体の推論時間なども含まれるため、すべてを「レビューが厳しいから」と断定はできませんが、レビュー・修正の往復が大きな要因だったと考えられます。
4. Luna単独だと「アイコン1文字」で落ちた
全部Luna(E)は3回中1回だけ不合格があり、5件の失敗はすべて同じ原因でした。
Expected: "残り45日"
Received: "◷ 残り45日"
仕様で決めた表示形式に、Lunaが気を利かせて時計アイコンを付けていました。計算の落とし穴(丸め、30日窓)は9回すべてで正しく実装されています。Luna自身のレビューではこの違反を検出できませんでした。一方、分業のFでは3回とも同様の取りこぼしは起きませんでした。
今回の3回という範囲では、Solをレビュー側に置くことで、こうした小さな仕様逸脱を防げた可能性があります。ただし、Eの失敗は1回に集中しており、試行回数も少ないため、「Solレビューなら常に防げる」とまでは言えません。
5. ブラインド評価はほぼ横並び。ただし構成ごとに傾向が違う
ブラインド評価の観点別の平均は次のとおりです(各5点満点、3回の平均)。
| 観点 | D:全部Sol | E:全部Luna | F:分業 |
|---|---|---|---|
| 見た目の分かりやすさ | 3.7 | 3.7 | 4.3 |
| スマホでの使いやすさ | 3.3 | 3.3 | 4.3 |
| 仕様への忠実さ | 5.0 | 5.0 | 5.0 |
| コードの読みやすさ | 3.7 | 3.3 | 3.0 |
| 自作テストの質 | 3.7 | 3.0 | 3.7 |
| 保守しやすさ | 3.7 | 4.0 | 3.3 |
| 合計 | 23.0 | 22.3 | 23.7 |
合計点は22.3〜23.7点に収まっており、3構成で大きな差はありません。
一方、内訳を見ると少し傾向があります。Fは見た目とスマホ対応の評価が高い一方、コードの読みやすさでは最も低い結果でした。
一つの仮説として、SolがUIや使い勝手まで細かくレビューし、その指摘をLunaが追加修正していったことで、画面は磨かれる一方、コードには継ぎ足しが増えた可能性があります。ただし、今回の評価だけでは、この因果関係まで確認することはできません。
もう1つ注意したいのは、「仕様への忠実さ」が全構成で満点だったことです。実際にはEの1回が表示形式の違反でテストに落ちていますが、スクショとコードを見た採点では見抜けませんでした。目視のレビューだけでは細かな仕様違反を取りこぼすので、隠しテストのような機械的な検証が欠かせません。
デザインの傾向も似ていて、9個すべてが緑系に収束しました。「ちょうどよく。」というキャッチコピーが、SolでもLunaでも何度も登場したのも印象的です。
まとめ
今回、GPT-6 SolとLunaを使い、同じWebアプリを3つの構成で各3回作らせて比較しました。
結果としては、
- 今回の実験では、受け入れ品質とコストのバランスが最も良かったのは分業構成。隠しE2Eテスト100%を、全部Solの47%のコストで達成した
- 一方で、分業は最も時間がかかった。全部Solは20.2分、全部Lunaは14.5分、分業は25.1分だった
- 全部Lunaは圧倒的に安く、今回の条件では最速だったが、3回中1回で表示形式の仕様違反を取りこぼした
- 分業コストの約9割はSol側であり、Solをどこまで使うかによって、まだコストと時間を調整できる余地がありそう
- ブラインド評価の合計点には大きな差がなかったが、分業はUI、全部Solはコードの読みやすさなど、構成ごとに異なる傾向が見られた
今回の結果だけを見ると、私は分業構成にかなり可能性を感じています。
ただし、「Solで考えてLunaで実装する」という構成そのものが正解だとは考えていません。
例えば、
- 要件が明確で単純な実装なら、最初から最後までLunaで十分かもしれない
- 小規模で失敗時の影響が小さいタスクなら、レビューにSolを使うコストは割に合わないかもしれない
- 一方で、複雑な仕様や重要な変更では、計画・実装・レビューのすべてにSolを使った方がよいケースもある
- 今回のように、実装量は多いが仕様への忠実さをしっかり担保したい場合には、考える・確かめる工程だけSolに任せる構成が有効かもしれない
つまり、モデル選択もアーキテクチャの一部になってきたのだと思います。
これまでは「どのモデルが一番賢いか」を比較することが多かったですが、実際の開発では、
このタスクにはどのモデルを使うか
この工程にはどこまで強いモデルが必要か
を考える方が重要になっていきそうです。
今回の分業構成は、その選択肢の一つとして、品質・コストのバランスがかなり良かった、というのが今回の実験から得られた一番の示唆です。
次に試すなら、
- 計画だけSol、実装・レビューはLuna
- 計画・実装はLuna、最終レビューだけSol
- Solレビューを2回から1回に減らす
- 軽微なタスクでは全部Luna、重要なタスクだけSolレビューを追加する
など、タスクの難易度やリスクに応じてモデルを切り替える方法を比較してみたいと思います。
最終的には、
一番強いモデルを常に使うのではなく、必要なところに必要なモデルを使う
という使い方が、AI駆動開発のコストと品質を両立するうえで重要になっていくのではないかと考えています。
注意
- 結果は1種類のWebアプリを各構成3回ずつ実行したものです。題材、規模、プロンプト、レビュー回数などが変われば、結果も変わる可能性があります
- 本文中の「100%」は隠しE2Eテスト20件の合格率を指し、ソフトウェア品質全般が100%であることを意味しません
- コストはChatGPT上で実際に請求された金額ではなく、Codexのログのトークン数を2026年9月時点のAPI料金に換算した試算値です
- キャッシュへの書き込み料金(入力単価の1.25倍)と、27.2万トークンを超える長いコンテキストの割増は、Codexのログから算出できないため含めていません。実際の請求額は表より少し高くなる可能性があります
- ブラインド評価の採点者は1モデルのみです。また各構成の差も小さいため、スコアの順位は参考値として扱っています
- 本検証は2026年9月28日に実施しており、GPT-6.1 Solのリリース前だったため、GPT-6 Solを使用しています
