研究

エージェントハーネスを継続的に改善する

Stefan Heule & Jediah Katz読了時間 2分

私たちは、Cursorのエージェントハーネスを、意欲的なソフトウェア製品と同じように構築しています。作業の多くはビジョン主導で、まず理想的なエージェント体験はどうあるべきかという考えから出発します。

そこから、そのビジョンに近づく方法について仮説を立て、それを検証する実験を行い、評価と実際の利用から得られる定量・定性のシグナルを使って改善を重ねます。このプロセスは、適切なオンラインおよびオフラインの計測基盤があってこそ成り立ち、変更によってハーネスが本当に良くなったのかを見極められるようになります。

新しいモデルへの早期アクセスを得ると、こうしたアプローチが一つにまとまります。私たちは何週間もかけて、そのモデルの強みや癖に合わせてハーネスをカスタムし、専用に調整したハーネス上で同じモデルが明らかに速く、賢く、効率的になるまで磨き込みます。

ときには、飛躍的な改善が見つかることもあります。しかし実際には多くの場合、ハーネスの改善とは、小さな最適化を執拗に積み重ね、その積み重ねによってエージェントがソフトウェア開発をよりうまくこなせるようにしていくことです。

コンテキストウィンドウの進化

大規模言語モデルとのやり取りの中核にあるのが、コンテキストウィンドウです。エージェントに何かを構築するよう依頼すると、コンテキストウィンドウはまずシステムプロンプトとツールの説明で始まり、続いて現在の会話の状態、最後にユーザーのリクエストが入ります。

このウィンドウをどう構成し、どう管理するかは、Cursorの歴史の中で大きく進化してきました。

2024年後半に最初のコーディングエージェントを開発した当初、モデルは自力で適切なコンテキストを選ぶのが今よりずっと苦手でした。そこで私たちは、たとえば編集のたびにlintエラーや型エラーをエージェントに提示したり、要求した行数が少なすぎる場合はファイル読み取りを補ったり、さらには1ターンで呼び出せるツール数の上限を設けたりと、ガードレールを整備するために多くのコンテキストエンジニアリングを行いました。

また、各セッションの開始時点でエージェントが常に利用できる、大量の静的コンテキストも提供していました。時期によっては、コードベースのフォルダ構成、クエリと意味的に一致するコードスニペット、ユーザーが手動で添付したファイルの圧縮版などが含まれていました。

こうしたものの大半は、今ではなくなっています。

現在でも役に立つ静的コンテキスト (たとえば、オペレーティングシステム、git status、現在表示中のファイルや最近表示したファイルなど) は一部含めています。しかし、モデルの機能向上に合わせて、私たちはガードレールを減らし、その代わりに、作業中にエージェント自身が取得できる、より多くの動的コンテキストを提供するようになりました。以前の投稿では、動的コンテキストの背景にある手法の一部を詳しく掘り下げて解説しましたが、その多くはその後、ほかのコーディングエージェントにも採用されています。現在、私たちの取り組みの多くは、エージェントが動的にコンテキストを取得し、外部とやり取りする方法をさらに増やすことに集中しています。

動的コンテキストでは、過去の会話、アクティブなターミナルセッション、関連するツールといった追加情報をいつコンテキストウィンドウに取り込むかを、モデルが判断できます。動的コンテキストでは、過去の会話、アクティブなターミナルセッション、関連するツールといった追加情報をいつコンテキストウィンドウに取り込むかを、モデルが判断できます。

ハーネスの変更を評価する2つの方法

ハーネスとモデルは一緒にエージェントの性能を左右しますが、「良い」とは何かを明確に定義するのは簡単ではありません。そこで私たちは、それを捉えるために複数の測定レイヤーを構築しています。

私たちは、独自の評価スイートである CursorBench に加えて、公開ベンチマークも維持しています。これにより、品質をすばやく標準化された形で把握でき、時系列で比較することも可能になります。ただし、どれほど優れたベンチマークでも実際の利用状況を完全には再現できないため、それだけに頼ると重要なシグナルを見落としてしまいます。

そのため、2つ以上のハーネスのバリアントを実運用で並行してデプロイし、実際の利用状況で A/B テストするオンライン実験も行っています。こうしたテストでは、さまざまなメトリクスを使ってエージェントの品質を測定します。たとえば、レイテンシ、トークン効率、ツール呼び出し回数、キャッシュヒット率のように分かりやすいものがあります。これらは傾向を見るうえでは役に立ちますが、エージェントが実際にうまく仕事をこなせたかどうかという、より曖昧で重要な問いには十分に答えられません。

私たちはそれを2つの方法で測定しています。

1つ目は、エージェントが生成したコードの「Keep Rate」です。エージェントが提案した一定のコード変更について、一定時間が経過した後に、そのうちどれだけがユーザーのコードベースに残っているかを確認します。これにより、ユーザーがエージェントの出力を手動で調整する必要があったのか、あるいは修正のためにエージェントに再度対応させる必要があったのかを把握できます。そうした場合は、エージェントの最初の応答の品質が低かったことを示しています。2つ目は、言語モデルを使って、エージェントの最初の出力に対するユーザーの応答を読み取り、意味的にユーザーが満足していたかどうかを捉える方法です。ユーザーが次の機能に進むのは、エージェントが役目を果たしたことを示す強いシグナルです。一方で、ユーザーがスタックトレースを貼り付けるのは、そうではなかったことを示す信頼できるシグナルです。

こうしたオンラインテストによって、有望に見えるアイデアを見送るべきだと分かることもあります。ある実験では、コンテキスト要約のためにより高価なモデルを試しましたが、エージェントの品質への影響はごくわずかで、増加したコストに見合わないことが分かりました。

劣化の追跡と修復

モデルと機能が増えるにつれて、ハーネスは他のソフトウェアと同じように、取りうる状態が増えて複雑になっていきます。それに伴ってバグが入り込む余地も広がり、その多くは大規模に運用してはじめて検出できます。

エージェントのツールは、バグが発生しやすい最も広い領域の1つであり、ツール呼び出しエラーは Cursor のセッションに非常に大きな悪影響を及ぼしかねません。エージェントはしばしば自己修正できますが、エラーはコンテキスト内に残り続け、トークンを無駄にし、積み重なったミスがモデルのその後の判断の質を落とす「コンテキストの劣化」を引き起こします。

失敗したツール呼び出しのあと、エージェントが行き詰まったり、完全に脱線してしまったりすることもあります。ツール呼び出し数やエラー率のようなメトリクスは、エージェントがうまく仕事をしたかどうかを直接測るものではありませんが、より大きな問題を示す兆候にはなります。

未知のエラーは、どれもハーネスのバグを意味するため、私たちはそのように扱います。一方で、多くのエラーは「想定内」です。たとえば、モデルが誤った編集を提案したり、存在しないファイルを読もうとしたりすることは、ときどき起こります。私たちは、こうした想定内のエラーを原因ごとに分類しています。InvalidArgumentsUnexpectedEnvironment はモデルのミスやコンテキストウィンドウ内の矛盾を捉え、一方、ProviderErrorGenerateImageWebSearch のようなツールで発生するベンダー側の障害を捉えます。

このほかにも UserAbortedTimeout などの分類があり、これらを合わせることで想定内のエラーの大半を網羅できます。

今年前半の集中的なスプリントで、すべてのツール呼び出しの信頼性を少なくとも99%、多くは99.9%まで引き上げました。今年前半の集中的なスプリントで、すべてのツール呼び出しの信頼性を少なくとも99%、多くは99.9%まで引き上げました。

私たちは、これらのメトリクスに基づいてアラートを定義し、本番環境に入り込んだ重大な劣化を捉えています。未知のエラーは常にバグであるため、どのツールでも未知のエラー率が固定のしきい値を超えた時点でアラートを発します。ただし、想定内のエラーがハーネスのバグを示しているのか、それとも想定どおりの挙動なのかを見極めるのは難しい場合があります。

たとえば、grep 検索のタイムアウトはツールのパフォーマンス上の問題が原因かもしれませんし、単に コードベース が巨大で、モデルが非効率なクエリを組み立てただけかもしれません。そこで私たちは、想定内のエラーが baseline を大きく上回ったときに発火する異常検知アラートを用意しています。モデルによってツール呼び出しの失敗率が異なる可能性があるため、baseline はツールごと・モデルごとに計算しています。

また、ログの検索方法をモデルに教えるスキルを備えた週次の自動化も実行しており、新たに発生した、または最近急増した問題を見つけ出し、調査内容とともにバックログのチケットを作成または更新します。私たちは、多くの問題の修正を一度に着手するためにクラウドエージェントを大いに活用しており、Linear から直接トリガーすることさえできます。

このプロセスは、エージェントハーネス向けの自動化された「ソフトウェア工場」を形にしていく取り組みの一部です。今年前半の集中的なスプリントを通じて、私たちは予期しないツール呼び出しエラーを1桁分減らしました。

さまざまなモデル向けにハーネスをカスタマイズする

私たちのハーネスの抽象化はすべてモデルに依存しない設計になっており、サポートしている各モデルに合わせて大きくカスタマイズできます。たとえば、OpenAI のモデルはパッチベースの形式でファイルを編集するよう学習されていますが、Anthropic のモデルは文字列置換ベースで学習されています。どちらのモデルでもどちらのツールも使えますが、慣れていないほうを使わせると余分な推論トークンがかかり、ミスも増えます。そのため、私たちのハーネスでは、各モデルに対して学習時に使っていたツール形式を用意しています。

こうしたカスタマイズはかなり深いレベルにまで及び、プロバイダーごとのカスタムプロンプトだけでなく、モデルのバージョンごとの調整まで含まれます。OpenAI のモデルは、指示に対してより文字どおりかつ正確に従う傾向がある一方で、Claude はもう少し直感的で、多少あいまいな指示にも寛容です。

リリース前に新しいモデルへの早期アクセスを得られた場合は、まず最も近い既存モデルのハーネスを土台にして、そこから反復的に調整を進めます。モデルがどこで混乱するのかを見つけるためにオフライン評価を実行し、私たちのチームのメンバーに実際に使ってもらって問題を洗い出し、それに応じてハーネスを調整します。こうした反復を、安心してリリースできると感じられるモデルとハーネスの組み合わせになるまで続けます。

この調整プロセスの多くは、新しいモデルの強みに合わせてハーネスをカスタマイズすることにありますが、ときにはハーネスで緩和できる、モデル固有の癖に出会うこともあります。たとえば、あるモデルで、私たちが「コンテキスト不安」と呼ぶようになった現象が見られました。コンテキストウィンドウが埋まってくると、タスクが大きすぎるように思えるといった予防線を張りながら、作業を拒み始めるのです。私たちはプロンプトを調整することで、この挙動を抑えることができました。

チャット途中でのモデル切り替えを支える

モデルごとに挙動、プロンプト、ツールの形式が異なるため、会話の途中でユーザーがモデルを切り替えられるように ハーネス を設計するのは特に難しい課題です。

ユーザーがモデルを切り替えると、Cursor はそのモデル向けにカスタマイズされたプロンプトとツールのセットを備えた適切な ハーネス に自動で切り替えます。ただし、そのモデルは依然として、別のモデルが生成した会話履歴に対してそれらのツールを適用しなければなりません。しかもその会話履歴は、学習時に想定していた分布から外れている可能性があります。

そこで、チャット途中で別のモデルから引き継いだことをモデルに伝えるカスタム instructions を追加しています。これらの instructions は、会話履歴には出てきても自身のツールセットには含まれていないツールを呼び出さないよう、モデルを誘導する役割も果たします。

モデルが自身のツールセットにないツールを呼び出すのを防ぐモデルが自身のツールセットにないツールを呼び出すのを防ぐ

2つ目の課題は、キャッシュが プロバイダー とモデルごとに異なることです。そのため、切り替えが発生するとキャッシュミスになり、最初のターンは遅くなり、コストも高くなります。これを軽減するため、切り替え時に会話を要約し、モデルに整理された概要を渡すことで、キャッシュの不利を抑えています。ただし、ユーザーが複雑なタスクをかなり進めている場合は、要約によって重要な詳細が失われることがあります。そのため、切り替える明確な理由がない限り、通常は会話全体を通して1つのモデルを使い続けることをおすすめしています。

会話途中でのモデル切り替えに伴う課題を避けるもう1つの方法は、代わりにサブエージェントを使うことです。サブエージェントは新しい コンテキストウィンドウ から開始します。最近では、特定のモデルでサブエージェントを実行するよう、ユーザーが直接依頼できる機能を ハーネス に追加しました。

ハーネスとソフトウェア開発の未来

AI支援によるソフトウェアエンジニアリングの未来は、マルチエージェント化していくでしょう。すべてのサブタスクを単一のエージェントで処理するのではなく、システムは専門特化したエージェントやサブエージェントに処理を委ねるようになります。たとえば、計画を担うもの、高速な編集を担うもの、デバッグを担うものといった具合に、それぞれが最も得意な役割に応じて担当範囲を持ちます。

これをうまく機能させるうえで本質的に問われるのは、ハーネスの設計です。システムは、どのエージェントに任せるべきか、そのエージェントの強みを生かせる形でタスクをどう組み立てるか、そしてその結果をどうつなぎ合わせて一貫したワークフローにするかを判断できなければなりません。そうした連携をオーケストレーションする力は、単一のエージェントではなく、ハーネスに宿ることになります。つまり、ハーネスの設計はこれまでもエージェントの成功にとって重要でしたが、今後はさらに重要性を増していくということです。

カテゴリー: 研究

著者s: Stefan Heule & Jediah Katz