中野智文のブログ

データ・マエショリストのメモ

Codexで始めるAI時代のソフトウェアメンテナンス

OpenAIが2026年10月14日に公開した「Maintaining software with the GPT-6 family」が興味深い。

GPT-6ファミリーを使い、デッドコード削除、古いFeature Flagの整理、CI高速化などを大規模に実施した事例だ。

特に注目したいのは、AIに調査箇所を細かく指定せず、必要な証拠を自分で集めさせるというアプローチである。

1. 実際に得られた成果

OpenAI社内で報告された主な成果は以下のとおり。

対象 成果
デッドコード削除 2週間で1,400件以上のPRをマージ、80万行以上を削除
コンテナイメージ 3.31GB → 1.72GB(48%削減)
古い実験・Feature Flag 数百件を削除
CPU使用量 特定のフラグ削除で約0.42%の削減を推定
CI高速化 中央値10分 → 4.5分
テスト高速化 592.6秒 → 29.8秒(95%削減)

デッドコード削除は5人のエンジニアが主にパートタイムで実施したものだ。

AIが調査からPR作成まで進め、人間がレビューすることで、大規模なメンテナンスを実現している。

2. 何が変わったのか

従来の静的解析だけでは、動的な呼び出しや設定経由の参照を十分に判断できなかった。

今回の方法では、Codexが必要に応じて以下を横断的に調査する。

  • Git履歴、Issue、PR
  • Slackなどの開発上の議論
  • StatsigなどのFeature Flag管理
  • CI実行履歴
  • 本番テレメトリ、CPUプロファイル

これらを組み合わせて削除の根拠を集め、小さなPRを作成する。

ただし、本番環境で実行が観測されないことは、削除して安全であることの証明ではない。 障害時の処理やキルスイッチなどを誤って削除しないための確認は不可欠だ。

3. Codexですぐ使える4つのプロンプト

以下は公式記事の手法を参考に、GitHub Issueベースの開発に合わせて調整したものだ。

①〜④のどれも、調査 → Issue作成までを実行する。コードの変更やPR作成は行わない。

プロンプト 調査対象 最終成果物
① 全体監査 リポジトリ全体の保守課題 改善候補ごとのIssue
② デッドコード調査 未使用コード・不要な依存関係 削除・整理用Issue
③ CI高速化調査 CI時間・実行コスト 最適化用Issue
④ Flaky Test調査 不安定なテスト 修正用Issue

①は②〜④を含む広い監査なので、併用する場合は既存Issueとの重複を確認する。

① メンテナンス全体の監査

リポジトリ全体を調査し、優先度の高い改善をIssue化する。

現在のリポジトリを対象に、以下を監査してください。

- 未使用コード・不要な依存関係
- 古い設定・Feature Flag
- 不安定なテスト(flaky tests)
- CIの高速化・コスト削減
- AGENTS.mdの古い指示や誤誘導

コード、Git履歴、Issue、PR、CI履歴を調査し、
利用可能な外部情報も使って根拠を集めてください。

動的参照、外部API、定期処理、キルスイッチなど、
運用に必要なコードを誤って削除対象にしないでください。

候補ごとに問題、根拠、期待効果、影響範囲、
リスク、検証方法、優先度を評価してください。

根拠が十分で改善効果が見込める候補について、
独立して実装・検証できる単位でGitHub Issueを作成してください。

既存Issueとの重複を避け、同じ根本原因は原則まとめてください。
各Issueには目的、調査根拠、影響範囲、
対応内容、完了条件を明記してください。

最後に作成したIssueの一覧を報告してください。
コード変更・PR作成・マージは行わないでください。

② デッドコードの調査

未使用コードや不要な依存関係を調査し、安全に削除できる候補をIssue化する。

現在のリポジトリで、デッドコードや
不要な依存関係を調査してください。

Git履歴、PR、呼び出し元、動的参照、
利用可能な本番テレメトリなどを確認してください。

キルスイッチ、フォールバック、
外部から参照されるインターフェースなど、
運用上必要な処理は維持してください。

削除できる根拠が十分な候補について、
影響範囲と検証方法を明確にし、
独立して実装・検証できる単位でGitHub Issueを作成してください。

既存Issueとの重複を避け、
不確実な候補を削除可能と断定しないでください。

最後に作成したIssueの一覧を報告してください。
コード変更・PR作成・マージは行わないでください。

③ CI高速化の調査

CI履歴を分析し、削減効果の大きい改善からIssue化する。

現在のリポジトリのCI実行履歴を調査し、
CIの処理時間と実行コストを削減する方法を提案してください。

各候補について、可能な範囲で
1日あたりのCI実行時間削減効果を推定してください。

改善効果、影響範囲、リスク、
追加の保守負担を比較し、優先順位を付けてください。

効果が見込める候補について、
独立して実装・検証できる単位でGitHub Issueを作成してください。

各Issueには問題、根拠、改善内容、
期待効果、検証方法、完了条件を記載してください。

既存Issueとの重複を避け、
推定値と実測値を区別してください。

最後に作成したIssueの一覧を報告してください。
コード変更・PR作成・マージは行わないでください。

④ Flaky Testの調査

最近のCI履歴から不安定なテストを発見し、原因が明確なものをIssue化する。

現在のリポジトリの最近のCI実行履歴を調査し、
不安定なテスト(flaky tests)を特定してください。

失敗ログ、再実行結果、関連コード、
過去の修正履歴を確認し、原因を分析してください。

すべてのflaky testを網羅しようとせず、
原因が明確で修正可能なものを優先してください。

根拠が十分な候補について、
独立して実装・検証できる単位でGitHub Issueを作成してください。

各Issueには再現条件、推定原因、
影響範囲、修正方針、完了条件を記載してください。

既存Issueとの重複を避け、
原因が不確かなものは断定しないでください。

最後に作成したIssueの一覧を報告してください。
コード変更・PR作成・マージは行わないでください。

4. Issue作成後はPR実装へ

①〜④でIssueを作成したら、個々のIssueを対象に実装を進める。

対象Issueを精査し、目的、要件、影響範囲、
依存関係、完了条件の不足や矛盾を修正してください。

本来の目的に対する余計な拡張、
過剰な検証や汎用化は避けてください。

独立して検証できる単位で必要最小限のPRを作成し、
関連テストを実行してください。

修正内容と検証結果をPRに記載し、
末尾に @codex review を付けてレビューを依頼してください。

マージは行わないでください。

これで、監査からIssue作成、実装、レビューまでを明確に分離できる。

5. モデルのコスト比較

記事では、100万行以上のコードを含む対象で、古い実験の発見能力を比較している。

モデル 発見数 APIコスト
GPT-6 Astra Ultra 142 $109.06
GPT-6.1 Sol Ultra 131 $15.12
GPT-6.1 Sol Medium 86 $5.27

Sol UltraはAstra Ultraの約92%の発見数を、約14%のAPIコストで達成している。

ただし、発見数は正解数や削除成功数を意味しない。複雑な依存関係の見落としもあるため、単純な優劣比較はできない。

まず安価なモデルで試し、必要に応じて推論強度を上げるのが合理的だ。

6. AGENTS.md自身も改善対象にする

記事では、Codexの作業を誤誘導したAGENTS.mdの記述を特定し、その指示がある場合とない場合で同じタスクを再実行する方法も紹介されている。

同じモデル、同じコード、同じタスクで比較し、品質・コスト・実行時間などを評価する。

AGENTS.mdは書いて終わりではなく、効果を測定しながら改善する設定ファイルとして扱うべきだろう。

まとめ

今回の記事から得られる実践的なポイントはシンプルだ。

  1. AIには細かな調査手順ではなく、目標と制約を与える。
  2. コードだけでなくGit・CI・運用データから証拠を集める。
  3. 調査結果から根拠のある改善だけをIssue化する。
  4. Issue単位で必要最小限のPRを作成し、人間がレビューする。
  5. AGENTS.mdやプロンプト自身も継続的に改善する。

Codexは新機能の実装だけでなく、ソフトウェアの健全性を継続的に改善するためのツールとしても活用できる。

まずは①の全体監査で改善候補を発見し、必要に応じて②〜④の専門的な調査を追加する。既存Issueとの重複を避けながら改善を進めたい。


参考資料

個人GitHubの全リポジトリで有効にしたセキュリティ設定まとめ

2026年10月24日

個人のGitHubで複数のリポジトリを運用していると、依存ライブラリの脆弱性やAPIキーの誤コミットを、それぞれのプロジェクトで個別に管理するのは面倒です。そこで、GitHubの Settings → Code security にある設定を見直し、既存のリポジトリと今後作成するリポジトリに共通のセキュリティ方針を適用することにしました。

今回の基本方針は、無料で利用できる保護機能を有効化し、更新PRは自動作成しても自動マージはしない、というものです。

今回の推奨設定一覧

設定項目 推奨 主な役割
Dependency graph ON 依存ライブラリと依存関係を把握
Dependabot alerts ON 既知の脆弱性を通知
Dependabot security updates ON 脆弱性修正のPull Requestを自動作成
Grouped security updates ON 複数のセキュリティ更新を可能な範囲で1つのPRにまとめる
Dependabot on self-hosted runners OFF Dependabot専用に自前Runnerを使う必要はない
Push protection for yourself ON 対応する秘密情報を含む公開リポジトリへのPushをブロック
Private vulnerability reporting ON(公開リポジトリ) 外部から脆弱性を非公開で報告してもらう

※「推奨」は今回の個人開発向け方針であり、すべてのリポジトリで実際に有効になったことを確認したという意味ではありません。

1. Dependency graph

依存しているパッケージをGitHubが把握するための基礎機能です。package.json、ロックファイル、その他の対応する依存関係マニフェストから情報を読み取り、リポジトリの依存関係を可視化します。

推奨:ON。 Dependabot alertsやsecurity updatesの前提になるため、最初に有効化します。基本機能は無料で使えます。

2. Dependabot alerts

Dependency graph上のライブラリに既知の脆弱性が見つかった場合に通知する機能です。何を修正すべきかを把握する入口になります。

推奨:ON。 通知を受け取るだけで問題が解決するわけではないため、重要度や利用状況を見て対応を判断します。

3. Dependabot security updates

脆弱な依存パッケージに対して、修正版へ更新するPull Requestを自動作成する機能です。修正PRの内容をレビューし、CIを通してから手動でマージします。

推奨:ON。ただし自動マージは行わない。

注意点として、Dependabotの更新ジョブと、Dependabotが作成したPRを契機に動く通常のCIは別物です。前者は標準のGitHub-hosted RunnerならActionsの利用時間に算入されませんが、後者はワークフローとリポジトリの公開状態に応じて通常のActions課金ルールが適用されます。

4. Grouped security updates

複数の脆弱性修正を、パッケージマネージャーやディレクトリなどの条件に従って可能な範囲でまとめる設定です。

例えば3件の修正PRが1件にまとまれば、レビューやCIの運用負荷を減らせます。一方で、1つの依存更新が原因でテストに失敗すると、まとめたPR全体のマージが止まります。

推奨:ON。 最初に有効化した際、既存のDependabot PRが閉じられ、新しいグループ化PRに置き換えられることがあります。また、.github/dependabot.yml 内のグループ設定が優先される場合があります。

5. Dependabot on self-hosted runners

Dependabotの依存更新ジョブを自分で用意したRunner上で実行するための機能です。社内ネットワークやプライベートレジストリなど特別な要件がなければ必要ありません。

推奨:OFF。 標準のGitHub-hosted Runnerで十分です。GitHubの公式説明では、標準GitHub-hosted Runner(およびself-hosted Runner)上のDependabotジョブは、含まれるGitHub Actions分数に算入されません。Larger Runnerでは通常料金が発生します。なおself-hosted Runnerのインフラ費用まで無料になるわけではありません。

6. Push protection for yourself

GitHubが対応する形式のAPIキー、アクセストークンなどが含まれるPushを検知してブロックする仕組みです。個人アカウントの設定から有効化します。

推奨:ON。 今回確認した画面ではボタンが「Disable」と表示されており、すでに有効でした。利用者向けの保護は公開リポジトリを対象とするため、これだけで非公開リポジトリやすべての秘密情報を網羅できるわけではありません。秘密情報を誤ってコミットした場合には、Pushできたかどうかにかかわらず、キーの失効・再発行も検討します。

7. Private vulnerability reporting

公開リポジトリの利用者やセキュリティ研究者が、発見した脆弱性を一般公開せず、メンテナーに報告できる機能です。

推奨:ON(公開リポジトリ)。 外部からの報告受付を整備できます。ただし報告内容のトリアージ、修正、必要に応じた公開は管理者側の作業です。

設定手順

  1. GitHubのCode security設定を開きます。
  2. Dependency graph、Dependabot alerts、Dependabot security updatesで既存リポジトリ向けの「Enable all」を選びます。
  3. Grouped security updatesも「Enable all」にします。
  4. これらの設定で利用可能な「Automatically enable for new repositories」にチェックを入れます。
  5. Dependabot on self-hosted runnersは有効化しません。
  6. Push protection for yourselfは「Disable」が表示されていれば有効なので、そのまま維持します。
  7. Private vulnerability reportingは「Enable all」を選び、「Automatically enable for new public repositories」にチェックを入れます。
  8. 代表的な公開・非公開リポジトリを開いて、Settings → Security / Advanced Securityなどの画面と Security → Dependabot alerts を確認します。

設定画面の文言や位置はGitHubのUI更新で変わることがあります。個人アカウントの設定は、そのアカウントが所有するリポジトリを対象に確認し、組織所有のリポジトリは組織側の設定も確認します。

費用について

処理 料金上の考え方
Dependency graph・Dependabot alerts 基本的な利用は無料
Dependabotのsecurity updates 機能自体は無料
Dependabotの標準GitHub-hosted Runnerでの更新ジョブ GitHub Actionsの利用分数に算入されない
DependabotでLarger Runnerを使用 通常の課金対象
Dependabot PRに対して実行する通常のCI 通常のActions課金ルールに従う(公開リポジトリの標準Runnerは無料、非公開リポジトリでは無料枠などに注意)
Self-hosted Runner GitHub側の分数に算入されなくても、自分のVM・運用コストは発生しうる

「Dependabotが無料」と「そのPRで動くすべてのCIが無料」は同義ではない点が重要です。

運用上の注意

  • 自動マージはしない。 更新が小さく見えても、破壊的変更や依存の競合が起こる可能性があります。
  • PR作成直後は件数が増えることがある。 最初に未対応の脆弱性が大量に見つかれば、通知やPRの数も増えます。
  • Grouped updatesは万能ではない。 まとめたPRで失敗した場合は原因を切り分けます。
  • Dependabot version updatesは別機能。 脆弱性に関係しない一般のバージョン更新まで一律に自動化する必要はありません。
  • 依存関係の検査だけで十分ではない。 アプリ本体の脆弱性、設定ミス、認証・権限設計などの対策も別途必要です。

コーディングエージェント用プロンプト

現在のリポジトリの未解決Dependabotアラートを調査し、可能なものをまとめて修正してください。

1. アラート、既存PR、依存関係を確認し、重大度・影響範囲・根本原因を整理する。
2. 既存のDependabot PRを優先し、重複する修正を避ける。
3. 互換性のある最小の安全なバージョンへ更新する。間接依存は依存元の更新を優先し、必要な場合のみoverrides等を検討する。
4. 同じ根本原因のアラートはまとめて修正し、無関係な変更や過剰な検証は行わない。
5. ビルド・テスト・CIで検証し、修正前後のアラートを比較する。
6. 独立した修正単位で必要最小限のPRを作成し、修正内容・検証結果・残存アラートを記載する。

アラートを減らすためだけのDismissや安易なダウングレードは禁止します。修正できないものは理由と対応方針を報告してください。

アクセス権限や検証環境が不足する場合は、その範囲を明記してください。問題がなくなるまで必要な再確認・修正を行い、最終的に結果を報告してください。

まとめ

今回の設定は、個人開発の複数リポジトリを対象に、低コストで守備範囲を広げるための基本セットです。

「依存関係を把握する → 脆弱性を検出する → 修正PRを自動作成する → レビューしてマージする」という流れを整え、さらに秘密情報の誤Push防止と脆弱性の非公開受付窓口を追加できます。

すべてを自動化するのではなく、検出と修正提案は自動化し、採用判断とマージは人間が行うのが、今回の運用方針です。

参考資料

Integration Branch & GitHub Actions Optimization Guide

Integration Branch & GitHub Actions Optimization Guide

目的

小さいPRによるレビュー品質・変更単位の明確さを維持しながら、GitHub Actionsの不要な実行とproduction deploy回数を削減する。

最適化の目的は「テストを減らすこと」ではない。

以下の3つを分離することが目的である。

レビュー単位
≠
統合検証単位
≠
production deploy単位

基本構成は次のとおり。

main
 ↑
integration/issue-<N>
 ↑        ↑        ↑
PR A     PR B     PR C

ただし、integration branchは常に使うものではない。


1. 最初に確認すること

実装前に、必ず現在のGitHub Actions構成を確認する。

確認対象:

  • PRでどのworkflowが走るか
  • main pushでどのworkflowが走るか
  • deploy workflowがいつ起動するか
  • 各stepの所要時間
  • 同じPRへの追加pushでCIが再実行されるか
  • cancel-in-progress が設定されているか
  • required checkのworkflow/job名
  • production deploy前にどの検証が必要か
  • stale revision防止があるか
  • production smokeがあるか

想像でCIを最適化しない。実測してから決める。


2. まず「Actionsコストの原因」を分類する

Actionsコスト増加には少なくとも3種類ある。

A. PRへの大量push

PR作成
↓
CI
↓
修正push
↓
CI
↓
修正push
↓
CI
...

1つのPRに数十〜数百回pushする運用では、これが最大要因になり得る。

PR CIが存在するrepoでは原則として:

concurrency:
  group: ci-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

相当を検討する。

要件:

  • 同一PRの新runで古いrunをcancel
  • PRごとにgroupを分離
  • main pushとはgroupを分離
  • production deployには安易に cancel-in-progress: true を適用しない

重要

cancel-in-progress: true は、すでに終了したrunのコストを取り戻すものではない。

レビュー修正を細切れにpushするより、

複数指摘を確認
↓
まとめて修正
↓
ローカル検証
↓
1回push

を基本とする。


B. 子PRごとの重いCI

E2E、Firebase emulator、Playwright、container buildなどが子PRごとに走る場合。

この場合はintegration向けPRだけheavy stepをskipし、

子PR
→ light CI

integration → main
→ full CI

とする効果が大きい。


C. 子PRをmainへ順次mergeすることでproduction workflowが何度も走る

PR CIが存在しないrepoでも発生する。

PR A → main → Deploy
PR B → main → Deploy
PR C → main → Deploy

を、

PR A ┐
PR B ├→ integration
PR C ┘
        ↓
       main
        ↓
      Deploy

にすることで、production-side Actionsを減らせる。


3. integration branchを使う条件

次の場合に使用する。

  • 1つの親Issueが複数PRに分かれる
  • 子PR同士の統合状態を最後に検証する意味がある
  • 子PRをmainへ順次mergeすると重いActions/deployが繰り返される
  • 最終的に1つのproduction revisionとして安全にまとめられる

branch名:

integration/issue-<Issue番号>

例:

integration/issue-128

4. integration branchを使わない条件

以下の場合は作らない。

  • 単一PRで完了する
  • 複数PRでも完全に独立している
  • integrationによる統合検証上の利益がない
  • CIが非常に軽く、複雑化の方が大きい
  • deployment contract上、複数のmain revisionが必要

「複数PRだからintegrationを使う」ではない。

判断基準は、

integration branchを追加することで、レビュー・検証・Actionsコスト・production安全性の全体が簡潔になるか

である。


5. 標準的なintegration運用

親Issueに、PR分割・順序・依存関係を先に記載する。

例:

PR1: API変更
PR2: UI対応
PR3: regression test
Final: integration/issue-N → main

実装順序:

main
 ↓
integration/issue-N

feature-A
 ↓ PR
integration/issue-N
 ↓ merge

最新 integration/issue-N
 ↓
feature-B
 ↓ PR
integration/issue-N
 ↓ merge

最新 integration/issue-N
 ↓
feature-C

必ず:

  • 1PRずつレビューする
  • merge後の最新integrationを次PRのbaseにする
  • 古いintegration状態から並列に大量PRを作らない

6. 最終PR

すべての子PR完了後、

integration/issue-N
↓
main

の最終PRを作成する。

最終PRでは:

  • 最新mainを取り込む
  • full CI
  • integration/E2E
  • regression確認
  • combined diff review

を行う。

最終PRを作ったあとmainが進んだ場合は、

最新mainを再取り込み
↓
影響する検証を再実行

する。


7. Closes #N の扱い

通常のintegration運用では:

  • 子PR: Refs #N
  • 最終PR: Closes #N

とする。

ただしproduction deploy後の確認までIssueを閉じてはいけないrepoでは、自動close keywordを使わない。

その場合:

Final PR merge
↓
production workflow
↓
production smoke
↓
Issueを手動close

とする。


8. Light CI / Full CI

Light CI

integration向け子PRでは、原則として高速な基本検証を残す。

例:

dependency install
typecheck
lint
unit test
build

Heavy CI

最終main PRへ集約できる候補:

Playwright install
browser E2E
preview E2E
Firebase emulator
Firestore rules emulator
full integration smoke
container build
dependency audit
benchmark

ただし、何をskipするかは実測で決める。


9. 「軽量化しすぎない」

重要な原則:

light CIは「安全性を落とすための仕組み」ではない。

子PRで省略した検証は、原則として最終main PRで必ず実行する。

変更内容そのものがheavy testを必要とする場合は、integration向けPRでも実行する。


10. Check identityを維持する

required checksが存在するrepoでは、workflowやjobそのものをbranch条件で消すと、

Required check: Pending

のままmerge不能になる可能性がある。

可能なら:

jobs:
  validate:

自体は常に生成し、

- name: Heavy E2E
  if: ${{ !startsWith(github.base_ref, 'integration/issue-') }}

のようにstep単位でskipする。

原則:

  • workflow名を変えない
  • job名を変えない
  • integration PRでもcheckを生成する
  • light/full差分はstep条件で表現する

11. cancel-in-progress のベストプラクティス

PR CI:

concurrency:
  group: ci-pr-${{ github.event.pull_request.number }}
  cancel-in-progress: true

またはPR/main共用なら:

concurrency:
  group: ci-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

Production deploy

原則:

cancel-in-progress: false

production deployはCIとは別物として扱う。

途中で新しいcommitが来たからといって、deployを無条件キャンセルしない。

代わりに:

  • stale SHA guard
  • exact current main確認
  • deployment scope判定

を使う。


12. 最大のトラップ: Deploy側の検証を削りすぎる

実際に発生した問題:

PRでfull CI済み
↓
だからDeployのlint/testは不要

と考えてDeployから検証を削除した。

しかし、

  • direct push
  • branch protection bypass
  • required check迂回
  • PR merge commitと検証SHAの差異

により、productionへ進む実際のmain SHAが未検証になる経路が残った。

したがって、

「PRで検証済みだからDeploy側の検証は消せる」

とは限らない。

production境界は独立して考える。


13. PR merge refとmain SHAは同一とは限らない

GitHubのPR CIが検証するものと、merge後にmainへ作られるcommitは一致しない場合がある。

したがって、

PR CI成功
=
production SHAそのものが検証済み

と単純には扱わない。

安全境界として必要なら:

Final PR full CI
+
Deployでexact current main SHA検証

を組み合わせる。


14. stale revision guardを維持する

production workflowでは、対象SHAが現在のmain HEADか確認する。

概念:

current_main="$(git ls-remote origin refs/heads/main | cut -f1)"

test "$GITHUB_SHA" = "$current_main"

あるいはdeployment scopeに応じて、

「mainが進んでいても、その変更が当該artifactに影響しなければ継続」

というfail-safeな判定を行う。

Actionsコスト削減を理由にstale revision protectionを弱めない。


15. Deployment contractはintegrationより優先

非常に重要。

integration branchで複数変更をまとめても、production側の契約上、

変更Aと変更Bを同一main revisionに含めてはいけない

場合がある。

例:

Hosting変更
+
Firestore / Storage resource変更

を同一revisionに含めるとfail closedする設計。

この場合、

integration → main

を1回でmergeしてはいけない。

代わりに:

integrationで開発・レビュー
↓
production-compatibleなfinal PR A
↓
main
↓
deploy

最新mainへ更新

final PR B
↓
main
↓
deploy

とする。

integration branchはproduction contractを上書きしない。


16. PR CIがないrepoにPR CIを増やさない

重要なアンチパターン。

既存repoが:

PR push
→ Actionsなし

main push
→ production validation/deploy

なら、Actions削減のために

PR push
→ 新しいCI

を追加すると、逆にコストが増えることがある。

このタイプのrepoではintegration branchの主目的は、

子PRをmainへ順次mergeすることによるproduction workflowの多重起動を減らす

ことである。


17. CIが軽いrepoでは分岐を増やさない

実測例:

CI全体: 約25秒
npm install: 約20秒
build: 約1秒
その他: 数秒

この場合、

integration PRではasset checkだけskip

のような最適化は、削減効果よりworkflow複雑化の方が大きい。

この場合は:

  • cancel-in-progress だけ追加
  • light/full分離はしない

が正しい。


18. 重いrepoではlight CIの効果が大きい

例えば、

CI全体: 約262秒

npm ci〜build:
約64秒

Playwright + E2E + Firebase:
約190秒

のようなrepoでは、

integration子PR:
64秒程度

main最終PR:
262秒

にできるため効果が大きい。

最適化はrepoごとに変える。


19. Benchmarkは「重そう」という名前だけで判断しない

実測した結果、

benchmark: 0.26秒

しかかからないケースがあった。

名前だけ見てheavy test扱いしない。

必ずstep時間を見る。


20. Artifact再利用・Reusable Workflowを早期導入しない

一見効率的でも、

  • SHA対応
  • 権限境界
  • artifact provenance
  • workflow間依存
  • rerun semantics

が複雑になる。

実測で大きな効果がなければ、

artifact共有
reusable workflow
汎用CI framework

は導入しない。

原則:

最小の条件分岐で済むなら、それを選ぶ。


21. 長寿命integration branchを作らない

アンチパターン:

main
 ↑
integration
 ↑ ↑ ↑ ↑
Issue A
Issue B
Issue C
Issue D

これでは:

  • Issue境界が曖昧
  • dependencyが増える
  • revert困難
  • 最終diffが巨大化
  • production責任が曖昧

になる。

必ず:

integration/issue-123
integration/issue-456

のような短命branchとする。


22. 子PRを全部同時に作らない

アンチパターン:

integration
├ PR A
├ PR B
├ PR C
└ PR D

を最初に全部作る。

Aの変更によってB/C/Dの前提が変わると、rebase・conflict・レビュー差分が増える。

推奨:

A
↓ review
↓ merge

最新integration
↓
B
↓ review
↓ merge

最新integration
↓
C

23. 「後でintegrationで直す」を許さない

integration branchは壊れたPRを一時保管する場所ではない。

各子PRはmerge前に:

  • Issue要件を満たす
  • 必要なローカルテストが通る
  • レビューが通る
  • known regressionを残さない

こと。

Failing PR
↓
integrationへmerge
↓
最後にまとめて直す

は禁止。


24. レビュー修正のpush回数も最適化する

Actionsコストが高いrepoでは、

修正1 → push
修正2 → push
修正3 → push
...

を避ける。

レビュー指摘に対して:

  1. 全指摘を確認する
  2. Issueスコープ内か判断する
  3. 根本原因を修正する
  4. 類似箇所・副作用を確認する
  5. ローカル検証する
  6. まとめてpushする
  7. @codex review

を基本とする。


25. Production smokeは削らない

Actions削減で最後に削るべきではないもの:

  • production smoke
  • authentication boundary check
  • stale revision check
  • deployment credential check
  • exact deploy target verification

production smokeは、

「CIで正しかった」

ではなく、

「実際にproductionへ正しく出た」

ことを確認する別の安全境界である。


26. 標準的な親Issue向け指示

以下を親Issueの実装指示として利用できる。


Issueを精査し、要件・影響範囲・依存関係・完了条件の不足や矛盾を修正してください。修正後も再確認し、問題がなくなるまで繰り返してください。

独立して完了・検証できる作業はIssueまたはPRを分割してください。

複数PRを1つの親Issueとして統合検証する明確な利点がある場合は、親Issue単位の短命な integration/issue-<Issue番号> ブランチを使用してください。

複数PRの場合は、実装前に親Issueへ以下を明記してください。

  • PR分割内容
  • 実装順序
  • PR間の依存関係
  • integration branchを使う理由
  • 最終統合・production publication方法

各子PRは原則として integration/issue-<Issue番号> をbaseとし、1つずつ実装・検証・レビュー・マージしてください。

各PRのmerge後は、最新integration branchを基準に次のPRを実装してください。複数子PRを古いintegration状態から同時並行で作成しないでください。

子PRでは親Issueを参照しますが、Closes / Fixes / Resolves は付けないでください。

すべての子PR完了後、integration branchを最新mainへ更新し、必要な統合・回帰検証を実施してください。

combined diffが1つのproduction revisionとして安全に成立する場合は、integration/issue-<N> → main の最終PRを作成してください。

deployment contract上、複数のmain revisionが必要な場合は、integration branchをそのまま1回でmainへmergeせず、production-compatibleなfinal PRへ分割し、依存順にmainへmergeしてください。

各final PR merge後にmainが進んだ場合は、残りのfinal PRを最新mainへ更新し、影響する検証を再実行してください。

PR CIが存在する場合は、同一PRへの追加pushで古いrunを停止できるよう cancel-in-progress: true を検討してください。ただしproduction deployでは既存の安全なconcurrency/stale revision制御を維持してください。

integration向け子PRのCI軽量化は、実測したstep時間に基づいて判断してください。workflow/job名を不必要に変更せず、required checkがPendingにならないよう、可能なら同じjob内のheavy stepだけを条件skipしてください。

Actionsコスト削減を理由に、production deployのstale revision guard、credential boundary、production smoke、必要なdeploy-time validationを弱めないでください。

PR用Actionsが存在しないrepoでは、integration運用導入だけを理由に重いPR CIを新設しないでください。

単一PRで十分なIssue、またはintegrationによる統合検証・Actions削減上の明確な利点がないIssueでは、integration branchを作成しないでください。

実装・検証後、@codex review の前にPR全体をセルフレビューしてください。要件漏れ、境界値・異常系、副作用・回帰、既存契約の破壊、類似問題、テスト不足、不要な変更や過剰な一般化がないか確認してください。

問題があれば、まずスコープ内か判断してください。スコープ内なら根本原因を修正して再検証し、スコープ外なら必要に応じて別Issueへ切り出してください。

最後に、実装・検証・セルフレビュー結果をPRへ簡潔にコメントし、末尾に @codex review を付けてください。


27. 新規repo向け初期チェックリスト

新しいrepoでは最初に以下を判断する。

PR CI

  • [ ] PR pushでActionsが走るか
  • [ ] cancel-in-progress: true があるか
  • [ ] 同一PRで数十回pushする運用があるか
  • [ ] required check名は何か

CIコスト

  • [ ] dependency install時間
  • [ ] unit test時間
  • [ ] E2E時間
  • [ ] emulator時間
  • [ ] container build時間
  • [ ] audit時間
  • [ ] benchmark時間

Deploy

  • [ ] main pushだけでdeployするか
  • [ ] stale SHA guardがあるか
  • [ ] exact current mainを確認するか
  • [ ] production smokeがあるか
  • [ ] deploy concurrencyは安全か
  • [ ] direct push / bypass時の安全性はあるか

Integration

  • [ ] 親Issueが複数PRになることが多いか
  • [ ] 子PRごとのheavy CIを減らせるか
  • [ ] 子PRごとのmain deployを減らせるか
  • [ ] 1つのproduction revisionにまとめて安全か

28. 判断フロー

Issueは単一PRで完了する?
  ├─ Yes → 通常PR
  └─ No
       ↓
複数PRを統合して検証する意味がある?
  ├─ No → 各PRを通常運用
  └─ Yes
       ↓
integration/issue-N を作る
       ↓
PR CIはある?
  ├─ No
  │    ↓
  │ PR CIを新設する必要は本当にある?
  │    ├─ No → ローカル検証 + review
  │    └─ Yes → 別途CI設計
  │
  └─ Yes
       ↓
     CIは重い?
       ├─ No → 同一CI + cancel-in-progress
       └─ Yes → integration PRだけlight CI
       
       ↓
子PRを1つずつintegrationへmerge
       ↓
latest mainを取り込む
       ↓
full integration verification
       ↓
1 revisionでproduction可能?
  ├─ Yes → integration → main
  └─ No  → deployment-compatible final PRへ分割
       ↓
production workflow / smoke
       ↓
Issue close

29. アンチパターンまとめ

避けること:

  • 全repoへ同じCI最適化をコピーする
  • 複数PRなら無条件でintegrationを作る
  • 長寿命integration branchを使う
  • 全子PRを最初から並行作成する
  • failing PRをintegrationへ積む
  • required check jobそのものをbranch条件で消す
  • benchmarkという名前だけでheavy扱いする
  • 数秒節約のためにworkflowを複雑化する
  • PR CIがないrepoにコスト削減目的でPR CIを新設する
  • PRでCI済みという理由だけでDeploy validationを削除する
  • production smokeを削る
  • stale SHA guardを弱める
  • Deployへ cancel-in-progress: true を安易に適用する
  • integration branchをproduction deployment contractより優先する
  • artifact共有/reusable workflowを早期に一般化する
  • レビュー指摘ごとに細切れpushする

30. 最重要原則

最適化の優先順位は次のとおり。

1. production安全性
2. correctness / regression detection
3. reviewability
4. Actionsコスト
5. workflowの見た目上の統一

GitHub Actions最適化は、

「検証をなくす」

のではなく、

「同じ検証を、最も意味のある境界で1回だけ行う」

ことを目指す。

そしてintegration branchは、

「PRをまとめる場所」

ではなく、

「レビュー単位を小さく保ったまま、統合検証とproduction publicationの回数を制御するための短命な境界」

として扱う。

GitHub + コーディングエージェントを使い倒す!PR分割とレビュー対応の便利プロンプト

最近、GitHubと連携して動くAIコーディングエージェント(codex, claude など)を開発に取り入れている方も多いと思います。

非常に便利な一方で、「気づいたらPR(プルリクエスト)が巨大になっていた」「レビューの指摘に対して、表面的な修正しかしてくれない」といった悩みを抱えることはありませんか?結果として、コードレビュー(codex review)のコスト(token数)が跳ね上がってしまうんですよね。

そこで今回は、「PRを小さく保ち、事前に精査させる」ことでエージェントの出力品質を上げ、レビューコストを大幅に下げるための便利フレーズ(プロンプト)を2つ紹介します。

1. Issue着手時のプロンプト(要件精査とタスク分割)

エージェントにいきなりコードを書かせるのではなく、まずは「要件の精査」と「Issue・PRの分割」を徹底させます。これにより、レビューしやすい小さな単位でタスクを進めてもらうことができます。

【プロンプト】

まず対象が main/master ブランチであることを確認してください。Issueを精査し、要件・影響範囲・依存関係・完了条件の不足・矛盾を修正してください。本来の目的に対する余計な拡張・過剰な検証も削り、必要最小限にしてください。修正後も問題がなくなるまで再確認・修正を繰り返してください。
独立して完了・検証できる作業はIssueまたはPRに分割し、複数PRの場合は先にIssueへ分割内容・順序・依存関係を明記してください。
複数PRを親Issue単位で統合検証する必要がある場合は、短命な integration/issue-<Issue番号> ブランチを作成し、各PRのbaseは原則このブランチとしてください。各PRを1つずつ実装・検証・レビュー・マージし、最新のintegrationブランチを基準に次へ進めてください。全子PR完了後、integrationからmainへの最終PRを作成し、統合状態で必要なフルCI・回帰検証を行い、親Issue完了後にintegrationブランチを削除してください。分割に統合検証上の利点がない場合や単一PRで十分な場合は作成せず、Closes #Issue番号 は親Issueを完了させる最後のPRだけに付けてください。
実装はIssue達成に必要な範囲を基本とし、抽象化・リファクタリング・共通化は、重複・複雑さを減らして変更全体をより簡潔・明確にできる場合に限ってください。将来用途を見越した過剰な一般化は避け、コード・コメント・ドキュメントは必要十分かつ簡潔に、特にドキュメントは必要最小限にしてください。
実装・検証後、@codex review の前にレビュー担当者の視点でPR全体をセルフレビューし、要件漏れ、境界値・異常系、副作用・回帰、既存契約の破壊、類似問題、テスト不足、不要な変更・冗長さを確認してください。問題はまずスコープ内か判断し、内なら根本原因を修正して再検証・再レビュー、外なら必要に応じて別Issueへ切り出してください。
今回の依頼はIssue本文の精査・修正に限りません。残る実装・実機検証を含む完了条件を満たすまで進め、PRのセルフレビューまで実施してください。但し再現性が難しい検証に固執する必要はありません。最後に完了条件を確認し、実装・検証・セルフレビュー結果をPRへ簡潔にコメントし、末尾に @codex review を付けてレビューを依頼してください。

💡 ポイント * 「再帰的に行う」「問題がなくなるまで繰り返す」と指示することで、AIの妥協を防ぎます。 * 「Closes #Issue番号」をつけるタイミングを制御することで、複数PRに分かれた際にIssueが意図せず勝手に閉じてしまうのを防ぎます。

2. レビュー指摘への対応プロンプト(根本解決と横断チェック)

PRのレビューで指摘が入った際の返しとして使います。AIは指摘された行だけを直しがちなので、類似箇所や副作用も含めて横断的に修正させ、テストまで書かせます。

【プロンプト】

PRを監視し続けてください。指摘があれば、まずIssueの目的・完了条件・既存の設計境界に照らして、指摘が本来のスコープ内か確認してください。
スコープ内であれば根本原因を修正し、同じ原因による類似箇所・副作用・未検出問題を横断確認して、必要な修正してください。
ただし、レビュー対応を理由に要件や設計境界を暗黙に拡張したり、汎用化・完全性の証明・過剰な防御実装へ発展させたりしないでください。スコープ外の問題は必要に応じて別Issueへ切り出してください。
最後に、PR差分が目的に対して必要最小限か確認してください。修正と確認が完了したら、実施した修正内容と確認結果をPRへコメントし、そのコメントの末尾に @codex review を付けて再レビューを依頼してください。指示文そのものをPRへ転載しないでください。指摘がなくなるまでこれを繰り返してください。問題がなくなればマージをして、git pull をしてください。

💡 ポイント * 「根本原因の修正」と「横断的な確認」を明記することで、モグラ叩き的なバグ修正を防ぎます。 * 再発防止のためのテスト追加も条件に含めることで、コードの品質を担保し、レビュアーの負担を減らします。

おわりに

コーディングエージェントは、人間のレビュアーと同じように「作業の前提条件や、期待する進め方(制約)」を最初にしっかり伝えておくことで、パフォーマンスが劇的に向上します。

日々のAIエージェントへの指示出しの参考にしてみてください!

TypeSafe skill の話(自分のメモ用)

TypeSafe skill(typesafe-ai/skills の typesafe-ai)は、TypeScript の型安全を扱うスキルではありません。TypeSafe の System One / Jev を、アプリケーションの「プログラム・プリミティブ」として組み込むためのエージェント用スキルです。

出力の中心は文章ではなく、Choice / Noul / Score による型付きの判定と確率分布です。アプリケーションの制御フローはコード側が持ち、モデルには「コモンセンスや意味理解が必要な、狭く限定された判断」だけを担当させます。

公式スキル自身も、ライブドキュメントを source of truth とすることを明記しています。したがって実装前には、必ず docs index と How to build を確認するのが第一原則です。

また重要なのは、型付き出力だからといって判定自体が常に正しいわけではないことです。

TypeSafe が保証するのは主として出力インターフェースです。たとえば Choice は定義された候補の中からしか値を返しませんが、意味的に誤った候補を選ぶ可能性までなくなるわけではありません。

したがって、本番では必ず実データによる評価と校正が必要です。


1. アーキテクチャ原則:エージェントループを作らない

TypeSafe は、典型的な「AI エージェント」を構築するための仕組みというより、AI-powered software を作るための仕組みです。

Jev に、

  • 返信文
  • コード
  • 長い推論説明
  • 次に実行すべきツール列

を自由生成させることを中心には据えません。

基本原則は明確です。

  • 決定的に処理できるルール、計算、ルックアップ、副作用は コード側で実行する
  • while ループの中でモデルに「次に何をするか」を自由選択させる設計にしない
  • モデルには、知識のある人なら1秒程度で判断できる狭い意味判断を任せる

たとえば、次のような処理にモデルは不要です。

days_overdue = (today - invoice.due_date).days

if days_overdue > 30:
    route_to_collections(invoice)

一方、

このチケットを分析して、最適な対応を決めて

のような大きな質問をそのままモデルに渡すのも適切ではありません。

代わりに、

  • どの部署が担当すべきか
  • 緊急性があるか
  • 返金要求があるか
  • 必要な証拠が含まれているか
  • ポリシー上の条件を満たしているか

といった、独立して意味を持つ狭い判断へ分解します。


2. 欲しい挙動から逆算する

TypeSafe skill の中核となる考え方は、モデルから出発するのではなく、アプリケーションが実現したい挙動から逆算することです。

基本手順は次のとおりです。

  1. アプリケーションが何を 表示・選択・変更・引き渡し するのか決める
  2. そのために必要な判断だけを洗い出す
  3. 既知のルールはコードに残す
  4. 意味理解やコモンセンスが必要な箇所にだけ TypeSafe を差し込む

TypeSafe は単純な分類器としてだけでなく、次のような構成にも使えます。

目的 設計 参照パターン
ルート選択+引数決定 ハンドラ選択と型付きパラメータ判定を組み合わせる function calling / fan-out
生成せずに値を選ぶ 候補をコード側で列挙し、その中からモデルに選ばせる value extraction / autoformat
証拠を選別する 検索 → 関連度判定 → 採用 rerank / hierarchical classification
再利用可能な信号を得る Score を取得し、重み付けや合成はコード側で行う composite scoring
主張と証拠を検証する 主張と引用・根拠の対応を判定する citation check
低コスト処理から段階的に上位処理へ回す 初段の結果を検証し、必要な場合だけ高コスト処理へ送る cascade patterns / SDE cascade

なお SDE cascade は、より具体的には structured-data extraction を段階的に処理する cookbook です。一般的な検証処理すべてを指す名称ではありません。

設計原則としては、

候補集合と実行はコード、意味判断だけモデル

と考えると分かりやすくなります。


3. プリミティブの選び方

プリミティブは、欲しい答えの形から選びます。

ここを間違えると、後段で結果をうまく合成できません。

欲しい答え プリミティブ 設計上の注意
既知の集合から1つ選ぶ Choice 候補間の確率分布を得る。候補漏れがあり得るなら other / none を入れる
条件が成立するか Noul yes の確率を返す。独立した confidence はない。複数ラベルなら 1ラベルにつき1つの Noul
順序付きの度合い Score 各レベルを単独でも解釈できる具体的な状況として定義する

Choice

Choice は、相互排他的な既知候補から1つ選ぶ場合に使います。

たとえば、

  • billing
  • technical
  • sales
  • other

のような部署振り分けです。

候補集合が完全でない可能性があるなら、other や none を用意します。

Choice は定義されていない値を生成できません。

これは強力な制約ですが、同時に、

候補集合そのものをコード側で正しく用意する責任がある

という意味でもあります。

Noul

Noul は、

この条件は成立するか

という yes/no 型の意味判断に使います。

結果として yes の確率を取得します。

たとえば、

  • 返金を要求しているか
  • 認証情報を要求しているか
  • 時間的な圧力をかけているか

などです。

複数のラベルが同時に成立し得る場合、それらを1つの Choice に押し込まず、独立した Noul を並べるのが基本です。

Score

Score は、順序のある度合いを表します。

たとえば、

No deadline implied
Wants a reply soon
Business is blocked now

のように、各水準を具体的な状況として定義します。

「低・中・高」のような抽象ラベルだけよりも、各レベルを単独で読んでも意味が分かる表現の方が適しています。

ランキングを作る場合も、各対象について同じ定義の Score を取得し、並び替え自体はコード側で行うのが自然です。


4. State と Question の設計

State

State には、現在の質問に必要な事実だけを渡します。

たとえば、

  • ポリシー
  • 取引情報
  • ユーザー入力
  • チケット本文
  • ID や属性
  • 外部システムから得た最新状態

などです。

複数の情報を渡す場合は、意味の分かる 名前付き JSON にします。

モデルが一般知識として知っているかもしれない情報でも、アプリケーション側で信頼できる最新データを持っているなら、それを state に明示的に渡す方が安全です。

質問から state の一部を参照する場合は、

`ticket.messages[0].text`

のようなバッククォート付きのパスで指します。

観測事実と推論を混ぜない

State 設計では、

  • 外部世界から取得した事実
  • コードが計算した値
  • モデルが以前推論した値

を区別することも重要です。

「前回モデルがそう判断した」という情報を、無条件に新しい観測事実として扱わないようにします。

過去のモデル出力を利用する場合は、

  • いつ取得したか
  • 前提がまだ有効か
  • 再判定が必要か

をコード側で管理します。


5. Question は1問1判断

Question にはいくつか重要な原則があります。

  • Question ID はコード側の識別子であり、モデルには送られない
  • 判断の意味はすべて instructions に書く
  • 1問 = 1つの一貫した判断
  • 独立して利用できる判断軸は別々の質問にする
  • criteria で境界条件を具体的に定義する
  • what / not_for / examples のような対比を使うと境界が明確になる
  • Score のレベルは抽象語ではなく、具体的な状況として記述する

悪い例:

{
  "is_spam": {
    "type": "noul",
    "instructions": "Is message spam?"
  }
}

これでは「spam」という大きな概念を1回で判断させています。

より良い設計では、たとえば次のように分解します。

  • 資格情報を要求しているか
  • 予期せぬ報酬を提示しているか
  • 時間的な圧力をかけているか
  • 送信者表示とドメインに矛盾があるか

それぞれを独立した Noul として取得し、最終的な spam 判定はコード側で重み付けして合成します。

この構造にすると、

  • 各判断の評価ができる
  • 閾値を個別調整できる
  • 誤判定の原因を追いやすい
  • 同じ信号を別用途でも再利用できる

という利点があります。


6. 同じ State に対する独立質問は1リクエストにまとめる

TypeSafe を効率よく使ううえで特に重要なのが、同じ state に対する独立した質問を1回のリクエストにまとめることです。

response = client.system_one(
    state=state,
    questions={
        "department": Choice(...),
        "urgency": Noul(...),
        "frustration": Score(...),
        "refund_requested": Noul(...),
        "policy_supports": Noul(...),
    },
)

これらの質問は 独立して評価され、互いの回答を参照しません。

したがって、将来必要になるかもしれない投機的な枝も、同じ state から判断できるのであれば1回のリクエストに含められます。

その場合、

  • 前提条件は各 instructions に明示する
  • 実際には使わなかった回答はコード側で捨てる

という設計にします。

2回目のリクエストが必要な場合

2回目の System One 呼び出しが必要なのは、たとえば、

  • 最初の回答によって検索すべき証拠が決まる
  • 最初の回答をもとに新しい state を取得する
  • 最初の回答によって次の候補集合そのものが変わる

場合です。

つまり、

依存関係が本当にある場合だけ直列化する

ということです。

逆に、

考えさせる → 答えを見る → 次を考えさせる → また答えを見る

というだけのエージェント型ループを作るのは、TypeSafe の設計意図とは基本的に逆です。

ただし無制限ではない

質問をまとめることは推奨されていますが、リクエストサイズにはモデルごとの上限があります。

そのため、

  • 無関係な state
  • 重複した質問
  • 実際にはほぼ使われない大量の投機質問

まで無制限に追加してよいわけではありません。

コスト、トークン、レイテンシは実測して最適化します。


7. Confidence と Probability を区別する

特に注意が必要なのが、confidence と probability は同じものではないという点です。

Choice / Score

Choice と Score には、

  • probability distribution
  • confidence

があります。

confidence は、確率分布がどの程度1つの答えに集中しているかを要約した指標です。

直感的には、

  • 分布が集中している → confidence が高い
  • 分布が平坦 → confidence が低い

という関係です。

ただし、

confidence = 正答確率

ではありません。

また、

confidence が高い = その処理を自動実行してよい

という意味でもありません。

Noul

Noul には独立した confidence はありません。

yes の probability そのものを使います。

たとえば、

if answers["refund_requested"].noul > 0.8:
    ...

のように扱います。


8. Confidence でゲートする

本番では、

モデルの答え + probability / confidence + 決定的ルール

をコード側で組み合わせます。

たとえば、

answers = response.answers

if answers["department"].confidence < 0.5:
    return escalate_to_human(ticket)

if (
    answers["refund_requested"].noul > 0.8
    and answers["policy_ok"].noul > 0.8
):
    return start_refund()

Choice では、

  • confidence が低い
  • probability distribution が平たい
  • 上位候補が拮抗している

といった状態は、不確実性が高いことを示します。

ただし、そのとき自動実行を止めるかどうかは処理のリスク次第です。

閾値はリスクごとに変える

グローバルに1本の threshold を設定するのではなく、操作のリスクに応じて変えます。

たとえば、

低リスク

  • UI 表示
  • 並び替え
  • 推薦
  • 下書き

では、比較的広く自動化できます。

高リスク

  • 返金
  • 権限変更
  • 決済
  • 外部への確定送信
  • 不可逆な操作

では、より高い confidence や追加の決定的ルールを要求し、それ以外は人へ回します。

公式ドキュメントでも、例として、

  • confidence < 0.5 なら人へ回す
  • 高リスク操作では confidence > 0.9 を要求する

といった構成が示されています。

ただし、これらの数値は普遍的な正解ではありません。

実際の threshold は、対象ドメインの実データを使って評価・校正して決めます。


9. 開発フロー上のベストプラクティス

1. ライブドキュメントを都度確認する

スキル本文は設計思想を理解するための入口です。

API、SDK、モデル、制限事項、cookbook は変更される可能性があるため、実装前にはライブドキュメントを確認します。

Mintlify ベースのドキュメントでは、URL 末尾に .md を付けることで生 Markdown を取得できる場合があります。


2. 汎用分類器を作る前に近い cookbook を探す

TypeSafe には、

  • function calling
  • rerank
  • citation check
  • hierarchical classification
  • guardrails
  • structured-data extraction cascade

などのパターンがあります。

ゼロから巨大な分類器を設計するより、まず近い cookbook がどのように問題を分解しているかを見る方が安全です。


3. 必要ならモデルバージョンを固定する

再現性が重要な場合や、confidence threshold を特定モデルに合わせて調整している場合は、jev-latest のような移動する alias ではなく、具体的な versioned model ID を固定します。

jev-latest は将来別バージョンを指す可能性があり、その場合は同じ入力でも出力分布が変化し得ます。

評価セットも固定しておくと、

  • モデル更新
  • question 修正
  • threshold 修正
  • state 構成変更

の影響を比較しやすくなります。


4. 観測事実と推論結果を混ぜない

state には、事実とモデルの過去推論を区別して入れます。

「前回モデルがそう判断した」というだけの情報を、新しい観測事実として扱いません。

過去の推論結果を再利用する場合も、鮮度や適用条件をコード側で確認します。


5. Choice の候補カバレッジをコード側で保証する

Choice は、定義された候補以外を選べません。

したがって候補集合に漏れがあり得るなら、

  • other
  • none
  • unknown

などを用意します。

値抽出では、

コードが候補を列挙 → モデルが候補を選択 → 必要ならコードが原文から値を取得

という構成にすると、モデルに自由生成させずに済みます。


6. 評価は原子質問と最終判断の両方で行う

評価対象を1段階だけにしません。

個別質問については、

  • Noul の calibration
  • Choice の accuracy
  • Score の一貫性
  • probability distribution
  • confidence と正答率の関係

を評価します。

そのうえで最終システムとして、

  • 最終ルーティング精度
  • false positive
  • false negative
  • 自動処理率
  • 人へのエスカレーション率
  • 高リスク誤実行率

も測定します。

原子質問が正しくても、合成ロジックが悪ければシステム全体は失敗します。

逆に、個々の質問に多少の誤差があっても、合成・ゲート設計によって安全なシステムを構成できる場合もあります。


7. トークンとレイテンシは実測する

「同じ state の質問をまとめる方が効率的」という原則と、

質問をいくら追加してもよい

ということは別です。

  • 不要な state
  • 過剰な投機質問
  • 重複した質問
  • 過度に長い instructions

はコストやレイテンシを増加させます。

最終的には実測値で最適化します。


8. LLM ガードレールとして使うなら TypeSafe を外側に置く

TypeSafe は、別の生成モデルの出力を検証する層としても使えます。

たとえば、

生成モデル
   ↓
TypeSafe による検証
   ↓
十分に安全 → 通す
不確実       → 高コストモデル
高リスク     → 人間

という構成です。

Noul / Choice で、

  • ポリシー違反があるか
  • 引用が主張を支持しているか
  • 必須情報が欠けていないか
  • 出力が指定用途に適しているか

などを判定できます。

ここでも、最終的な実行判断はコード側が持ちます。


10. よくある失敗

代表的なアンチパターンは次のとおりです。

  • 1問に「分析して最適な対応を決めて」と詰め込み、確率の意味が分からなくなる
  • Choice に相互排他的でないラベルを入れる
  • 候補集合に other / none を入れず、無理に最も近い値へ押し込む
  • 一番高い probability の候補を常に採用し、低 confidence を無視する
  • confidence を正答確率だと解釈する
  • confidence が高いだけで高リスク操作を自動実行する
  • 必要なポリシー文書を state に入れず、モデルの記憶に依存する
  • 質問 ID だけ意味のある名前にして、instructions を曖昧にする
  • System One に文章生成やツール実行そのものを期待する
  • スキルをインストールしただけでライブ docs を確認せず、古い SDK や API の前提で実装する
  • 本来コードで処理できる決定的ルールまでモデルに判断させる
  • モデルの前回回答を、新しい観測事実として無批判に state へ戻す
  • cookbook の threshold を、そのまま全ユースケースに適用する
  • 型付き出力であることを「意味的に正しいことの保証」と誤解する

最小の正しい形:サポートチケットの振り分け

from typesafe_sdk import Choice, Noul, Score, TypeSafeClient


def triage_ticket(ticket, policy, customer):
    # 決定的に判断できるものはコードだけで処理する
    if ticket["status"] == "closed":
        return "no_action"

    state = {
        "message": ticket["body"],
        "policy": policy,
        "plan": customer["plan"],
        "open_invoices": customer["open_invoices"],
    }

    with TypeSafeClient() as client:
        response = client.system_one(
            state=state,
            questions={
                "dept": Choice(
                    instructions="Which team should handle this message?",
                    criteria={
                        "billing": "Payment, invoice, subscription",
                        "technical": "Bugs, outages, integration",
                        "sales": "Pricing, plan change, new purchase",
                        "other": "None of the above",
                    },
                ),
                "refund": Noul(
                    instructions=(
                        "Does the customer request a refund or chargeback "
                        "in `message`?"
                    )
                ),
                "policy_ok": Noul(
                    instructions=(
                        "Does `policy` support a refund for the situation "
                        "described in `message`?"
                    )
                ),
                "urgency": Score(
                    instructions="How time-sensitive is `message`?",
                    criteria=[
                        "No deadline implied",
                        "Wants a reply soon",
                        "Business is blocked now",
                    ],
                ),
            },
        )

    answers = response.answers

    # ChoiceAnswer は .choice を使う
    if (
        answers["dept"].confidence < 0.55
        or answers["dept"].choice == "other"
    ):
        return "human_review"

    # Noul は yes probability を直接使う
    if (
        answers["refund"].noul > 0.75
        and answers["policy_ok"].noul > 0.75
    ):
        return "refund_queue"

    return answers["dept"].choice

この例のポイントは単純です。

制御・合成・副作用はコードに置き、意味判断だけを狭い独立質問として並列にモデルへ渡し、不確かなケースはリスクに応じてコード側で扱う。

これが TypeSafe skill を使った実装の基本形です。

特定のユースケース――たとえば人流データの異常判定、サポートチケット、RAG の引用検証、ツール呼び出しの妥当性確認など――へ落とし込む場合も、まず

「コードで決定できる部分」と「意味判断が必要な部分」を分離する

ところから始めます。

その後に、

State → 狭い Question 群 → Choice / Noul / Score → probability / confidence → コードによる合成とゲート → 副作用

という順序で設計すると、TypeSafe の思想に沿った構成になります。

【Gemini / Firebase AI Logic】Structured OutputのHTTP 400原因はSchemaの「maxItems」だった話

Firebase AI LogicのStructured OutputがHTTP 400になるまで:maxItemsと非公開の複雑度制限を切り分けた記録

はじめに

ブラウザアプリからFirebase AI Logic経由でGeminiを呼び出し、音声分析結果をJSONとして受け取る機能を実装しました。

認証、App Check、課金、クォータ、音声送信までは正常に動作したものの、本番用のresponse schemaを指定したリクエストだけが、次のエラーで失敗しました。

HTTP 400 INVALID_ARGUMENT
Request contains an invalid argument.

最終的に、原因はJSON Schemaの構文誤りではなく、配列に指定していた大きなmaxItems値でした。Firebase AI Logicへ送るschemaのmaxItemsを最大4に制限すると、同じ完全なproduction schemaが成功しました。

この記事では、なぜ通常のテストでは見つからなかったのか、どのように原因を切り分けたのか、そしてローカルのデータ契約を弱めずに修正した方法を紹介します。

システム構成

対象は、ブラウザ上でゲーム用サウンドを編集するTypeScript/Viteアプリです。AI機能では、編集した音声を短いWAVに変換し、Geminiに分析させます。

実APIへのアクセスにはAPIキーを直接使用せず、Firebase AI Logicを利用しています。リクエストの成立には、次の条件が必要です。

  • Firebase Authentication
  • Email/Password認証
  • アプリ側の許可UID判定
  • reCAPTCHA Enterprise App Check
  • Firebase production mode
  • Firebase AI Logic
  • 対象Geminiモデル

この構成には、APIキーをブラウザへ直接置かず、不正利用を抑制できる利点があります。一方で、Firebase AI LogicのEmulatorが存在しないため、Auth、App Check、Firebaseのproxy、Geminiのschema検証をまとめて再現するには、本番相当の環境が必要になります。

最初はHTTP 429だった

当初のリクエストはHTTP 429を返していました。通常のRPMやTPM上限には達していませんでしたが、Google AI Studio側の前払いクレジット不足が原因でした。

クレジットを補充すると429は解消し、次はHTTP 400へ変化しました。この時点で、少なくとも次のことが分かりました。

  • 認証経路はGeminiまで到達している
  • 課金・残高によるブロックは解消している
  • 新しい問題はリクエスト内容にある

同じ「AIが利用できない」というUI表示でも、429と400では原因がまったく異なります。エラーを安全な分類と数値HTTP statusに分けて記録することが重要でした。

MockやEmulatorでは再現しなかった

以下のテストはすべて成功していました。

  • TypeScript strict modeのtypecheck
  • ESLint
  • Vitest
  • Firebase Authentication/Firestore/Storage Emulator
  • Mock AIを使ったPlaywright
  • production bundle検査

これらはアプリ側の型、制御フロー、UI、Firestore Rules、監査ログ、privacy boundaryを検証できます。しかし、実際のGeminiモデルがresponse schemaを受理するかどうかは検証できません。

つまり、問題は「本番Hostingでしか発見できない」のではありません。正確には、「実Firebase AI Logicと実Geminiを使う統合環境がなかったため、既存のテストでは発見できなかった」という状態でした。

一度にschemaを変更しない

Googleから返るメッセージは、Request contains an invalid argument.だけでした。どのfieldが原因かは示されません。

そこで、production schemaを段階的に組み立てる診断を用意しました。

最初の診断は次のような構成です。

Stage 検証内容 結果
A 基本テキスト 成功
B System Instruction 成功
C 0.1秒の無音WAV 成功
D 最小response schema 成功
E scalar fields 成功
F nested object 成功
G string enum 成功
H scalar array 成功
I object array 成功
J 完全production schema HTTP 400

これにより、Auth、App Check、音声、System Instruction、structured outputそのものは原因から除外できました。

診断は次の原則で実装しました。

  • 成功したStageはlocalStorageへcheckpointを保存する
  • resetされるまで成功Stageを再送しない
  • 失敗・未実行Stageは何度でも実行できる
  • 一括実行は最初の失敗で停止する
  • 2 requests/minuteなど既存のclient rate limitを守る
  • prompt、WAV、Base64、raw response、token、keyはログへ保存しない
  • 監査にはStage、schema分類、成功可否、安全なerror code、HTTP statusだけを残す

schemaをさらに分割した結果

production schemaを、知覚評価、観察項目、recommendationsへ分けました。

検証内容 結果
8項目のperceptual assessment 成功
observationのfield構造 成功
大きなcategory enum 成功
recommendation基本構造 成功
targetSectionsを含むrecommendations 成功
完全observations HTTP 400
完全production schema HTTP 400

個々のobject、enum、nested arrayは受理されています。しかし、productionのobservationsを組み立てると失敗します。

次に、構造を変えず、配列上限だけをproduction値へ変更しました。

成功する診断では、次の値を使っていました。

{
  "strengths.maxItems": 4,
  "issues.maxItems": 4,
  "evidence.maxItems": 4
}

一方、production schemaは次の値でした。

{
  "strengths.maxItems": 24,
  "issues.maxItems": 24,
  "evidence.maxItems": 12,
  "recommendations.maxItems": 20,
  "warnings.maxItems": 20
}

構造を維持したままproduction上限へ切り替えたStageは、単独でもHTTP 400になりました。逆に、完全なproduction schemaのすべてのmaxItemsを最大4にすると成功しました。

これにより、少なくともこのモデルとFirebase AI Logic経路では、大きなmaxItems値がschema complexityの判定に影響していたと確認できました。

公式ドキュメントから分かったこと、分からなかったこと

Firebase AI Logicの公式資料では、maxItemsはresponse schemaのサポート対象として掲載されています。

Geminiの公式資料にも、structured outputはJSON Schemaのsubsetをサポートし、maxItemsを利用できると記載されています。

また、schemaが大きい、深くネストされている、制約が多い場合にはAPIが拒否する可能性があり、schemaを単純化するよう案内されています。

しかし、次の情報は公開されていません。

  • maxItemsの実質的な上限
  • 複数の配列やnested objectを含む場合の複雑度計算
  • maxItems: 24が拒否され、4なら成功するという閾値
  • モデルごと、Firebase AI Logic経路ごとの差
  • HTTP 400になった具体的なfield

したがって、これは公式ドキュメントの単純な見落としではありません。「schema complexity」という抽象的な制限を、実際の環境で段階的に測定する必要がある問題でした。

修正方法

ローカルschemaの上限をすべて4へ変更すると、アプリ内部のデータ契約まで変わってしまいます。そこで、Firebaseへ送信するschemaだけを縮小しました。

概念的には次のような変換です。

function firebaseResponseSchema(
  schema: Record<string, unknown>,
  options: { maxItemsCap?: number } = {},
): Record<string, unknown> {
  return visitSchema(schema, (key, value) => {
    if (
      key === "maxItems" &&
      typeof value === "number" &&
      options.maxItemsCap !== undefined
    ) {
      return Math.min(value, options.maxItemsCap);
    }

    return value;
  });
}

audio analysis/refinement経路だけ、次のように指定します。

firebaseGenerationConfig(config, responseSchema, {
  maxItemsCap: 4,
});

この結果、境界は次のようになりました。

ローカルschema/parser
  strengths/issues: 最大24
  recommendations/warnings: 最大20
  evidence: 最大12

Firebase AI Logicへ送るschema
  すべてのmaxItems: 最大4

Geminiの出力件数は4に抑えられますが、ローカルのvalidatorは従来の上限を維持します。import済みデータや将来の別経路まで4件へ制限する必要はありません。

また、この変換はaudio AIに限定しました。正常に動作している通常のテキストAI schemaへ、診断由来の制約を不用意に広げないためです。

修正後の結果

修正後、次の条件を満たす完全production schemaが成功しました。

  • Firebase Authentication
  • 許可UID
  • App Check
  • Firebase AI Logic
  • 音声入力
  • System Instruction
  • nested object
  • string enum
  • object array
  • recommendations
  • 全maxItemsを最大4へ縮小した完全response schema

上限を縮小していない診断は引き続きHTTP 400になり、同じ構造を上限4で送る最終診断だけが成功しました。この比較により、修正と成功の因果関係も確認できました。

学んだこと

1. 「サポートされているfield」と「任意の値が受理される」は別

maxItemsは公式にサポートされています。しかし、大きな値を含むschemaが必ず受理されるわけではありません。

2. structured outputの制限は、出力サイズだけではない

maxItemsは実際に24件を生成する指示ではなく上限ですが、その値自体がschema complexityへ影響する場合があります。

3. genericなHTTP 400にはdelta debuggingが有効

schema全体を推測で書き換えるより、成功する最小構造へfieldを一つずつ追加するほうが確実です。

4. 成功checkpointが試行回数を減らす

成功Stageを毎回再実行しないことで、rate limit、費用、待ち時間を抑えられました。

5. 本番ユーザー向けHostingと実API診断環境は分離すべき

今回の問題は実Geminiを呼ばなければ再現できません。しかし、本番ユーザー向けサイトで試行錯誤する必要はありません。App CheckとAuthを維持したFirebase Hosting preview channelなど、本番相当の診断環境を用意するのが次の改善です。

6. ログは詳細であるほどよいとは限らない

音声やpromptを保存すれば調査しやすくなりますが、privacy boundaryを破ってしまいます。Stage、schema分類、HTTP statusだけでも、適切に実験を設計すれば原因を絞り込めます。

まとめ

今回のHTTP 400は、課金、クォータ、認証、App Check、音声形式、enum、nested object、recommendationsの問題ではありませんでした。

原因は、Firebase AI Logicへ送ったproduction response schemaの大きなmaxItems値でした。

同じschema構造 + productionの大きなmaxItems → HTTP 400
同じschema構造 + maxItemsを最大4へ制限 → 成功

公式ドキュメントからschema complexityの可能性は予測できますが、具体的な閾値は分かりません。実環境で安全な段階診断を行い、成功するschemaとの差分を測定する必要がありました。

Structured Outputで説明のないHTTP 400に遭遇した場合は、fieldの対応可否だけでなく、property数、nesting、enum数、required数、そしてmaxItemsなどの制約値も含めて、schemaを段階的に縮小してみる価値があります。

Firebase × Gemini API の落とし穴:「Cloud Billing は有効なのに 429 エラー」の正体と解決法

Firebase や Google Cloud を使って Gemini API をアプリに組み込む際、誰もが一度は遭遇する不気味なエラーがあります。

HTTP 429: RESOURCE_EXHAUSTED
Check billing and quota settings

Google Cloud のコンソールを開いて確認してみても、

  • ✅ クレジットカードは登録されている(billingEnabled: true)
  • ✅ API も有効化されている(generativelanguage.googleapis.com / firebasevertexai.googleapis.com)
  • ✅ Cloud Logging を見ても明確なクォータオーバーのログがない

「課金も有効だし、まだ数回しか叩いてないのになぜ??」 と頭を抱えることになります。

この記事では、このエラーの本当の原因と、1回で解決する手順を解説します。


結論:原因は「Google AI Studio 側の前払いクレジット不足」

結論から言うと、「Google Cloud 側の Billing 紐づけ」と「Google AI Studio 上の Gemini API 利用プラン」は別で管理されている のが原因です。

AI Studio(Gemini API)の仕様上、新規利用や特定のティア(Tier 1〜2)では 「前払い(Prepay)」制度 が適用されます。

Google Cloud の Billing アカウントがどれだけ正常でも、AI Studio 側でプリペイドクレジットが 0 円になっていると、API リクエストが即座に 429 エラーでブロック されます。

💡 補足 アプリ画面に表示される Check billing and quota settings というメッセージは、アプリ側が 429 エラーを一律で一般化した表示にすぎません。Google から「設定を見直せ」と具体的に指定されているわけではないため、ログ追跡で迷子になりがちです。


🛠️ 解決手順(3ステップ)

1. Google AI Studio の Dashboard を開く

  1. ブラウザで Google AI Studio にアクセスし、Firebase/GCP と同じ Google アカウントでログインします。
  2. 左側メニューの 「MANAGE」 > 「Dashboard」 をクリックします。

2. 対象プロジェクトをインポートする

  1. 画面右上の 「☁️ プロジェクトをインポート」 をクリックします。
  2. 利用中の Firebase プロジェクト(例: your-project-id)を選択してインポートします。

3. 前払いクレジットを購入(チャージ)する

  1. インポートしたプロジェクトのステータス欄に 「🔴 クレジットなし」 と表示されているか確認します。
  2. 「クレジットを購入」 をクリックし、必要な金額をチャージします(※最低チャージ額は約 $10 / 約 2,000 円〜)。

❓ 2,000円もチャージして大丈夫?(従量課金との違い)

「テスト利用したいだけなのに 2,000 円も払うの?」と不安になるかもしれませんが、ご安心ください。

  • 一括で 2,000 円が引かれるわけではありません。
  • チャージした金額はプール(デポジット)され、API を呼び出した分だけ数銭〜数円単位で引き落とされます。
  • 例えば Gemini 1.5 Flash などの軽量モデルの場合、数千文字のやり取りでも 1 回あたり 0.00数円レベル です。

個人開発やテスト利用であれば、最低額の 2,000 円分をチャージしておけばかなり長い期間使い続けることができます。


📊 チェックリスト(ハマった時の振り返り)

確認項目 状態 影響
GCP Billing 有効 クレジットカード決済の受け皿(必須)
AI Studio インポート 未実施 AI Studio 側でプロジェクトを認識できない
AI Studio ステータス 🔴 クレジットなし HTTP 429 エラーで即時遮断(今回の犯人)
AI Studio ステータス 🟢 正常 API 利用可能(429 解消)

まとめ

Firebase で AI 機能を開発していて「設定は合っているはずなのに 429 になる」ときは、コードや Logging を掘る前に Google AI Studio の Dashboard を確認しましょう。

「プロジェクトをインポートして少額チャージする」 これだけで一発解決することがほとんどです!