AI 支援開発

機能するAIペアプログラミングのための決定論的なパイプライン

AIコーダーは強力ですが、ゲートがなければ逸脱してしまいます。こちらが、状態ファイルと計画の凍結を備え、それをラップする、計画・ビルド・検証・シップのパイプラインです。

この記事は英語の原文をAIモデルが翻訳したものです。表現が原文と異なる場合があります。 英語の原文を読む

AIアシスタントに「すべてやっておいて」という指示でコーディングタスクを渡すと、予測可能な形で失敗します。モデルは有能であり、目の前の狭い範囲のタスクにおいてはしばしば期待以上に有能ですが、どこで作業が終わるのかという感覚を持っていません。ステップ間でコンテキストを失います。気付いた点を修正するために元の目標から逸れてしまいます。一見正しそうなコードを書き、作成とデプロイの間にテストを挟むことなくシップしてしまいます。何かが壊れたとき、直近の40件の編集のうちどれが原因なのか特定できず、クリーンに元に戻すこともできません。

解決策は、より良いプロンプトではありません。それはハーネスです。人間が設計・制御する決定論的なパイプラインでAIの作業をラップします。レビュー、実装、検証、ドキュメント化、デプロイの各段階に、失敗したときに実行を停止する明示的なゲートを設けます。モデルは各ステージ内で創造性を提供します。パイプラインはそれを囲む規律を提供します。この記事では、そのハーネスの構築方法、すべてのステージにゲートが必要な理由、そして全体をスキップすることが正しい判断となる特定のケースについて説明します。

ガイドなしのAIが逸脱する理由

失敗の様式は、名前を付けられるほど一貫しています。それらを理解することで、どのゲートを構築すべきかがわかります。

第一に、コンテキストの喪失が挙げられます。AIアシスタントは、最近の会話の有限なウィンドウ内で動作します。長いタスクではそのウィンドウが埋まり、最も初期の決定がそこから抜け落ちてしまいます。狭いスコープに同意することから始めたモデルは、コードの深い部分に到達する頃には、そのスコープを覚えていません。初期の決定と矛盾する発言をしても、それは嘘をついているのではありません。純粋に、もうそれが見えなくなっているのです。

第二に、スコープクリープが挙げられます。バグ修正を依頼すると、モデルはしばしば隣接する3つの改善可能な点に気づき、それらを改善します。なぜなら、それぞれが局所的には合理的に見えるからです。個々の変更はすべて正当化できます。しかしその合計は、あなたが依頼した4倍のサイズのdiffとなり、開くつもりのなかったファイルにまで手が加えられ、実際の修正はその中に埋もれてしまいます。

第三に、最も大きな損害をもたらすのが検証の欠如です。放置されると、AIはコードを書き、コードが正しく動作することではなく、コードが正しく見えることに基づいて成功を報告します。正しく見えることと、正しいことの乖離は、まさに興味深いバグが存在する場所で発生します。例えば、off-by-oneエラー、間違ったフィルター、ローカルでは成り立っていたがデプロイ環境では失敗した仮定などです。実際にコードを実行するゲートがなければ、モデルの自信は現実から切り離されたものになります。

可逆性の低さが、他の要因を結びつけます。作業が、何がなぜ決定されたのかの記録もない、未分化な一つの大きな変更として提供された場合、ロールバックすることは結び目を解くようなものです。書き留めなかった決定を元に戻すことはできません。

これらのいずれも、モデルの素の能力を信用しない理由にはなりません。これらは、構造化されていない委任を信用しない理由です。その能力は本物です。指示とゲートこそが、それをあなたが責任を持てる出荷された成果物へと変えるのです。

5つのステージからなるパイプライン

ハーネスは固定された一連のステージです。この順序は飾りではありません。各ステージは次のステージが必要とする入力を生成し、それぞれのステージの最後には、実行を続けるために通過しなければならないゲートがあります。

ステージ AIのタスク ゲートのチェック項目 失敗した場合
レビュー (Review) コンテキストを読み、計画を作成し、リスクを挙げる 計画が具体的で、スコープが限定されており、人間が承認する 停止し、計画を修正する
実装 (Implement) 凍結された計画にのみ基づいてコードを記述する コードがコンパイルされ、型チェックが通る 停止し、修正またはロールバックする
検証 (Verify) テストを実行し、不変条件をチェックする テストが成功し、不変条件が維持される 停止し、デプロイに進まない
文書化 (Document) 何がなぜ変更されたかを記録する 変更ログが実際の差分と一致する 停止し、一致させる
デプロイ (Deploy) 通常のリリースパスを通じてシップする デプロイ後のヘルスチェックが成功する ロールバックする

レビューは思考が行われる場所であり、間違いを最も安価に発見できる場所です。モデルは関連するコードを読み、何をするつもりかを書き留め、起こりうる問題点をリストアップします。人間がその計画を読み、承認するか、差し戻します。計画は短く、差分は長いため、計画を承認する方が完成した差分をレビューするよりもはるかに安価です。ここで間違った計画を発見すれば、ロールバックではなく数分のコストで済みます。

実装 (Implement) は、承認された計画をコードに変換します。承認された計画以外のことは行いません。検証 (Verify) は、このパイプラインをガイドなしの委任から切り離すゲートです。コードを実行し、テストが失敗した場合は先に進むことを拒否します。文書化 (Document) は、次の担当者や次のAIセッションがその論理的根拠を再構築できるように、永続的な記録を残します。デプロイ (Deploy) は、人間が使用するのと同じリリースパスを通じてシップし、同じデプロイ後チェックを行い、失敗した場合は中途半端にシップされた状態を残さずにロールバックします。

状態はモデルのメモリ内ではなく、ファイルに保存される

AIアシスタントのワーキングメモリは揮発性です。セッションを閉じる、コンテキストウィンドウを超える、または再起動を強制するエラーが発生すると、モデルが保持していたものはすべて失われます。パイプラインの状態がそのメモリ内にしか存在しない場合、リセットは最初からやり直すことを意味し、半分デプロイされたタスクを最初からやり直すのは危険です。

そのため、パイプラインはその状態をファイルに書き込みます。現在のステージ、下された決定、完了した項目、残りの項目、そのすべてがディスク上に存在し、作業の進行に合わせて更新されます。モデルは各ステップの開始時にこの状態を読み取り、終了時に書き込みます。揮発性のコンテキストは、信頼できる情報源ではなく、永続的な状態のキャッシュになります。

pipeline-state:
  task: "add rate limiting to the public API"
  stage: verify              # review -> implement -> verify -> document -> deploy
  plan_frozen: true
  decisions:
    - "token bucket, 100 req/min per key, chosen over sliding window for simplicity"
    - "limit stored in existing cache layer, no new dependency"
  progress:
    - [x] middleware written
    - [x] unit tests for bucket refill
    - [ ] integration test under concurrent load
  failures:
    - none

その真価は最悪の瞬間に発揮されます。タスクの途中でセッションが停止します。新しいセッションは、以前のコンテキストを何も持たずに開始します。それは状態ファイルを読み取り、作業がどこまで進んでいたかを正確に把握します。計画は確定済み、実装は完了、残るは1つの検証ステップのみです。それは推測したり、さらに悪いことに、完了した作業をやり直して正しいものを元に戻したりするのではなく、そこから再開します。ファイルに状態を保存することによって、AI駆動のタスクが特に陥りやすい中断を乗り越えることができるのです。

計画の凍結がスコープクリープを阻止する

レビュー段階で承認済みの計画が作成されると、パイプラインはそれを凍結します。凍結とは、スコープがロックされることを意味します。実装中、AIは計画に項目を追加したり、計画から項目を削除したりすることはできません。合意されたものを、それ以上でもそれ以下でもなく構築します。

これはスコープクリープによる失敗に直接対処します。凍結がなければ、隣接する事柄に気づいて修正するというモデルの習性が抑制されずに実行され、差分が肥大化します。凍結があれば、その同じ本能は壁に突き当たります。もしモデルが実装の途中で別の変更が必要だと判断しても、単にその変更を行うことはありません。その変更は、人間が確認して承認または却下できる、計画への明示的な修正として記録される必要があります。

ルールは、スコープが絶対に変更できないということではありません。実際の作業では予期せぬ事態が明らかになり、適応を拒否すること自体が失敗です。ルールは、スコープの変更が暗黙のうちに蓄積されるのではなく、可視化され、意図的に行われるということです。記録された修正の短いリストを持つ凍結された計画は、監査可能です。偶然に大きくなった凍結されていない計画は、誰も選択しなかった単なる大きな差分にすぎません。

plan (frozen at review):
  1. add token-bucket middleware
  2. wire it into the public router
  3. unit + integration tests

amendment (recorded during implement, approved):
  4. add a config flag to disable the limit per environment
     reason: staging load tests need it off

パイプラインが触れたすべてのものは、凍結された計画か記録された修正のいずれかにまで遡ることができます。誰も同意しなかったものが差分に現れることはありません。

停止ルールは失敗の連鎖を防ぐ

エラーに遭遇したAIアシスタントは再試行します。これは通常、良いことです。しかし、同じエラーに遭遇し、同じ修正を試みるアシスタントはループに陥り、時には長時間にわたって労力を浪費し、繰り返すたびに状況を悪化させることさえあります。パイプラインには、いつ停止するかについての厳格なルールが必要です。

最初のルールは、繰り返し失敗する回数の上限です。同じステップが2回連続で失敗した場合、パイプラインは3回目の試行を行わずに停止し、問題を人間に提示します。2回同じ失敗が続くということは、モデルが問題を理解していないことを意味し、それ以上自律的に試行してもその理解は生まれません。ただ空回りするだけです。2回で停止することで、起こり得た無限ループを、明確な引き継ぎを伴う有限なものに変えます。

2番目のルールは、破壊的なアクションに対する個別のゲートです。データベースの削除、テーブルのドロップ、履歴の書き換え、force-pushingなど、元に戻すのが困難または不可能な操作は、通常のステップと同じ自動承認を通過しません。その特定のアクションに対して、人間による明確かつ具体的な確認が必要です。その理由は非対称性にあります。元に戻せる間違いのコストはロールバックですが、元に戻せない間違いは取り戻せないデータを失うというコストを伴う可能性があるため、その手前にあるゲートはより厳重でなければなりません。

on step failure:
  record failure in state
  if failures_of_this_step >= 2:
    HALT and surface to human      # no third attempt
  else:
    retry with adjusted approach

before any destructive action:
  require explicit human approval for THIS specific action
  # never covered by a blanket "yes, proceed"

これらのルールを組み合わせることで、被害範囲を限定します。AIは安全で元に戻せる作業の中間領域では自律的に動作でき、パイプラインは、自律性が最も危険となる2つの時点、つまり、AIが立ち往生したときと、元に戻せないことをしようとしているときに、的確に人間をループに介入させます。

なぜラッパーは決定論的でなければならないのか

このパイプラインの中心にあるAIは、本質的に非決定論的です。同じリクエストを2回与えると、2つの異なる回答、2つの異なる実装、同じステップの2つの異なる順序が得られることがあります。その変動性は欠陥ではありません。それは、そもそもモデルを役立つものにしている創造性の裏返しなのです。モデルにはソリューション空間を探索してほしいのです。

それを取り巻くパイプラインは、その逆でなければなりません。ステージは決まった順序で実行されます。ゲートは毎回同じチェックを適用します。状態は同じ場所に同じ方法で記録されます。同じ状態ファイルが与えられれば、パイプラインは同じポイントから再開し、同じことを行います。これこそが、その中心にあるランダム性にもかかわらず、この仕組み全体を信頼できるものにしているのです。

ラッパーの決定論性は、3つの具体的なものをもたらします。再現性:ステージ内のモデルの出力が異なっていても、プロセスを再実行して同じように動作させることができます。監査性:すべての決定とすべてのステージ遷移が記録されるため、何がなぜ起こったのかを事後的に正確に再構築できます。信頼性:予測可能なゲートが出口にあり、その作業が有効になる前にチェックしているからこそ、ステージ内でモデルに真の自律性を持たせて行動させることができます。

この分業こそが、全体の考え方です。AIは創造性、つまり変動と探索から恩恵を受ける部分を担います。パイプラインは規律、つまり決して変動してはならない部分を担います。どちらも他方の仕事はしません。創造的なパイプラインはカオスになるでしょう。規律正しいAIは使う価値がないでしょう。

パイプラインをスキップし、単に質問する場合

ハーネスは無料ではありません。それには実際のオーバーヘッドがあります。計画の作成、状態ファイルの維持、各ゲートの通過、ドキュメントの記録などです。適切なタスクにとって、そのオーバーヘッドは割安です。不適切なタスクにとっては、2分で終わる仕事を窒息させる官僚主義です。

タスクが小規模で、一度きりで、簡単に元に戻せる場合は、パイプラインをスキップしてください。ファイル全体にわたる変数の名前変更。読んで捨てるだけの一時的なスクリプトの下書き作成。あるコードがどのように機能するかについての質問への回答。ブロックの再フォーマット。これらのいずれについても、凍結された計画と状態ファイルを持つ5段階のパイプラインを立ち上げることは、純粋な無駄です。単にモデルに質問し、結果を読んでください。間違いのコストは、一瞥と元に戻す操作だけです。

作業が反復可能で、影響が大きく、元に戻すのが困難な場合、特にデプロイで終わる場合は、完全なパイプラインを使用してください。本番環境に出荷されるものすべて。データベーススキーマに触れる、またはデータを移行するものすべて。チームメイトがその上に構築するものすべて。後で説明または監査する必要があるものすべて。この場合、オーバーヘッドは全くオーバーヘッドではありません。それは、ガイドなしの委任が招く、まさに失敗、コンテキストの喪失、スコープクリープ、未検証のデプロイ、元に戻せない変更に対する、利用可能な最も安価な保険です。

決定的な問いは単純です。間違いのコストはどれくらいですか?答えが「一瞥と元に戻す操作」であるなら、ハーネスをスキップし、モデルを自由に実行させてください。答えが「本番環境のインシデント、データの損失、または追跡不可能なdiffを巡る午後半日のフォレンジック考古学」であるなら、それをラップしてください。プロセスの重みを結果の重みに合わせ、リスクが許す限りAIを高速かつ自由に使わせてください。