0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

AIが見当違いな実装をするのはなぜか

現在のAIコーディングエージェントは、適切な指示さえあれば人間以上のパフォーマンスを発揮できます。

しかし実際の開発では、「見当違いな実装」や「過剰な作り込み」による手戻りが発生することがあります。その大きな原因のひとつが、AIに必要なコンテキストの共有不足です。

「このAPIはこういう理由でこの設計になっている」「ここではあのテーブルを使ってはいけない」といった暗黙知をプロンプトで伝えきれていないと、AIが誤った方向に実装を進めてしまいます。

これはAIに限らず、人間のチームメンバーへタスクを依頼する際にも起こる共通の課題です。

人間とAIの認知の差をプロンプトだけで埋めるのは難しい

私は「必要なコンテキストさえ正しく渡せれば、AIは完璧に自走できる」と確信しています。

問題は、人間とAIの間にあるコンテキストのギャップです。人間にとっては自明な前提でも、AIが同じ前提を共有しているとは限りません。そして、その認知の差をタスクのたびにプロンプトだけで漏れなく埋めるのは想像以上に困難です。

結果として、必要な前提が伝わらないまま実装が進み、「見当違いな実装」や「過剰な作り込み」につながってしまいます。

必要なコンテキストをAI自身に探させたい

そこで、人間が毎回すべてのコンテキストを説明するのではなく、エージェント自身がリポジトリ内を探索し、過去の設計判断や依存関係を自律的に読み取れる仕組みを作ることにしました。

この仕組みを実現するためのトライアルとして、リポジトリ内にタスク文書用のディレクトリを設け、担当範囲の変更理由と経緯をMarkdownに残す運用を試しています。

この記事では、「コードをSSOT(Single Source of Truth)」としつつ、AIのコンテキストとしてタスクの背景や判断履歴をアーカイブしていく手法をご紹介します。

1. コードだけでは「なぜ」は残らない

コードから分かるのは現在の実装

現在のコードを確認すれば、「どのように実装されているか」は読み取れます。

一方で、

  • なぜこの設計になっているのか
  • どのような制約を前提としていたのか
  • ほかにどのような案を検討したのか

といった、設計に至るまでの経緯まではコードから読み取れません。

人間やAIエージェントが次の変更を検討するためには、現在のコードだけでなく、こうした過去の判断もコンテキストとして不可欠です。

「不採用の理由」はコードには残らない

特に重要だと考えているのが、採用した方針に加えて、「検討した別案をなぜ採用しなかったのか」 を残すことです。

これは、人間とAIが後から同じ選択肢を検討するときの貴重な判断材料になります。

例えば、既存データの更新処理を作る際、「Delete-Insertで洗い替えるか、IDを維持してUpdateするか」を検討したとします。

結果として「IDが変わると他の履歴データとのつながりが切れるため、Delete-Insertは採用しない」と判断したなら、その経緯を文書に残します。

最終的なコードからUpdateを採用したことは分かっても、Delete-Insertを検討した事実や、なぜ採用しなかったのかまでは読み取れません。

過去の判断を次の変更のコンテキストにする

理由が残っていれば、将来AIがDelete-Insertを再提案した際にも、IDを維持する必要があった背景を確認できます。

そのうえで、現在もその制約が成立するかをコードと照合し、変更の妥当性を判断する材料にできます。

ただし、過去の判断は絶対的なルールではありません。残すのは 「その時点での前提と判断」 であり、前提が変われば不採用だった案を再検討しても問題ありません。

継続して守るべき制約は AGENTS.md やテストで明示し、過去の判断履歴とは役割を明確に分けます。

2. 現在の状態はコードに、変更の経緯は文書に残す

現在の実装はコードをSSOTとする

設計の経緯は文書に残しますが、現在の実装の詳細まで文書で管理することはしません。

実装の詳細をコードと文書で二重管理し、同期し続ける負担を避けるためです。

同じ内容を二か所に記載すると変更のたびに双方の更新が必要になり、漏れが生じると内容が食い違ってしまいます。

そのため、現在の実装を確認する際はコードをSSOTとし、文書にはコードから読み取れない目的や判断の理由だけを残します。

タスク文書は進行状態に応じて使い分ける

タスク文書には進行状態を持たせ、実装前には計画として、完了後は変更経緯の履歴として扱います。

完了した文書を後続の変更に合わせて書き換え、現行仕様として保守し続ける運用は行いません。「現在どうなっているか」はコードから確認し、「なぜそうなったか」は過去の文書から確認します。

AIが過去の文書から必要な背景を辿れるよう、文書同士の関係を構造化します。

3. AIが必要なコンテキストを辿れるようにする

タスク文書を変更単位で残す

タスク文書を変更単位で分けて保存します。

文書を変更単位に分割することで、今回の作業に必要な背景を選んで参照しやすくなります。

進行中のタスクを入口にし、依存先や関連する判断をたどり、現行コードと照合することで、読み込む情報を必要な範囲に絞り込めます。

ディレクトリ構成の例を以下に示します。

docs/tasks/
├─ AGENTS.md                         (運用ルール)
├─ index.md                          (未完了タスクの一覧)
├─ example-csv-export.md             (未完了のタスク)
├─ .....md                           (未完了のタスク)
└─ archive/
   ├─ index.md                       (完了タスクの一覧)
   └─ 2026/
      ├─ example-search-api.md       (完了時点のスナップショット)
      └─ .....md                     (完了時点のスナップショット)

ここでは docs/tasks/ に未完了タスクを置き、配下の archive/ に完了タスクを保存しています。

メタデータでタスクの状態と依存関係を表現する

タスク文書はYAML frontmatter付きのMarkdownとして記述し、ディレクトリ全体をOpen Knowledge Format(OKF)のVersion 0.2 の bundleとして管理しています。

OKFは、Markdownにメタデータを持たせて知識を構造化するオープンな形式です。今回はタスクの状態や依存関係をエージェントが読み取り、関連文書を探索するために利用しています。

記述例を以下に示します。

---
type: Engineering Task
title: CSVエクスポート機能を追加する
description: 検索条件に一致するデータをCSVファイルとして出力できるようにする。
status: stable
task_status: open
---

status は、OKFで予約語として定義されている属性で、文書のライフサイクルを表します。
どのような基準で更新するかを、docs/tasks/AGENTS.md に記載しています。

docs/tasks/AGENTS.md (抜粋)
文書ライフサイクルの `status` は次の基準で更新してください。
- `draft`: 重要な設計判断、対応範囲、完了条件のいずれかが未確定で、実装前の合意が必要な状態。
- `stable`: 実装に必要な設計判断と完了条件がレビュー可能な粒度で確定し、残る前提条件や未決事項が明示されている状態。タスク完了を意味しません。
- `deprecated`: 別の正本へ統合済みで、既存参照を切り替える期間だけ残している状態。

task_status は独自に追加したカスタム属性で、タスクの進捗状況を表します。
こちらも同じく、docs/tasks/AGENTS.md に更新基準を記載しています。

docs/tasks/AGENTS.md (抜粋)
タスク進捗の `task_status` は次の基準で更新してください。
- `open`: どの実装単位にも着手していない状態。
- `in_progress`: 1つ以上の実装単位を進行中としている状態。
- `blocked`: 外部判断、依存タスク、環境などの待ちによって進行できない状態。本文に阻害要因と再開条件を記載してください。
- `done`: 文書の完了条件をすべて満たした状態。正本への反映、完了記録の追加、アーカイブへの移動を同じ完了作業で行ってください。

依存関係から必要な背景を辿る

依存関係は depends_on(前提)、blocks(後続)などのカスタム属性で定義します。

前段の記述例の「CSVエクスポート機能」のタスクを docs/tasks/example-csv-export.md に置く場合、メタデータには以下のように記載します。

docs/tasks/example-csv-export.md (フロントマターのみ抜粋)
---
type: Engineering Task
title: CSVエクスポート機能を追加する
description: 検索条件に一致するデータをCSVファイルとして出力できるようにする。
status: stable
task_status: open
depends_on:
  - archive/2026/example-search-api.md
blocks:
  - example-export-button.md
---

この例では、「検索APIを追加するタスク (archive/2026/example-search-api.md)」がCSVエクスポート機能の前提であり、「一覧画面にエクスポートボタンを追加するタスク (example-export-button.md)」がその完了を待つ後続タスクです。パスはタスク文書の配置場所を基準とした相対パスです。

AIは指定された相対パスを使って、先行タスクの結果を確認できます。

また、依存先の名前に加えて「そのタスクの何を前提にしているか」を本文に追記すれば、設計時に照合すべき内容も明確になります。

例えば、「CSVの出力対象は、一覧画面と同じ検索条件で絞り込む。絞り込みには先行タスクで整備した検索処理を利用する」と本文に記載することで、依存先で確認すべき判断が具体的になります。

メタデータを置くだけでAIが自動的に関連情報を完璧に読み込むわけではありませんが、適切な探索手順と組み合わせることで、毎回背景説明を書き直す負担を大幅に削減できます。

必要な文書だけを読み込む

AIが一度に扱える情報量には限りがあるため、参照する文書を絞り込む工夫も必要です。

単にタスク文書をスナップショットとして保存するだけでは読み込み量は減らないため、適切な探索手順とセットで運用します。

進行中のタスクから依存関係を順にたどり、今回の変更に関係する判断だけを確認することで、コンテキストの過剰な読み込みを防ぎます。

例えば、「ユーザー削除時に監査ログを記録する」というタスクを以下のように定義したとします。

docs/tasks/audit-log-user-delete.md (フロントマターのみ抜粋)
---
type: Engineering Task
title: ユーザー削除時に監査ログを記録する
description: ユーザー削除時に、削除対象と実行者を監査ログへ記録する。
status: stable
task_status: open
depends_on:
  - archive/2026/audit-log-storage-policy.md
---

この場合、まず depends_on にある「監査ログの保存方式を決める」タスク文書(archive/2026/audit-log-storage-policy.md) を確認します。

依存先の文書から、記録対象や保存方法など、今回の変更に関連する判断を抽出して確認します。

一方で、同じ監査ログに関する文書であっても、今回の変更と無関係な表示方法や検索機能の判断まで読み込む必要はありません。

依存関係を起点として必要な文書と判断だけを追跡することで、コンテキストの量を最適化できます。

過去の文書と現在のコードをリンクする

過去の判断を確認できても、現在の実装状態が分からなければ次の変更を正しく判断できません。

そのため、タスク文書には「当時の実装コミット」と「現在のSSOTとなるコード」の両方を記録します。

先行タスクを docs/tasks/archive/2026/example-search-api.md に保存した場合の完了時メタデータは以下の通りです。

docs/tasks/archive/2026/example-search-api.md (フロントマターのみ抜粋)
---
type: Engineering Task
title: 検索APIを実装する
description: 検索条件を指定してデータを取得するAPIを作成する。
status: stable
task_status: done
completed: 2026-09-01
implementation_commits:
  - "a1b2c3d"
canonical_docs:
  - ../../../../internal/handler/search.go
  - ../../../../internal/service/search.go
---

task_statuscompleted は作業の完了および完了日、implementation_commits は実装時のコミットハッシュです。
canonical_docs は、現在の実装を確認するための参照先コードを示します(アーカイブ先からの相対パスのため ../../../../ が付きます)。

コミットハッシュがあれば「当時どのような差分が発生したか」を正確に追跡でき、コードのパスがあれば「現在の実装状態」をすぐに確認できます。

これらを紐付けることで、エージェントは「過去の判断」と「現在のコード」を行き来しながら次の変更を検討できるようになります。

4. タスク文書の運用フロー

この仕組みを、開発プロセスの中で次のように運用します。

Step 1. 実装前に目的・実装方針・完了条件を書く

実装前にタスク文書を作成し、目的・実装方針・完了条件を記述します。

人間とAIエージェントが同じ文書を参照し、変更の目的や前提条件を揃えます。着手前には依存タスクの完了状況や本文の前提条件も確認します。

Step 2. 実装中に計画との差分を判断する

実装を進めながら、文書の完了条件と照合します。

計画とコードに差異が生じた場合は、それが不具合なのか妥当な設計変更なのかを判断します。設計変更であれば、その理由を文書側にも反映させます。

計画通りの実装自体を目的にするのではなく、開発プロセスで判明した事実も含めて最終的な判断経緯を残します。

Step 3. 完了時に変更の経緯を追記する

実装結果と検証内容を確認し、当初の計画からの変更点とその理由を完了記録として追記します。

併せて、当時の実装コミットハッシュや現在の参照コード(canonical_docs)へのリンクを記録します。

Step 4. 完了したタスクをアーカイブする

完了記録の追記後、タスク文書を archive/<完了年>/ ディレクトリへ移動します。

インデックスファイルや相対リンクも同じタイミングで更新します。

過去のタスク文書を後続変更に合わせて書き換えたり、現行仕様書へ統合し続けたりする運用は行いません。完了したタスク文書は、その変更時点での前提と判断を保存したスナップショットとして固定します。

補足:仕様駆動開発(SDD)との違い

ここまでの内容は仕様駆動開発(SDD)と類似して見えるかもしれません。

実装前に目的や条件を文書化し、人間とAIが実装方針や完了条件を共有する点は共通しています。

異なるのは、コードの変更に合わせて仕様書を更新・保守し続けない点です。現在の実装はコードをSSOTとし、完了したタスク文書は「その変更を行った時点で、何を目的とし、どのような判断をしたのか」という履歴(スナップショット)として扱います。

おわりに

変更理由や依存関係をリポジトリに残すことで、過去のタスク文書を人間とAIが 「次の変更を考えるためのコンテキスト」 として活用できるようになりました。

運用開始から1ヶ月ほどですが、タスクごとにAIへコンテキストをゼロから説明する手間が大幅に減り、大きな手応えを感じています。

一方で、MarkdownとGitによる管理である以上、メタデータの不一致やリンク切れといった整合性の問題は残ります。今後はCI/CDやスクリプトによる自動チェック体制を整えていく予定です。

今後も「現在の実装はコードから確認し、必要な背景は過去のタスク文書から辿る」という運用をブラッシュアップしていきたいと考えています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?