自動運転するコードベースに向けて

長時間稼働する自律型コーディングに関する私たちの研究には、大きな反響をいただいています。
この取り組みは、現行モデルの限界を押し広げるための社内研究として始まりました。研究の一環として、何千ものエージェントをオーケストレーションし、その挙動を観察するための新しいエージェントハーネスを作成しました。
先月までには、システムは1週間連続で稼働できるほど安定し、私たちの研究プロジェクト(ウェブブラウザ)へのコミットの大部分を自動で行えるようになっていました。このブラウザは外部利用を想定していませんでしたし、コードに不完全な部分が含まれることも想定していました。
それでも、多少のクセがあるとはいえ、何千ものエージェントが協調し、人間の介入ほぼなしで実行可能な成果物を生み出せたことは、共有するに値する一つのマイルストーンだと感じました。それ以降も研究を継続しており、このハーネスがどのように構築されたかについて、より詳しくお伝えしたいと考えています。
あわせて、この研究の一部を、一部のユーザーの皆さまにもお試しいただけるようにします。
背景
この研究プロジェクトは、もともと私の個人的なサイドプロジェクトとして始まりました。
ブラウザは、興味深いベンチマークになりそうだと感じました。最先端モデルの限界がはっきり露呈するのに十分な複雑さがあり、連携して動作する必要がある多くの異なるサブシステムが存在するからです。
当初の計画は、JavaScript をサポートせずにウェブページをレンダリングできるようにすることでした。まず Opus 4.5 にプロンプトを与え、ブラウザエンジンを構築するための詳細な計画を書いてもらいました。そして、その計画がどこまで進むかを見るために、「続けて」と繰り返し促しました。
しかし、これはすぐに行き詰まりました。モデルは自分が何をしているか見失い、まだ程遠いにもかかわらず頻繁に成功を宣言して停止し、複雑な実装の詳細でスタックしてしまいました。ただし、深い知識と知性の片鱗は見えました。小さな単位であれば、良質なコードを書くことができたのです。
根本的な問題は、ブラウザというタスクがあまりに巨大で、サブタスクに分解する必要があることでした。次に、エージェントに対して、エージェントが並行して取り組める主要な作業の依存グラフを立てさせました。タスクごとにエージェントを手動で起動し、止まったら再度促していました。これによりスループットは向上しましたが、結果はさほど良くなりませんでした。エージェント同士がコミュニケーションを取ったり、プロジェクト全体に対するフィードバックを提供したりできなかったのです。システムは、より動的である必要がありました。
その一方で、GPT-5.1(および後の GPT-5.2)は、指示に正確に従う能力でより良い結果を示し始めました。これは長時間稼働するエージェントに適していそうだったので、これらの実験に基づき、OpenAI のモデルを使うようにハーネスを更新しました。
この時点で、ハーネスは JavaScript なしのシンプルなブラウザであれば構築できるようになっていましたが、1 つのエージェントだけで完全なブラウザエンジンを構築するのは、時間的に現実的ではありませんでした。
ここから次のラウンドの研究が始まりました。計算リソースを 10 倍使えば、意味のあるスループットを 10 倍にできるのか?
単一エージェントからマルチエージェントへ
私たちは、Rust ベースのシンプルなハーネスを用意した新しいリポジトリから取り組みを始めました。
分散システムの複雑さに対処する代わりに、十分なリソースを備えた大きな Linux VM(仮想マシン)上でハーネスを単体で動かしました。ハーネスを操作するために VM に SSH 接続し、シンプルなターミナルインターフェースを利用しました。
私たちは、システムに対する適切な可観測性を最初から重視しました。すべてのエージェントメッセージ、システムアクション、コマンド出力をタイムスタンプ付きでログに記録し、セッションを分析・再生できるようにしました。これは手動でレビューする際に役立っただけでなく、大量のデータを Cursor に取り込んで効率的に精査し、すばやくパターンを見つけるためにも有用でした。
自己調整
最初のマルチエージェントのアイデアは、もっとも単純なものでした。互いに対等な役割を持つエージェントが共有の state ファイルを使い、他のエージェントが何に取り組んでいるかを把握し、自分が何に取り組むかを決め、その結果をファイルに書き戻す、というものです。


私たちは、何をすべきかをできるだけ規定せず、代わりにエージェント同士が自律的に協調の仕方を見つけられるようにしました。しかし、このアプローチはすぐに破綻しました。
coordination file は、すぐにさらなる問題を生みました。エージェントはロックを長く保持しすぎたり、解放し忘れたり、許可されていないタイミングでロック/アンロックを試みたりしており、そもそも coordination ファイル上のロックを保持することの重要性を理解していませんでした。ロック機構は間違えやすく、条件がそろったときだけたまたま正しく動くような状態になりがちで、プロンプトを増やしても状況は改善しませんでした。
ロックは競合も増やしました。20体のエージェントが、ほとんどの時間をロック待ちに費やすことで、スループットは 1〜3 体分まで低下しました。私たちは、エージェントに別のエージェントの作業を明示的に待機するためのツールを与えましたが、ほとんど使われませんでした。ロックなしの楽観的並行制御(optimistic concurrency control)アプローチも試しましたが、オーバーヘッドは減ったものの、混乱は解消されませんでした。
エージェント間に明確な構造がないため、どのエージェントも大きく複雑なタスクを引き受けようとはしませんでした。彼らは競合や衝突を避け、プロジェクト全体の責任を負うよりも、小さく安全な変更を選ぶ傾向にありました。
構造とロールの追加
次に、エージェントごとのオーナーシップと責任を明確にするために、ロールを切り分けました。


プランナーはまず、ユーザーの指示に沿って前進するための、具体的なアプローチと成果物を設計します。これはエグゼキューターに引き渡され、エグゼキューターが計画を完全に達成する責任を持つ唯一のリードエージェントとなります。エグゼキューターはワーカー向けにタスクを生成でき、それによって線形にスケールし、スループットを高められます。
継続的な前進と説明責任を保つため、エグゼキューターの処理が終わった後に独立したジャッジが走り、計画が完遂しているか、さらに別のイテレーションを回すべきかを判定します。これにより多くの調整上の問題が解決されました。実行の責任と監督に専念する単一の役割を設けることで、ワーカーは自分のタスクに専念しつつも、システム全体としては確実に成果を出せるようになりました。
観察とヒルクライミング
この設計に行き着くには、システムを綿密に観察する必要がありました。
大きな問題があると、それは多くのエージェントやツール呼び出しで繰り返し発生しがちです。例えば、多数のエージェントが同時に git restore を実行していたため、競合が多すぎることに気づきました。行動が期待どおりになっていない理由を理解するために、Cursor を使ってログを解析し、プロンプトと照らし合わせて比較しました。
最終的に、このシステムは最も遅いワーカーによってボトルネックになっていることが分かりました。仕組みが硬直的すぎたのです。
また、すべての計画を最初に立ててしまうと、新しい問題が見つかったときに、システムが動的に再調整しづらくなります。一部のエージェントは非生産的な方向に進んでしまい、ループの次のイテレーションまで自己修正できなくなります。
継続実行エンジン
次のバージョンでは、独立した planner を廃止しました。
この executor はタスクを生成するだけでなく、目標を達成するための進め方自体も計画するようになりました。唯一のエージェントであるため、計画をどこかに書き出したり、静的で変わらない単一の計画に縛られたり、すべての worker が終わるのを厳密に待つ必要もありませんでした。
新鮮さの確保
すべてのロールのエージェントが長時間の稼働でドリフトしないようにするために、新鮮さを保つメカニズムを導入しました:
scratchpad.mdは追記していくのではなく、頻繁にゼロから書き直すようにしました。- 各エージェントは、コンテキスト上限に到達した際に自動的に要約するようにしました。
- システムプロンプトに自己省察とアラインメント(方針の再確認)のリマインダーを追加しました。
- エージェントがいつでも方向転換し、前提を疑って見直せるよう奨励しました。
このシステムは非常に動的かつ柔軟になりました。コードを積極的に探索し、意思決定を再検討し、ワーカーを管理し、タスクをインターリーブ実行し、常に最新情報を反映し続けられます。さらに、エージェントは指示を最後まで守ることが比較的得意だと分かったため、システムをシンプルに保つ目的で judge エージェントは削除しました。


病的な挙動
これらの改善にもかかわらず、continuous executor は病的な挙動を示し始めました。ランダムにスリープしたり、agent の実行を停止したり、自分で作業をこなそうとしたり、plan を拒否してごく少数のごく狭い範囲のタスクしか spawn しなかったり、worker の変更を正しくマージしなかったり、ループが完了したと早々に主張したりしました。
調査の結果、plan、explore、research、spawn tasks、check on workers、review code、perform edits、merge outputs、そしてループが完了したかどうかを判断する、といったあまりにも多くの役割と目標を同時に与えられていることが分かりました。振り返ってみると、それではオーバーロードしてしまっていたのも当然だと言えます。
最終的なシステム設計
最終的な設計には、ここまでの知見がすべて反映されています。
- ルートプランナーがユーザーの指示の全スコープを担当します。現在の状態を把握し、ゴールに向けて前進するための、具体的で的を絞ったタスクを提示する役割を担います。自分では一切コーディングを行いません。また、自分の出したタスクが実行されているかどうか、あるいは誰に実行されているかについても把握しません。
- プランナーが自分のスコープを細分化できると判断した場合、その一部を完全に委譲されたサブプランナーを生成します。サブプランナーは、その限定された範囲についてのみ、同様の形で完全な責任を負います。これは再帰的に行われます。
- ワーカーはタスクを引き受け、それを完了まで進めることだけに責任を持ちます。ワーカーはより大きなシステム全体については把握していません。他のどのプランナーやワーカーともやり取りしません。自分専用のレポジトリのコピー上で作業し、完了したら、システムがタスクを依頼したプランナーに提出する単一のハンドオフ(引き継ぎ)を書き上げます。
興味深いことに、これは現在の一部のソフトウェアチームの運営方法を反映したものでもあります。


サブプランナーは、ワーカーを高速にファンアウトさせつつ、システム全体に必ずどこかのエージェントが明確なオーナーシップと責任を持っている状態を維持することで、スループットを向上させます。これは、単一のプランナーでは圧倒されて視野が狭くなりがちな大規模プロジェクトやタスクにおいても役立ちます。
ハンドオフには、実行内容だけでなく、重要なメモ、懸念点、想定からの逸脱、発見内容、考察、フィードバックも含まれます。プランナーはこれをフォローアップメッセージとして受け取ります。これによりシステムは継続的に動き続けます。プランナーが「完了」となっていても、アップデートを受け取り続け、最新のリポジトリを取得し、継続してプランニングやその後の意思決定を行えます。
すべてのエージェントがこのメカニズムを持っており、これによって、グローバルな同期や不要なクロストーク(相互干渉)のオーバーヘッドなしに、情報がよりグローバルな視野を持つオーナーへとチェーンを遡って伝播しつつ、システム全体は極めてダイナミックかつ自己収束的な状態を保てます。
インテグレーターの削除
当初は、グローバルな視点を持つ集中型の品質管理を行い、複数のワーカーが同時に push・rebase・コンフリクト解消・merge を試みることで発生する競合を取り除くために、インテグレーターを追加していました。
しかし、これはすぐに明らかなボトルネックになりました。何百ものワーカーがいる一方で、すべての作業が通過しなければならないゲート(いわば「お役所的な手続き」)が 1 つしかなかったのです。プロンプトの変更を試してはみたものの、最終的にはこれは不要だと判断し、システムを単純化するために削除できると結論づけました。
スループットとトレードオフ
このシステムは 1 週間で 1,000 万回のツールコール全体にわたり、ピーク時には 1 時間あたり約 1,000 件のコミットを処理しました。システムの稼働開始後は、私たちによる介入は一切必要ありませんでした。
このスループットを実現するために、意図的にいくつかのトレードオフを行いました。
コミットの正しさ
すべてのコミットの前に常に 100% の正しさを要求していたときは、実効スループットの大きな直列化と低下を引き起こしていました。API 変更やタイプミスのような小さなエラーが 1 つでもあると、システム全体が行き詰まってしまうのです。worker たちは自分のスコープ外にはみ出して、無関係な箇所まで直し始めました。多くの agent が殺到し、同じ問題を修正しようとして互いに踏みつけ合う状態になりました。
こうした挙動は有用でも必要でもありませんでした。ある程度の余裕を許すことで、agent たちは他の問題もすぐに別の agent が直してくれると信頼できるようになります。実際そのとおりで、システムはコードベース全体に対する効果的なオーナーシップと委譲を持っているからです。エラーは発生してもすぐに修正されます。エラー率は小さく一定に保たれ、完全にクリーンになることはめったにないとしても、安定していて管理可能であり、爆発的に悪化することはありません。
これは、理想的かつ効率的なシステムはある程度のエラー率を受け入れる一方で、リリース前に agent が定期的にスナップショットを取り、素早く一通りの修正パスを行うための最終的な「グリーン」ブランチが必要であることを示しているのかもしれません。
同期のオーバーヘッド
複数のエージェントが同じファイルを操作したり、同じコードをリファクタリングしたりすることがあります。これを完全になくそうとしたり、過度に凝った解決策を作り込んだりするのではなく、多少の揺らぎが生じる瞬間は受け入れ、短時間のうちにシステムが自然に収束して落ち着くようにしています。
このアプローチでは追加のトークンを消費し、局所的な競合も発生しますが、システム全体としてはよりシンプルに保てます。モデルを整合させやすく、過負荷にもなりにくく、運用・監視もしやすく、摩擦が少なく、全体としての生産性も高まります。また、過度に複雑なアプローチも避けられます。
インフラに関する知見
それぞれのマルチエージェント実行は、分散システムまわりの時期尚早な複雑さを避けるため、十分なシステムリソースを持つ大型の単一マシン上で動かしました。これは用途によく合っており、ほとんどの実行ではピーク時でも数百エージェント程度で、マシンのリソースを飽和させはするものの、過剰にリソースを割り当ててしまうほどではありませんでした。このアーキテクチャにより、システムメトリクスの観測が容易になり、必要に応じて状態を共有・コピーしやすくなりました。
エージェントのRAM使用量を制限したあと、ボトルネックはディスクになりました。特にモノリス構成のプロジェクトでは、数百のエージェントが同時にコンパイルを行うことで、多数のビルド成果物の読み書きが毎秒数GBのオーダーに達します。これはハーネス全体のスループットに大きな影響を与えました。ここから得られた興味深い教訓は、プロジェクト構造やアーキテクチャ上の決定、開発者体験がトークンおよびコミットのスループットに影響しうるということです。なぜなら、本来は思考やコーディングに時間を費やしたいところが、コードベースの扱い(例: コンパイル)が時間の大半を占めてしまうからです。
一般的な開発環境にも制約や非効率がありました。単一ユーザーのワークスペースでは妥当、もしくは大した問題でないことでも、1台のマシン上で数百のエージェントが同じことを行うと目立ってきます。これを解決する単純な方法としては、各エージェントに専用マシンを与えることが挙げられます。しかし、こうしたプリミティブやツールの一部を少し見直して再設計するだけで、大きな効率向上を見込める「取り組みやすい改善余地」が多くあります。
たとえば、Git や Cargo のような多くのツールは、単純な並行処理制御メカニズムとして共有ロックを利用しています。データベースのような並行システムで確立されたメカニズムを取り入れることで、これらをマルチエージェントシステムでも同様にうまく機能させられるでしょうか?すべてのエージェントがリポジトリのコピーを持っていますが、ほとんどのファイルや成果物は同一です。より高度な本番環境向けストレージシステムに見られるコピーオンライトや重複排除といったシンプルな機能を追加することで、別個のインフラを構築せずとも、典型的には「単一ユーザー」向けのシステムに同様の簡単なメリットをもたらせるでしょうか?
エージェントへの意図の明示
このマルチエージェントシステムに与える指示は非常に重要でした。
当初は、指示そのものを主目的にはせず、まずはハーネスが安定して効果的であることを優先していました。しかし、指示の重要性はすぐに明らかになりました。桁違いに多くの時間と計算資源を使っている点を除けば、典型的なコーディングエージェントと対話しているのと本質的には同じだったからです。これは、最適とは言えない指示や不明瞭な指示も含めて、あらゆるものを増幅します。
最初の指示により多くの時間をかけるのは理にかなっています。結局のところ、エージェントはあくまでエージェントであり、あなたの指示に厳密に従うよう訓練されています。指示がどんなに悪くても、その道筋をたどり、それを変えたり上書きしたりはしません。
私たちは研究プロジェクトで成果を得たかったため、プロジェクトとハーネスの進化に合わせて初期の指示を変更しました。新しいマルチエージェントシステムの運用方法を学びながら、同時にブラウザの構築方法も学んでおり、不十分または曖昧な仕様が出力品質にそのまま反映されるのが見えていました。これはハーネス自体の問題ではありませんでした。ハーネスは単に私たちの指示に正確に従っていただけです。
ブラウザプロジェクトからのいくつかの例を挙げます:
- 当初、指示は仕様の実装とバグつぶしにフォーカスしていました。"spec implementation" のような指示はあいまいで、エージェントは賢く優先度付けするのではなく、ほとんど使われないようなマイナーな機能を深く掘り下げてしまいました。
- 私たちは、ユーザーフレンドリーな範囲内でのパフォーマンス要件が暗黙に共有されていると想定していました。しかし実際には、パフォーマンスと他の目標とのバランスを取らせるには、明示的な指示と厳格なタイムアウトを設ける必要がありました。
- システムの複雑な部分では、エージェントがメモリリークを起こすコードやデッドロックを引き起こすコードを書くことがあります。人間ならこれに気づきますが、エージェントにとっては常に明白とは限りません。そこで、プロセスベースの明示的なリソース管理ツールが必要となり、システムがより優雅に復旧し、防御的に振る舞えるようにしました。
JavaScript のないシンプルなブラウザの最初のバージョンでは、フル機能のブラウザへと発展させるには不適切なアーキテクチャに収束してしまいました。これは初期仕様の不備による失敗でした。
同様に、エージェントには「ブラウザをゼロから実装する」プロジェクトだと伝えていたにもかかわらず、本来であれば自分で実装すべき依存関係を取得してしまったり、適切な実装が進むまでの一時的なスキャフォールディングとしてライブラリを使ってしまったりしました。これは指示面での見落としでした。後の実行では、依存関係に関する方針と「使ってはいけないライブラリ」を明示的に示したことで、この問題は解消されました。
その後の実行では、多数の自己完結した crate への大規模な再構成も行われ、モノリス構造から脱却しました。リポジトリは大きく壊れた状態でしたが、マルチエージェントシステムは数日で動作するコードへと収束しました。これは、システムが協調的かつ知的に作業する強力な能力を持ち、完全に壊れた状態でもそこからさらに悪化したり行き詰まったりせず、立て直せることを示しています。この実行ではコンパイル待ちの時間も大幅に減り、以前の数倍のスループットで動作しました。
アーキテクチャと指示は重要です。エージェントは非常に高いエンジニアリング能力を持ちますが、良くも悪くも、与えられた指示を最後まで忠実に追従します。過度に狭いメトリクスと完全な自由放任とのバランスを取ることは難しく、何が「自明」で何が明示的に述べる必要があるかを見極めるのも同様に難題でした。
これらすべては、意図を引き出し、明示し、理解することの重要性を示しています。そして、このスケールではその重要性がさらに増します。Steerability(制御可能性)と observability(観測可能性)は、今後も探究しがいのある興味深い研究領域になるでしょう。
プロンプトの最適化
プロンプト設計は、この進化プロセスの中で非常に重要な要素でした。
モデルがすでにできることについては指示を出さず、モデルがまだ知らないこと(例:マルチエージェント協調)や、対象ドメインに固有のこと(例:テストの実行方法、デプロイパイプライン)だけを指示したほうがよいと分かりました。モデルを、「エンジニアリングの知識は十分にあるが、あなたの特定のコードベースやプロセスは知らない、非常に優秀な新入社員」のように扱ってください。
制約は指示よりも効果的です。"No TODOs, no partial implementations" は "remember to finish implementations." よりもうまく機能します。モデルは一般に、デフォルトで良い振る舞いをします。制約とは、その境界線を定義するものです。
より高レベルまたはより深いタスクに対しては、チェックボックス的な発想は避けてください。意図については詳細に説明しつつも、「やることリスト」を細かく指定しすぎると、モデルはより広い範囲ではなく、その項目の達成に焦点を当てがちです。また、明示的に挙げなかった項目は、暗黙のうちに優先度を下げることにもなります。一般的には、モデルに判断力と自律性を発揮させたほうがよいことが多いです。
スコープの量について説明する際には、具体的な数値やレンジを提示することが有効であることも分かりました。"generate many tasks" のような指示は、少量の出力になりがちです。これは、保守的なデフォルトで、安全側に倒しつつ、形式的には指示に従っている状態です。"Generate 20-100 tasks" と言うことで、スコープが大きいこと、野心的であるべきことが伝わり、実際に振る舞いが大きく変わるのを観察しました。
システム設計から得られた知見
今回の検証から、いくつかの原則を確立しました。
- システムはアンチフラジャイルであるべき。 同時に動作するエージェント数をスケールさせると、障害の発生確率も高まります。個々のエージェントが失敗してもシステム全体は耐え、ほかのエージェントがリカバリしたり、別のアプローチを試せる必要があります。
- 仮説駆動より経験的アプローチを重視する。 人間の組織や既存のシステム設計を前提に「こうあるべきだ」という仮定から入るのではなく、データと観察に基づいて調整していくことを重視しました。
- スループットを明示的に設計する。 これは、システムを大幅に遅くするような「常に100%正しく動くコード」ではなく、最終的な整合性チェックを前提とした「少量だが安定したエラー発生率」を受け入れる、といった形で、コーディングの他の側面とのトレードオフを行うことを意味します。
この種のシステムは、うまく設計できれば洗練されたシンプルなものになりますが、どの「シンプルなアプローチ」が機能するかは、多くの異なるアプローチを検証してみるまで明確ではありませんでした。現在のシステム設計は、オーバーヘッドを最小限に抑えつつ、有用な形でトークンスループットを線形にスケールさせられています。ハーネスについては、これ以上の大きな改良サイクルは必要ありませんでした。
結論
センスや判断力、方向性は人間が担った一方で、AI はこの研究を高速に反復し探索するうえで、生産性を飛躍的に高める重要な存在となりました。
これは、AI を使って AI を開発し、モデルやエージェント、ハーネスが良くなるほど自己強化的に加速していく「好循環」の AI ループにどこか似ています。私たちは、やがて私たち自身を形作るツールを形作っているのです。
この研究には、今日の一部のソフトウェアチームの働き方と詩的なまでに似たところがあります。これらのモデルは明示的にこのような方法で訓練されたわけではないため、これは創発的な振る舞いであり、結局のところソフトウェアプロジェクトを構造化する正しい在り方なのかもしれないことを示唆しています。
私たちは、非常に長時間動作するエージェントに関する研究を今後も続け、その知見を今後のプロダクトに反映していきます。