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?

本記事の内容は私(R2-san)の体験(実装コード)・判断・指示に基づきますが、文章作成は生成 AI が行い、私の監修・確認を経て公開しています。

前回、失敗したら、何も変えない で、AI モデルが出した変更案がファイルへ当たるまでの話を書きました。当てる直前に控えを取り、失敗したら何も変えず、あとから丸ごと戻せるようにする、という話です。

あの記事では飛ばした工程があります。当てたあとに走る検証と、探索の途中で走るコマンドです。この2つを決めているのは、AI モデルでも、R2 本体(Runtime)でもありません。

第2回で、意味を考えるのは AI モデル、事実と状態を保証するのは Runtime、そして許可を与えるのは三者目 = ユーザー、と書きました。今回はその三者目の中身です。

先に結論を書きます。

R2 は、決まったルールを確実に適用するだけで、ルールそのものを決めません。

そして、この記事の山場は、その原則を守っていたつもりで**「検証に通りました」と嘘をついていた**話です。


何をもって検証とするかを、R2 は決めない

変更を当てたあと、R2 は検証を走らせます。テストや lint です。何を走らせるかは、ユーザーが事前に登録したコマンドだけ。登録が無ければ検証は行われず、R2 が「このリポジトリならたぶんこれだろう」と気を利かせることもしません。

そして、検証が行われなかったときの表示を、途中で直しました。コマンドが0件の検証は、内部では「0件の成功」となり、成功として扱われます。それをそのまま「検証に通った」と表示していました。嘘ではないけれど、読む側には嘘です。今は「検証はスキップされた(検証コマンドが登録されていない)」と明示します。

0件の成功を、実行済みの検証の成功として表示しない。

登録が無い場合に、AI モデル側が検証コマンドを提案することもできます。ただしそれは、承認より前には実行されません。提案の一部として、承認の対象に含まれます。

つまり「見て承認したもの」の中に、実行されるコマンドまで含まれているということです。


育つ許可リスト

もう1つ、探索の途中で AI モデルがコマンドを自分で考案する経路があります。「今失敗したテストだけをもう一度走らせたい」「変更予定のファイルだけに lint を掛けたい」といった仮説検証です。

実行するかどうかは、ユーザーが持っている許可リストとの照合だけで決まります。照合のやり方は、意図的にごく単純なままにしてあります。

実行ファイル名の完全一致 + 引数の先頭部分の完全一致

glob なし。正規表現なし。シェル経由の実行なし。

どのルールにも当てはまらないコマンドは、実行せずにユーザーへ聞きます。そして、その場で許可したものをルールとして保存できます。使い込むほど聞かれる回数が減っていく、育つ許可リストです。実行されたかどうかに関わらず、考案されたコマンドは全部そのままの文字列で記録されます。これが監査の背骨です。なお、拒否ルールだけは AI モデルへ知らせません。「これは拒否されます」は、回避のヒントになるからです。

正直に書いておくと、この経路の安全の根拠は「そのコマンドが安全かどうか」ではありません。コマンドが何をするかを機械的に見分けることは諦めていて、根拠はユーザーの事前承認とその場の承認です。契約にも、そう書いてあります。


「検証に通りました」が嘘になっていた

さて、山場です。

検証コマンドとして、こう登録できてしまっていました。

登録された検証コマンド   cd リポジトリの場所 && テストを実行
実際に走ったもの         ディレクトリ移動だけ(&& から後ろは黙って捨てられた)
終了コード               0
出力                     空
R2 の表示                Validation: passed

シェルで日常的に打つ形です。ところが R2 は、コマンドをシェル経由では実行しません(そのほうが安全だからです)。結果、この1本は「ディレクトリ移動」というコマンドに余計な引数がくっついたものとして扱われ、&& から後ろは黙って捨てられました。ディレクトリ移動は成功し、終了コードは 0。

そして R2 は、堂々と「検証に通りました」と表示していました。テストは1件も走っていないのに。

第2回で「AI モデルの主張と、R2 が確認した事実を混ぜない」と書きましたが、これはもっと手前の問題でした。

確認したつもりの事実が、事実ではなかった。

直し方として、実行方式をシェル経由へ変える案は取りませんでした。安全な実行方式のほうを変えたくなかったからです。実行結果を後から怪しむ案(終了コードが 0 で出力も空なら、たぶん何も走っていない)も捨てました。もともと何も出力しないコマンドまで疑うことになり、しかも疑う根拠が推測だからです。

代わりに、受け付ける時点で弾くことにしました。Task を作るより前に、登録されたコマンドを検査して、「書いたとおりには実行されない形」を拒否します。該当したら、Task の記録すら作りません。エラーには、該当したコマンドをそのまま出して、次にどう書けばよいかを添えます。

同じ検査は、AI モデルが考案するコマンドにも、許可リストとの照合よりに掛けています。自動許可のルールに一致していようが、ユーザーがその場で承認しようが、この形は実行しません。

そのうえで、実行そのものにも枠を掛けました。実行時間には既定の上限があり、無制限には設定できません。出力は上限を持ち、超えたら末尾を優先して残します(失敗の要約は末尾に出るからです)。そして、資格情報の値は切り詰めより先に消します。


危険かどうかを、AI に決めさせない

検証の手前、適用するかどうかを決める段階にも、同じ形の線があります。

AI モデルは提案と一緒に「この変更は低リスクです」と言えます。R2 はそれを、危険度を下げる方向には一切使いません。使うのは、危険度を上げる方向と、「分からない」を書き添える方向だけです。

そして、「分からない」は決して「低リスク」ではありません。判定できなかったことを、安全だったことにしない。

ちなみに、この判定を緩めたことが一度あります。ファイルの削除や改名は、当初それだけで高リスクへ昇格していましたが、今は昇格しません。緩められたのは、削除と改名では「対象ファイルの内容が想定どおりであること」を今も無条件に確かめていて、そちらが代わりに効いているからです。


失敗したという事実も、残す

検証が失敗すると、R2 は AI モデルに修正案を求めます。その修正案づくり自体が失敗することもあります。以前は、そのとき何も残っていませんでした。Task を開き直しても「修正を試みたのか、まだ試していないのか」が読めなかった。

素朴な直し方は「Task の失敗理由を、修正試行の失敗で上書きする」です。これは却下しました。Task がそもそもなぜ失敗したのかという情報が消えるからです。それは、ユーザーが対処すべき対象であり、次の修正案の入力でもあります。

代わりに、専用の記録を足すことにしました。試行1回につき、必ず1件。そして、

  • Task の失敗理由は書き換えない
  • 状態も変えない
  • 修正の残り回数も消費しない

最後の1つが、ささやかに気に入っています。修正案づくりが失敗したというこちら側の都合で、ユーザーが使える修正の回数を削らない。

この判断を残した記録には、却下の理由がこう書いてあります ――「対象利用者はログファイルを読まない」。だから、製品の中で読める形にする必要がありました。


実装して分かった難所 ― 待たないほうが親切

最後に、この領域で一番ひやりとしたところを1つ。

同じリポジトリに対して Task を並行して走らせられるようにすると、こういうことが起こり得ます。

Task A が、あるファイルの「元の内容」を控えて、書き換える
その最中に Task B が、同じファイルの「元の内容」を控える
        ↑ これは A が書いた後の内容

  → B の控える「元の内容」は、もう元ではない
  → どちらの控えからも、本当の元へ戻れない

戻せるという保証そのものが壊れます。 前回書いたとおり、R2 は「最後は git がある」を前提にしていません。だからこれは致命的です。

対処は、書き込みの経路にだけ、作業ツリーごとに、書き込みを一度に1つしか通さない鍵を掛けることでした。読み取りの経路には掛けません。そして、その鍵が取れなかったら待たずに「今は実行できない」として失敗させ、その事実を記録に残します。

待たないのは意図的です。待つと、待っている間に何が起きているのか、押した本人に分からなくなります。失敗して、もう一度押してもらうほうが分かりやすい。


まとめ

AI coding agent を作っていると、判断を AI モデルへ寄せるか、Runtime へ寄せるかの二択で考えたくなります。でも実際には、どちらでもない決定がありました。

作ってみて分かったことを3つ。

1. R2 はルールを適用するだけで、ルールを決めない

何をもって検証とするか、どのコマンドを実行してよいか。これはユーザーが決めます。「安全そうだから通す」という判断を、R2 はしません。

2. 「確認したつもり」がいちばん危ない

AI モデルの主張を疑うのは、比較的やさしい。難しいのは、自分が確認したはずの事実を疑うことでした。0件の成功を「検証に通った」と表示していたのも、何も走っていないのに「通りました」と言っていたのも、同じ根です。

3. 禁止を増やすのではなく、育つようにする

許可リストは、使い込むほど聞かれる回数が減ります。その場の承認をルールとして残せるからです。安全を「聞く回数」で買い続けると、いずれ中身を読まないまま承認されます。

現在、この考え方を R2 Fugu Agent Runtime という個人開発の AI coding agent runtime で検証しています。

次回は、**実際のリポジトリに向き合ったときに出てくる問題(複数リポジトリの重ね登録と、多言語の索引)**について書く予定です。


あとがき

この R2(AI エージェント)を作り始めた当初は、開発に使っていた AI エージェントへの不満もあって、それを解消しようという意気込みがありました。ところが今では、開発エージェント(および AI モデル)で不満の多くが解消されて、R2 への開発意欲は減退中です(先行開発のつもりが、いつの間にか後追い開発に変わりました)。特にこれから作り込もうとしていたセッション間のメッセージ機能は、AI モデルのレベルから実装が進んでいて、AI エージェント(R2)のレベルだけでは見劣りがしてしまいます。

記事の内容も、AI モデルの振る舞いではなく(それは AI モデルで変わります)、考え方やアプローチを読み取っていただければと思います。


関連記事

このシリーズ(Qiita・製品設計):

この製品を AI に作らせる開発の進め方については、Zenn に別のシリーズを書いています:

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?