迫りくるループ
原文は Armin Ronacher により に公開されました。 このブログを購読する
もうClaudeに自分でプロンプトを送ることはない。Claudeにプロンプトを送り、何をすべきかを判断させるループを回している。僕の仕事はループを書くことだ。
— ボリス・チェルニー
ここ数ヶ月、コーディングエージェントの上に、単にコーディングエージェントを使うのとは明らかに違う何かを構築する人をどんどん目にするようになった。その一部はPiの上で起きていて、もちろんそれを見るのは嬉しいことだ。とはいえ、パターンはどこでも同じだ。何らかのキューにタスクが積まれ、マシンがそれを拾い、試し、止まる。そしてハーネスが、それが本当に終わりだったのかを判断する。
もし終わりでなければ、ハーネスは同じセッションを続行し、別のメッセージを注入したり、コンテキストを変えて新しいセッションを始めたり、タスクを別のマシンに送ったりする。タスクは、モデル単体なら本来「終わりました」と言うはずの地点を越えても生き続けるのだ。
認めたくないくらい、そのタイプのループのことを考えている。
すべてのコーディングエージェントの中には、すでにエージェントループがある。モデルがツールを呼び出し、結果を取り込み、また別のツールを呼び、ファイルを読んで編集し、テストを実行し、やがて何らかの答えを出す。そのループは、私たちがずっと前からよく知っているものだ。もう一つのループはハーネスレベルのループ、すなわちエージェントループの外側にあるループだ。そのループも新しいものではない。Claude Codeの初期から私たちはその亜種をやってきたが、そのループはエージェント的エンジニアリングの中でますます存在感を増し、ここ数週間はTwitter上の議論を支配し始めている。
まだうまくできていない
現状、正直に言うと、深く思い入れのあるコード――そしてそれは結局、かなりの量のコードなのだが――については、このやり方であまり成功できていない。
理由の一部は好みの問題で、一部はコントロールの問題だ。自分が求めるコードの姿には高いハードルを設けようとしているし、出荷するコードは理解していたい。プレッシャーの中でも、あるいは他の人間との議論の中でも、まずクランカーに説明してもらわなくても、システムが何をしているのかを説明できるでいたい。もちろん、この「コードを理解したい」という欲求が数年後も続いているのかという疑問はある。今のところ、理解が重要だという考えを捨てるには至っていない。
この欲求を前提にすると、自分が注意を払わずに書かれたコード、とりわけループから生まれたコードの体験には、何か欠けているものがある。現行のモデルが生成するコードは、往々にして防御的すぎ、複雑すぎ、局所的な推論に偏りすぎている。強力な不変条件を避ける。不正な状態をそもそもあり得なくする代わりに、フォールバックを追加する。コードを重複させ、質の悪い抽象化を作り出し、不明確な設計をさらなる仕組みでごまかす。さらに悪いことに、これが改善している兆しは今のところほとんど見られない。むしろ、この点に関しては逆方向に進んでいるようにすら感じられる。少なくとも私の好みからすれば、Claude Codeとultracodeのような現在の手放し型ハーネスが生み出すコードは、去年の秋に私たちが作っていたものよりも質が低い。なぜなら、例えばFableを使ったClaude Codeは一つの問題に30分以上も中断なく取り組み続けるが、以前のプロセスはもっと人間がループの中に入っていたからだ。
さらに、モデルは局所的な失敗を見つけると局所的な防御を追加しがちだということはよく知られている。Karpathyが言及していたように、彼らは「例外を死ぬほど恐れている」。重要な不変条件を持つシステム、とりわけ永続化されるデータ形式やコアインフラにおいて、正しい修正は「あらゆる不正なケースを処理する」ことではない。正しい修正は、不正なケース自体を表現不可能に、あるいはそもそも書き込めないようにすることだ。ところが、かなり手動で誘導しても、LLMからは自然にはそうしたコードは出てこない。仮にそうしたコードが自然に出てきたとしても、彼らは今や起こり得ないはずのエラーまで処理しようとする。
この振る舞いをループの背後に置くと、それは増幅される傾向がある。各イテレーションで小さな防御が一つずつ追加されれば、システムは一見より堅牢に見えながら、徐々に理解しづらくなっていく。手を離せば離すほど、そうなる。しかも、こうしたツールを明確な指針なしにジュニアに渡すと、非常に悪いプラクティスを教えてしまうことになる。なぜなら、彼らに「なぜそんなことをしているのか」と尋ねれば、彼らはもっともに聞こえる理屈で自分のやり方を正当化するからだ。
ループがうまくいく場所
同時に、ループというパターンが機能していないふりをするのは不誠実だろう。なぜなら、すでにいくつかの領域では驚くほどよく機能しているからだ。
コードの移植はその一つだ。Bunの一部をZigからRustへ移行するという報告された取り組みをはじめ、大規模な自動移植の印象的な事例はすでに存在する。私自身もMiniJinjaをGoへ移植するのに使い、うまくいった。パフォーマンスの探求も、美しく機能する別の例だ。マシンが実験を試し、ベンチマークを取り、失敗を捨て、探索を続ける。セキュリティスキャンも自然に当てはまるし、ほぼあらゆる種類のリサーチも同様だ。複雑な問題空間を探索し、必ずしも永続的なコードをコミットすることなく報告させるというものだ。これらの多くに共通しているのは、新しいコードを生成するのではなく既存のコードを変換するか、あるいは意図的に寿命の短いコードを生み出すかのどちらかだということだ。概念実証やアイデアを生み出したり、発見を浮かび上がらせたり、あるいは機械的な変換に近いものだったりする。
永続性を必要としない成果物を生み出すループや、明確に検証可能な機械的翻訳の形を作り出すループの方が、ハーネスが目標を機械的に測定する一般的な能力よりも重要だと私は考えている。ループの成功例の多くは、別のLLMをジャッジやオーケストレーターとして使っている。機械的翻訳のケースはバイナリなテストケースで検証できるが、代わりにLLMに判断させることもできるのだ!
例えばClaude Codeは、自分で実行する実験ワークフロー全体を作るのがどんどん上手くなっている。確かに、そこから生まれるコードは粗雑だ。しかしそれは、ワークフローのあるステップが正味の改善や完了をもたらしたかどうかをハーネスがうまく判断できていないというより、モデルの側の問題であることが多い。
ハーネスに必要なのは、続行させるための何らかのシグナルだけだ。それは客観的でもバイナリでもある必要はなく、ただ次のイテレーションを駆動するのに十分役立てばいい。
実験や計測といった一日の退屈な部分を取り除き、アイデアを与えてくれるループは、私はすでに大好きだ。
ソフトウェアを有機体として
一方で、同じループ的手法を使って永続的なコードを書くことには、まだしっくりこない。私が好んで使う比喩は、ソフトウェアが決定論的な機械から有機体へと移行するというものだ。
私は、機械を理解することが奨励される環境でソフトウェアエンジニアになった。理解を深めるために剥がせるレイヤーは常にあった。決定論的で観測可能な振る舞いを示さない機械は、許容されることはあっても、一般的には最適とはみなされなかった。ソフトウェアアーキテクチャの観点からも、決定性を減らすよりも、むしろ高める方向へ進むことが望ましいと考えていた。同様に、コードを理解できることも、疑いようのない目標だった。実際には常に可能だったわけではないが、それでも私たちは、巧みなアーキテクチャによって新しいエンジニアでも複雑なコードベースをナビゲートできるようにコードを書くことに誇りを持っていた。よく設計されたシステムでは、不変条件がどこにあり、どの部分が荷重を支え、どの変更が安全なのかを知っているエンジニアが常にいた。理想的には、そのすべてがきちんと文書化もされていた。その理解が欠けているところは、一般的に改善すべき点とみなされた。
もちろん、その理想は常に厳しいものだった。多くのソフトウェアシステム、特に非常に成功したものは、チームのエンジニアがクリーンに保てていた時期を持っていた。大規模なソフトウェアシステムは、誰か一人の頭の中に収めるには大きすぎ、動的すぎ、外部サービスに依存しすぎていることが少なくない。LLMがなくても、私たちはすでに分散システムをある種医師のように診断している。症状を観察し、仮説を立て、「追加の検査を発注」し、いくつかの処置を試し、また観察するのだ。
しかしLLMによって、私たちはその方向へはるかに大きく、はるかに速く進んでいる。私たちはコードを書くためにLLMを使い、診断や処置のためにも使う。本番障害が発生した後の最初のステップとして、クランカーにログを読ませ、根本原因を提案させ、積極的にパッチを上げさせる世界に、すでに多くのエンジニアが生きている。そうしてできたパッチは、しばしば別のマシンがレビューを引き取り、時には人間の監督なしにmainにマージされることさえある。
明らかにそれは強力だし、魅力的に聞こえることも否定できない。しかしその考えに身を委ねること、特に人間の監視がどんどん少なくなる中でそれを行うことは、もはやシステム全体を以前と同じようには理解できなくなることを受け入れることを意味する。私たちはそれを治療し、監視し、安定させるが、必ずしも理解しているわけではない。
一部のソフトウェアにとっては、それで構わないことに疑いはない。すべてのコードが人間の手によるオーサーシップに値するわけではないし、過去にはもっとひどいコードが書かれていたかもしれない。
だが、すべてのソフトウェアがこのように作られることを私は望むだろうか?
完全にオプトアウトすることはできない
非常に居心地が悪いのは、この完全に機械駆動の未来からオプトアウトすることが、選択肢にならないかもしれないということだ。
セキュリティは今日最も明確な例だ。たとえ自分がソフトウェアを構築するのにループを使わなくても、他の誰かがあなたのソフトウェアに対してループを使うだろう。攻撃者はマシンを常時稼働させるし、攻撃者でなくてもセキュリティ研究者がそうする。そしてその自動化された作業の一部は埃を巻き上げるだけだが、同時に本物の問題も見つけ出す。そしてシグナルもノイズも、自分自身でマシンを問題に投入しない限り、対応がほぼ不可能な量であなたの元にやって来る。
curlの至福の夏についてのDaniel Stenbergの投稿は、メンテナーがすでにどれほどのプレッシャーに晒されているかの良い例だ。私の知る限り、AIは今日のcurlのコア開発において大きな役割を果たしていない。それにもかかわらず、メンテナーは報告に圧倒されており、そのほとんどが今やAIによって生成されたものだ。
攻撃者や報告者がループするなら、防御側も追いつくためにはいずれループする必要が出てくる。直接パッチを書くためではないにせよ、トリアージや再現のためになど、プレッシャーは増していく。
競争の面でも同じことが言える。一部のチームは純粋なスピードで他を凌駕するだろう。少人数のグループがマシンを効果的にオーケストレーションする方法を見つけ出したことで、突然速く動き出すプロジェクトもあるだろう。かつて50人必要だったことを5人でやってのけるスタートアップも出てくる。文字通り、あなたのプロダクトに対してマシンをループで回し、「あっちと同じように作って」と頼む人も現れるかもしれない。そしてもしそのユーザーたちが満足しているなら、それは本当に問題なのだろうか?
すべてのソフトウェアが同じように影響を受けるわけではない。一部の領域ではずさんさは罰せられ、信頼と責任が求められるだろう。しかし多くのソフトウェアは、純粋なスピード、素早い実験、広範なカバレッジがとてつもなく重要となる世界に生きている。
新たな依存を築く
私にとって最も恐ろしいのは、私たちがこれらの新しいマシンに、新しい形で依存するようになることだ。ソフトウェアは常にツールに依存してきた。コンパイラにお金を払わなければならなかった時代を覚えている。これらの新しいツールは、ソフトウェアを作ることに実際のコストが伴っていた時代へのフラッシュバックだ。しかし今やそれは一度きりの支払いではなく、継続的な依存だ。財布の中身への依存だけではなく、認知的な依存でもある。
もしコードベースがループによって生成され、ループによってレビューされ、ループによってパッチが当てられ、ループによって生かされ続けているとしたら、同じクラスのシステムへのアクセスを失ったとき何が起きるだろうか?何らかの貿易規制によって最強のモデルへのアクセスが奪われたらどうなるか?単にコストが耐え難くなったらどうなるか?あなたやあなたのチームが、マシンなしではコードを理解する最後の能力さえ失ってしまったらどうなるか?
私たちは、人間にとって単に保守が難しいだけでなく、保守モデルの一部としてマシンの参加を前提とするコードベースを作り出すかもしれない。これはすでに起きている!至る所で起きているわけではなく、問題視される形で起きているとすら限らないが、私たちはそれをますます目にするようになっている。人々は、自分では完全には説明できないコードをどんどんマージしている。人々は、クランカーが提供する文脈でメッセージを補強したり言い換えたりすることなしには、issueレポートを作ったりチャットで議論したりする能力を失いつつある。あまりにも多くの人々が、要約や文脈付けをますますマシンに頼るようになっている。LLMという間接層を介して私と会話する人々に遭遇する機会も、どんどん増えている。
繰り返すが、それが間違っているとすら限らないかもしれない。だが、それは私たちのやり方にとって大きな変化だ。
未来のハーネス
事態がそちらへ向かっていることに、私はほとんど疑いを持っていない。しかしそこへ行くには、コーディングエージェントだけでなく、あらゆる場所のツールについて何らかの手を打つ必要があるだろう。
単により多くのループをオーケストレーションするだけでは足りない。変更やオーケストレーション、エージェントの可視化を良くしたところで、私たちの理解が戻るわけではない。人間をループの中に揺り戻し、ループによる変更を長期的に読み解けるようにする賢い方法を見つけるか、あるいはますます複雑になるこれらのシステムを構成するより良い方法を見つけるか、どちらかが必要だ。
ここで、Piの役割についての私の考えも変わりつつある。Piは慎重だったし、その慎重さは良いことだと思っている。あらゆるインタラクションが、追従できない変更を加える制御不能なマシンの群れと化す未来は望まない。自己を記述するソフトウェアへの競争に勝とうとするあまり、Piが保守不可能な混乱と化すことも望まないし、Piがこの種のエンジニアリングを推進することも望まない。同時に、Piはハーネスであり、ハーネスは人々がこれらの新しいタイプの実験を走らせる中心にある。
コーディングタスクのためのタスクキュー、エージェントのオーケストレーション、サブエージェント、永続的なセッションは、ますます重要になっていくだろう。懸念を抱き、ループを盲目的に受け入れてはいない私たちでさえ、そうした実験を始めなければならない。そうする必要がある。なぜなら、この未来をいかに統制し、生き延び可能なものにするかを理解する必要があるからだ。
ループを制御する
この投稿から読み取れるように、私はこの未来に対してとても落ち着かない気持ちでいる。恐怖からではなく、これまでのこの技術での経験からくる慎重さゆえだ。
ハーネスループという考えを採用することは、作業が終わったかどうかをハーネスが決めることを意味する。エージェントループでは、モデルがやがて「完了」と言い、私がレビューする。それ以前でさえ、私は通常途中で舵取りをする。私は関与し、その過程で学ぶことを楽しんでいる。ハーネスが駆動するループでは、自分の役割がそもそも何なのかさえよくわからない。「完了」のシグナルさえすべての意味を失い、判断するまた別のマシンへと伝達されるだけになる。私の役割は伝令にまで縮小される。
今日、私はそのように構築されたシステムから出てくるコードの多くが好きではないし、AIの支援で作られたソフトウェアの多くと関わることも楽しめていない。ループは強力だが、責任をどんどん取り除いていく。そして少なくとも今日の時点では、私たちに機械へ身を委ねることを大いに促す。
それでも、このループする未来が私たちの未来になることに、私は疑いを持っていない。今はそれに反感を覚えているという事実にもかかわらずだ。すでに私は、驚くほど小さなチームが信じられないスピードで構築する姿を目にしているし、コードベースがますます不可解で混乱した有機体へと変わり、さらなるマシンによってのみ診断可能になっていくのも見ている。それらのコードベースは、有用であると同時にとっちらかっている。
だから、問題はループするかどうかではない、ということを受け入れつつあると思う。明らかに私たちはループするのだから。むしろ問題は、ループの未来において、いかに判断を放棄せず、いかに良いエンジニアリングの原則を保ち、いかに責任ある人間が監督し続けられるようにし、その過程で正気を保つためにコードのアーキテクチャをどう考え直す必要があるか、ということなのかもしれない。
記事をランダムに読む
コメント
ログインしてコメントする