はじめに
AIエージェントとWebサービスを開発するなら、アプリの雛形と一緒に、開発の進め方もテンプレートに入れておくと便利です。
認証やDB接続を用意する。既存の構成を説明する。テスト方法を伝える。どこまで終わったかを確認する。新しいサービスを作るたびにこれを繰り返すのは、なかなか面倒です。
そこで、資格試験の問題演習サービス用のテンプレートに、進捗管理、完了判定、上限付きの再評価、依存グラフ、セッション間の引き継ぎを組み込みました。新しいサービスでは、アプリの共通部分と、AIと開発を進めるための仕組みをまとめて再利用します。
初期設定や再説明、作業の抜け漏れによる手戻りを減らして、サービス固有の開発に早く入るための設計を行なって爆速開発しましょう!!
この記事では、自作テンプレートの構成と運用を紹介します。テンプレートとCLIのソースコードは現時点では公開していないため、コマンドは運用の流れを説明する例として掲載しています。
ループもグラフも、広義のハーネスに含める
本記事では、ハーネスを「AIエージェントが作業を進め、結果を確認し、次の作業へ引き継ぐための周辺の仕組み」として捉えています。
OpenAIの公式記事Codex as a platform: build on the open agent harnessでも、エージェントのループ、状態の維持、ツール利用、実行範囲などを支える仕組みとしてハーネスが説明されています。
この記事では、検証できる単位に分けた作業を「タスク」と呼びます。タスクごとに「何を満たせば完了か」を決めたものが「受入条件」です。
| 設計すること | テンプレートに用意するもの |
|---|---|
| 何を読んで着手するか | 共通ルール、作業分野ごとの短い案内 |
| 何をもって完了とするか | 受入条件、検査結果、実装担当以外によるレビュー、確認の根拠 |
| 不合格だったらどうするか | 評価履歴、残り回数、停止条件 |
| どの順番で進めるか | タスク間の依存関係、着手候補、依存グラフ |
| 次のセッションでどう再開するか | 現在地、次の行動、引き継ぎ記録 |
| どこまで操作してよいか | 依頼範囲、資料の保護、公開や本番変更のルール |
ループエンジニアリングは再評価の進め方、グラフエンジニアリングは作業の関係と順序を設計する部分として、広義のハーネスに含めます。この記事での整理であり、用語の厳密な境界を決めることが目的ではありません。
実装・検査・修正を繰り返す流れを図にすると、次のようになります。
この流れでは、人やAIエージェントと、ターミナルから使う自作CLIが役割を分担します。
| 担当 | 行うこと |
|---|---|
| 実装担当のAIエージェント | コードの変更、必要な検査の実行、修正、結果の記録 |
| 実装担当とは別のレビュー用エージェント | 変更内容と検証結果の確認。この記事では「独立レビュー」と呼ぶ |
| 自作CLI | 記録の形式や完了条件の判定、着手候補の表示、評価・引き継ぎの保存 |
| GitHub Actions | コードのpushやプルリクエストに応じた自動検査 |
アプリの土台と、作業の土台を一緒に配る
今回のテンプレートは、資格試験の問題演習サービスを作るためのものです。
アプリ側は、画面・APIにNuxt / Nitro、認証にBetter Auth、DB操作にPrisma、DB構造の変更と適用履歴の管理にPrisma Migrate、DBにPostgreSQL、データの検証にZodを使っています。言語はTypeScriptです。会員登録、演習、回答履歴、お気に入り、管理画面の問題編集なども共通部分です。課金は初期状態では無効です。
このアプリ基盤に、開発用のハーネスを追加しました。
.
├── AGENTS.md
├── config/
│ └── exam.json
├── content/
│ └── questions.json
├── frontend/ # 画面・API・共有スキーマ・テスト
├── scripts/
│ ├── context.mjs
│ ├── workflow-policy.mjs
│ ├── workspace-policy.mjs
│ └── check-private-sources.mjs
├── docs/verification.md # 検査・レビューの実施結果
├── docs/workflow/
│ ├── README.md
│ ├── state.json
│ ├── task.example.json
│ ├── policy.json
│ ├── handoffs/ # 初回の引き継ぎ時に生成
│ └── contexts/
│ ├── product.md
│ ├── development.md
│ └── operations.md
├── .githooks/pre-push
├── .github/workflows/ci.yml
├── .gitignore
└── .dockerignore
ハーネスのCLIはNode.jsの標準機能だけで動くため、アプリの依存ライブラリを導入せず、DBも起動せずに使えます。記事中のnode scripts/context.mjs statusなどは、scripts/に置いた自作CLIをNode.jsで実行するコマンドです。
コマンド例は、すべてテンプレートのルート、つまりAGENTS.mdやscripts/があるディレクトリで実行します。Node.jsはfrontend/package.jsonのengines.nodeに合うバージョンを用意します。このテンプレートのDocker環境では24系を使っています。
再利用するのは、CLI・設定・共通ルールです。新しいサービスには、以前のサービスのタスク、利用者データ、秘密値、公開の承認記録を持ち込みません。進捗の管理ファイルdocs/workflow/state.jsonは、タスク0件・引き継ぎなしから始めます。
AGENTS.mdには共通ルール、state.jsonには現在地を置く
どのファイルに何を記録するかも、あらかじめ決めておきます。
| 置き場所 | 役割 |
|---|---|
AGENTS.md |
既存構成、変更範囲、検証、秘密情報などの共通ルール |
docs/workflow/state.json |
タスク、依頼の根拠、受入条件、評価履歴、依存関係をまとめる管理ファイル |
docs/workflow/contexts/*.md |
作業分野ごとに、読む資料や守るルールをまとめた短い案内文書 |
docs/workflow/handoffs/*.json |
作業の区切りごとの引き継ぎ |
docs/verification.md |
実際に行った検査・レビューと、その結果 |
たとえばAGENTS.mdには、次のような共通ルールを置きます。これは記載内容の要約です。
- 作業開始時にGitの状態を確認し、既存の変更を保護する。
- 受入条件と検証方法を決めてから着手する。
- 必要な根拠だけを読み、過去の検証を今回の成功扱いにしない。
- 重いテスト・型検査・ビルドは順番に行う。
- 結果と根拠を記録し、独立レビュー後に完了を判定する。
- コミット・push・公開は、その作業への明示指示の範囲で行う。
一時的な「このタスクは資料待ち」「この修正は検査済み」といった情報は、state.jsonへ記録します。進捗の更新先をこのファイルに集約し、案内文書へ同じタスク一覧を複製しません。
作業分野を表す「文脈」と、その短い案内文書である「文脈カード」は、次のように対応させています。
| 文脈のID | 作業分野 | 文脈カード |
|---|---|---|
product |
学習設計・サービス方針 | docs/workflow/contexts/product.md |
development |
実装・検証 | docs/workflow/contexts/development.md |
operations |
資料・データ・配信の運用 | docs/workflow/contexts/operations.md |
新しいセッションでは、まずAGENTS.mdと進捗を確認し、担当する分野の案内を読みます。
git status --short
node scripts/context.mjs status
node scripts/context.mjs start development
node scripts/context.mjs next
statusは進捗と前回記録からの変化、start developmentは実装用の文脈カード、nextは着手できる依頼済みタスクを表示します。これらは読取専用で、タスクを作業中へ変更する操作ではありません。初期状態はタスク0件なので、着手候補がなくても正常です。
毎回すべてのコードや過去ログを説明し直す負担を、この読み始めの手順で減らしたいと考えています。
タスクを登録し、「完了したと言える条件」を決める
ここからは、「新しいサービスの名前と分類を設定する」という依頼を受けた例で説明します。タスクのIDは、一貫してexam_settingsを使います。
進捗を更新する担当者が、docs/workflow/task.example.jsonを参考に、docs/workflow/state.jsonのtasks配列へタスクを1件追加します。担当者は人でもAIエージェントでも構いませんが、同じ進捗ファイルを並行編集しないよう1人にまとめます。
登録するタスクのJSON例を折りたたんで示します。これは説明用の依頼であり、実際には依頼内容・担当者・条件を書き換えて使います。確認の根拠には、検査結果を記録した文書や、確認したソースファイルなどを指定します。
登録するタスクのJSON例
{
"id": "exam_settings",
"context": "development",
"title": "新しいサービスの名前と分類を設定する",
"status": "doing",
"depends_on": [],
"authorization": "requested",
"request_ref": "依頼メモ:新しいサービスの名前と分類を変更する",
"next_action": "設定を変更して関連する検査とレビューを行う",
"evidence": [],
"last_verified_at": null,
"verification_scope": "未検証",
"acceptance": [
{
"id": "checks",
"kind": "automated",
"description": "設定と画面・APIが整合し、関連する既存検査に通る",
"verification": "設定の検査と変更箇所に関係する既存テストを実行する",
"status": "pending",
"evidence": [],
"checked_at": null
},
{
"id": "review",
"kind": "review",
"description": "依頼された変更が反映され、対象外の変更がない",
"verification": "実装担当以外が変更内容と検証結果を確認する",
"status": "pending",
"evidence": [],
"checked_at": null,
"reviewer": null
}
],
"execution": {
"worker": "implementer",
"max_attempts": 3,
"attempts": []
}
}
doingは作業中、requestedは依頼済み、受入条件のpendingは確認前を表します。依頼済みという記録を作る前に、実際のユーザー依頼を確認します。
画面の変更ならPC・スマートフォンでの確認、DB変更なら対象データや履歴の保持など、影響する範囲の条件も加えます。軽い文書修正に毎回フルビルドを要求するような運用にはしません。
タスク登録後、条件の不足を確認するコマンドは次のとおりです。
node scripts/context.mjs gate --task exam_settings
gateは、受入条件・根拠ファイル・依存関係・評価履歴から、完了できるかを判定します。登録直後は条件も評価結果も未確認なので、判定がpendingでも正常です。この場合、未完了を表す終了コード2を返します。
修正ループには、回数と止め方を持たせる
実装して検査すると、修正が必要になることがあります。そのたびに人間が「次はここを直して」と指示する負担を減らすため、依頼の範囲内で、評価結果を見て修正へ進める手順を決めておきます。
検査や修正を行うのはAIエージェントです。CLIのattemptは、その作業で実施した評価の結果を保存します。
タスク例のmax_attempts: 3は、初回を含む総評価回数が3回という意味です。個々のテストコマンドの実行回数とは区別します。
1. 不合格の結果を記録する
検査で失敗が見つかったら、docs/verification.mdに不合格だった条件と原因を記録します。state.jsonでは、該当する受入条件のstatusをfailedにし、実施結果を反映します。
あらかじめ決めた合格基準は保持し、更新するのは判定結果・確認日・根拠です。依頼自体が変わった場合は、その理由を確認し、過去の記録を残して別タスクに分けます。
保存前には、世代番号を確認します。世代番号はstate.jsonのrevisionで、attempt・checkpointの保存が成功するごとに1増えます。statusの先頭にある「世代N」を読み、--expect-revisionへ同じ番号を指定します。
次のコマンド例は、世代0から始め、不合格1回の後に修正して合格する場合です。実際に使うときは、保存のたびに現在の世代番号を確認して数値を置き換えてください。
node scripts/context.mjs status
node scripts/context.mjs attempt --context development --task exam_settings \
--outcome failed --expect-revision 0 \
--summary '検査で不合格だった条件と原因を検証資料に記録した' \
--next '原因を修正して再評価する'
この保存に成功すると、例の世代番号は1になります。state.jsonを手で編集しただけでは世代番号は自動で増えません。番号の更新はCLIに任せます。
残り回数があれば原因を修正して再評価します。最後に許された回数でも合格すれば完了へ進めますが、上限に達した不合格はCLIがneeds_reviewへ変更します。これは原因と計画の見直しが必要な状態です。履歴の削除や上限の繰り返し引上げで再評価を続ける運用にはしません。
2. 修正後に検査・レビューを行い、合格を記録する
修正後の検査と独立レビューが通ったら、実施結果をdocs/verification.mdへ記録します。進捗の更新担当者は、state.jsonの受入条件ごとに次の欄を埋めます。
-
status:passedにする。 -
evidence:結果を記録したファイルのパスを設定する。この例ではdocs/verification.md。 -
checked_at:実際に確認した日付を設定する。 - レビュー条件の
reviewer:実装担当と異なるレビュー担当者を設定する。
たとえば、検査の受入条件を更新する欄の抜粋は次の形です。evidenceはファイルパスの配列で、日付は実際に確認した日へ置き換えます。
{
"status": "passed",
"evidence": ["docs/verification.md"],
"checked_at": "2026-10-27"
}
タスク全体のevidenceにもパスの配列を設定し、last_verified_atとverification_scopeに確認日時・確認した範囲を記録します。日付は2026-10-27のような形式を使います。
その後、合格評価を保存して判定を確認します。例では直前の不合格保存によって世代が1になっています。
node scripts/context.mjs status
node scripts/context.mjs attempt --context development --task exam_settings \
--outcome passed --expect-revision 1 \
--summary '修正後の検査と独立レビューが合格し、結果と根拠を記録した' \
--next '完了を記録する'
node scripts/context.mjs gate --task exam_settings
合格評価の保存後、例の世代番号は2になり、gateの判定はpassedになります。条件の欄を埋めただけでは合格にならず、実施済みの合格評価を保存する必要があります。この段階のタスク状態は、まだdoingです。
3. 完了と引き継ぎを保存する
最後に、対象タスクを指定して完了を保存します。
node scripts/context.mjs status
node scripts/context.mjs checkpoint --context development --task exam_settings \
--status done --expect-revision 2 \
--summary 'サービス名と分類の変更、検査、独立レビューが完了した' \
--next '追加依頼または依頼済みの次のタスクを確認する'
この保存で、例の世代番号は3、タスク状態はdoneになります。流れは、検査・レビューの実施→結果の記入→合格評価の保存→完了判定→完了の保存です。
gateが検査するのは、記録の条件や根拠の存在です。結果を記入した事実だけで、実際にテストを実行したことや、説明内容の正しさまで証明できるわけではありません。検証とレビューを実施してから記録します。
依存グラフで、着手できる作業を整理する
複数の作業があると、順番も必要になります。
たとえば、サービスの設定を変更するexam_settings、その設定を使う分類フィルターを調整するtopic_filters、規約文面を確認するlegal_copyを登録した場合です。IDと依存関係だけを抜き出すと、次のようになります。これは説明用の抜粋で、tasks配列の完全なデータではありません。
[
{ "id": "exam_settings", "depends_on": [] },
{ "id": "topic_filters", "depends_on": ["exam_settings"] },
{ "id": "legal_copy", "depends_on": [] }
]
depends_onには、そのタスクより先に完了している必要があるタスクのIDを入れます。ここでは、試験設定の変更を終えてから分類フィルターを調整します。規約文面の確認は設定変更に依存しないので、担当ファイルが重ならなければ並行して進められます。
依存関係の更新先はstate.jsonのdepends_onに集約し、グラフも同じデータから生成します。
node scripts/context.mjs graph --format mermaid
node scripts/context.mjs graph --format json
mermaidは図として描画できるテキスト、jsonはプログラムで扱えるデータを出力します。nextは、依頼済みで依存作業の完了を確認できる着手候補を表示します。資料待ちのタスクがあっても、独立したタスクを選ぶために使えます。循環した依存関係も検査します。
依存グラフのCLIがエージェントを起動したり、並列実行を配分したりするわけではありません。作業を進めるエージェントが依存関係と依頼範囲を確認するために使います。複数担当で進める場合は変更ファイルを分け、重い検査は順番に実行し、state.jsonの更新担当は1人にまとめます。
引き継ぎを用意すると、次の作業へ戻りやすい
checkpointは現在の進捗を保存し、docs/workflow/handoffs/へ引き継ぎファイルを追加します。タスクIDと--statusを指定した場合は、そのタスクの状態も更新します。attemptによる評価の保存時にも、引き継ぎファイルを作成します。
引き継ぎには要約と次の行動に加えて、次の情報を残します。
- GitのHEAD:作業中のコミットID。
- 変更されたファイルの名前と状態。
-
state.json内のwatch_filesで指定したファイルのハッシュ。ハッシュは、ファイル内容から計算する変更検知用の値です。 - 保存する
state.json自体のハッシュ。
watch_filesには、たとえばAGENTS.mdや検証手順のMarkdownなど、小さな文書・ソースファイルを指定します。引き継ぎJSONには、Gitの差分本文や教材の全文を保存しません。
保存中は、同時書込を防ぐためのロックを作り、指定された世代番号も確認します。指定した番号と現在の番号が違う場合は保存を止め、最新の進捗を読み直します。引き継ぎは追加方式で、過去の記録は上書きしません。
次のセッションでは、statusで最新の引き継ぎ先と変更の有無を確認し、記録した要約・次の行動から再開します。ただし、ハッシュの一致はテストの再実行ではありません。Gitや確認に使ったファイルが変わっていれば、関連する検査をやり直します。
資料保護とCIも、テンプレートへ入れておく
サービス開発では、コードと一緒にGitHubなどへ送れない資料を扱うこともあります。
既定のlocal-only/をGitの追跡対象とDockerのビルド対象から除外し、送信前に追跡対象と履歴のファイル名を検査します。追加の保護対象はdocs/workflow/policy.jsonで設定します。
node scripts/check-private-sources.mjs
現在のファイルを削除しても、過去のコミットから送信される可能性があるため、マージ差分を含む履歴も確認します。名前を照合する検査なので、別名にした資料の中身まで検出するものではありません。保護対象を変えたら、.gitignoreと.dockerignoreの除外設定もそろえます。
.githooks/pre-pushは、pushの直前に送信検査を行うためのフックです。ファイルを置いただけでは有効にならないので、既存のGitフック設定を確認して導入します。
CIは、GitHubへのpushやプルリクエストをきっかけに、GitHub Actionsで自動実行する検査です。このテンプレートでは、次の順で進みます。
- Gitの全履歴を取得し、資料保護・ハーネスの回帰検査・進捗ファイルの形式を確認する。
- 成功後、アプリのAPI・DB・フロントエンド・型・ビルドの検査と、PC・スマートフォンのブラウザ検査を行う。
回帰検査は、以前直した不具合などが再発していないかを確かめるテストです。共通の検査方法まで用意しておくと、テンプレートから作るサービスでも何を確認すべきかを決めやすくなります。
新しいサービスで、実際に差し替えるもの
テンプレートから独立したリポジトリを作ったら、サービス固有の内容を設定します。
| 対象 | サービスごとに決めること |
|---|---|
config/exam.json |
サービス識別子、名前、ロゴ、配色、分類、セット数・問題数 |
content/questions.json |
新規サービスへ初期投入する、確認済みの問題・解説・出典 |
| 開発環境 | 独立したDB、Compose名、ポート、接続URL |
| 運用 | 問い合わせ先、利用規約、公開や課金の条件 |
docs/workflow/state.json |
空のタスク一覧から始め、今回依頼されたタスクと受入条件を登録 |
Compose名は、Docker Composeで作る開発環境を区別する名前です。DBやポートもサービスごとに分離します。すでにDBへ保存した問題は、content/questions.jsonを編集しただけでは更新されません。新規サービスの初期取込と、運用中データの編集を分けます。
AIには、たとえば次のように依頼できます。
このテンプレートを、新しい資格試験向けに調整してください。
AGENTS.mdとdocs/workflow/README.mdを読み、現在のGit状態を確認してください。
今回の対象はサービス名と分類の変更です。
受入条件・変更範囲・検証方法を決めて、state.jsonのtasksへ記録し、
既存の変更を保護しながら実装、検査、独立レビューを進めてください。
結果と次の行動を引き継ぎへ残し、変更したファイルを報告してください。
共通ルールはリポジトリへ置いてあるので、依頼ではサービス固有の目的や条件を中心に伝えられます。画面について依頼者と実装担当の認識が揃っていない場合は、既存の部品を使った小さな試作で確認してから進めます。
改善したハーネスも、次のサービスへ再利用する
テンプレートは、作ったら終わりではありません。
実際のレビューでは、マージでだけ追加された資料を履歴検査で見逃す問題と、引き継ぎの保存先がシンボリックリンクを通じてリポジトリ外を指す場合の保護不足が見つかりました。これらの問題を修正して回帰テストを追加し、共通の改善としてテンプレートへ反映しました。
さらに、先に作っていた別のサービスにも、変更内容を比較して共通部分だけを取り込みました。その際は、サービス固有の資料保護、既存の進捗や引き継ぎ、DB方式を保持しています。既存サービスへ更新を取り込む手順まで共有しておくと、次のサービスにも同じ改善を持っていけます。
おわりに
個人開発のため、開発速度の倍率や削減時間は、まだ計測していません。この記事で紹介したのは、アプリの共通機能に加えて、着手・検証・修正・引き継ぎの手順も再利用するための設計です。
アプリの土台と、AIと開発を進めるための土台を、一緒にテンプレート化する。 これが今回の「爆速開発」で試したかったことです。
参考にした記事
次の記事を、設計を整理する際の参考にしました。本文の構成・ファイル名・CLI・運用ルールは、自分のテンプレートに合わせたものです。