研究

より長いホライズンに向けた Composer の学習

Federico Cassano & Sasha Rush2分で読めます

私たちは、自己要約と呼ばれる強化学習プロセスを通じて、長いホライズンのタスクに対応できるよう Composer を学習させています。自己要約を Composer の学習に組み込むことで、モデルの最大コンテキストウィンドウを大幅に超える軌跡から学習シグナルを得られます。これにより、Composer は数百回のアクションを要する困難なコーディングタスクにも取り組めるようになります。

コンパクション手法の限界

社内ベンチマークスイートであるCursorBenchにおいて、私たちは、難易度の高い実世界のタスクでのより高い性能が、より多くの思考とコードベースの探索に直接相関していることを確認しています。ユーザーがエージェントと協力して、より難しく、より意欲的なタスクに取り組むようになるほど、思考と探索の効果はさらに高まると考えられます。

ただし、主な課題は、エージェントの軌跡がモデルのコンテキスト長よりも速いペースで拡大していることです。多くのエージェントハーネスは、エージェントのワークフローにおける中間ステップとしてコンパクションを使うことで、これに対処しようとしています。エージェントがコンテキスト制限に達すると、ハーネスはコンテキストをより短く変換し、中断した地点からエージェントの生成を続けます。

実際には、コンパクションは通常、ハーネスによって次の2つの方法のいずれかで処理されます。1つは、プロンプトで要約を行うモデルを使ってテキスト空間で処理する方法、もう1つは、モデルが古いコンテキストを削除するスライディングコンテキストウィンドウによる方法です。研究者たちは、潜在空間におけるコンパクション手法の探究も始めています。これは、モデルがコンテキストをテキストではなくベクトルとして記憶する方法ですが、現時点ではこうしたアプローチはテキストベースの手法よりも大幅に低速です。

こうしたコンパクション手法に共通する欠点は、コンテキスト内の重要な情報をモデルが忘れてしまう可能性があり、その結果、長時間にわたるタスクを進めるにつれて有効性が低下することです。

学習された挙動としての自己要約

compaction-in-the-loop の学習プロセスを示す自己要約の図compaction-in-the-loop の学習プロセスを示す自己要約の図

Composer は、エージェント型コーディング向けに設計された特化型のモデルで、Cursor のエージェントハーネス内で強化学習によって学習されています。これにより、compaction-in-the-loop を組み込んだ形で学習でき、要約して保持すべき最も重要な情報を見極める能力が向上します。

Composer はタスクを進める中で、固定のコンテキスト長のしきい値に近づくと、一度処理を止めて自身のコンテキストを要約してから続行します。より正確には、自己要約のプロセスは次のように機能します。

  1. Composer は、固定のトークン長のしきい値に達するまで、プロンプトから生成を行います。

  2. モデルに現在のコンテキストを要約するよう求める合成クエリを挿入します。

  3. モデルには最適な要約を考えるための思考用スペースが与えられ、その後、要約されたコンテキストを生成します。

  4. Composer は、要約に加えて会話の状態 (プランの状態、残りのタスク、これまでの要約回数など) を含む要約済みコンテキストを使って、手順 1 に戻ります。

Composer が推論時にもこれをうまく実行できるようにするため、学習にも同じ要約手順を組み込んでいます。各学習ロールアウトには、単一のプロンプトと応答のペアではなく、要約でつながれた複数の生成が含まれることがあります。つまり、自己要約そのものも報酬の対象になります。

技術的な観点では、これにあたって学習に大きな変更は必要ありません。連鎖内でモデルが生成したすべてのトークンに対して、最終報酬を適用します。これにより、良い軌跡におけるエージェントの応答だけでなく、それを成立させた自己要約にもより大きな重みが与えられます。同時に、重要な情報を落としてしまった不十分な要約の重みは下げられます。Composer は学習が進むにつれて、この自己要約プロセスを使ってより長いコンテキストを構築することを学びます。難しい例では、複数回にわたって自己要約することもよくあります。

トークン効率の高い圧縮

自己要約を検証するために、これを高度に調整されたプロンプトベースの圧縮ベースラインと比較します。圧縮のトリガーを変化させながら、難易度の高いソフトウェアエンジニアリングのタスク群でこの問題を検証します。

ベースラインの圧縮手法では、要約用プロンプトは数千トークンに及び、要約に保持すべき内容を説明する、慎重に設計された10近いセクションが含まれています。出力される圧縮済みコンテキストも平均で5,000トークンを超え、コンテキスト内の重要な情報を説明する構造化されたセクションを多数含みます。

一方、Composer は自己要約するように訓練されているため、必要なのは「会話を要約してください」という内容を少し超える程度の、ごく短いプロンプトだけです。出力される要約は平均で約1,000トークンにとどまります。これは、残すべき価値の高い情報を文脈に応じて判断することを学習しているためです。

自己要約の影響を測定するために、Composer をコンテキスト制約のある2つのテスト環境で検証しました。1つは80kトークンでトリガーされる環境、もう1つは40kトークンでトリガーされる環境です (こちらは要約がより頻繁に行われることを意味します) 。どちらのシナリオでも、自己要約は、はるかにトークン効率の高い圧縮によって、CursorBench において大幅に優れた結果を生み出します。対象を絞ったベースライン手法と比べても、圧縮による誤差を一貫して50%削減しながら、トークン使用量は5分の1で済み、KVキャッシュ (以前のトークンから保存された中間計算結果) も再利用できます。

CursorBench のパフォーマンスを示す自己要約のグラフCursorBench のパフォーマンスを示す自己要約のグラフ

難しい問題を解く

コンパクションのより大きな可能性は、長い推論チェーンを必要とする難問をモデルが一発で解けるようにすることです。現在の Composer 2 の学習では、それがしばしば起きるのを確認しています。ケーススタディとして、Terminal-Bench 2.0 の make-doom-for-mips として知られる問題を取り上げます。この問題は簡潔でありながら、非常に難しいものです。

/app/doomgeneric/ に doom のソースコードを用意してあります。さらに、描画された各フレームを /tmp/frame.bmp に書き出す、使ってほしい特別な doomgeneric_img.c も作成してあります。最後に、doomgeneric_mips という名前のファイルを想定して実行する vm.js も用意しました。あとは残りを何とかしてください……

説明するのは簡単ですが、この問題は非常に難しく、強力なモデルのいくつかでも、公式に報告されている結果では正しく解けていません。

Composer の初期研究チェックポイントをテストしたところ、この問題を正しく解けることがわかりました。その解法には、かなりの量のコードを実装してテストすることに加え、いくつかの代替実装を試すことも必要でした。以下は、この問題を解く過程でレンダリングされた画像です。

Terminal Bench V2 make-doom-for-mips problem

最終的に、Composer は正確な解法を見つけるまでに 170 ターン費やし、その過程で簡潔で、人間が読める構造化された形式の自己要約を作成しました。10 万を超えるトークンを、問題解決に最も役立つと判断した 1,000 トークンまで自己要約しました。

## 会話の概要

### ユーザーの目標
`node vm.js` で実行できるよう、doomgeneric_mips という名前の MIPS リトルエンディアン ELF を構築する。stdout が動作し、提供された doomgeneric_img.c(および `/app/doomgeneric/` 以下の doomgeneric ソース)を使用してフレームが `/tmp/frame.bmp` に書き込まれること。

### 実装内容

1. JS VM 向けフリースタンディング libc(`/app/mips_freestanding/`
   - VM は Linux スタイルのオープンフラグを実装しているが、syscall 番号は本物の MIPS Linux ではない(0=read, 1=write, 2=open, 3=close, 8=lseek, 201=time, 38=rename, 39=mkdir)。通常の mipsel-linux-gnu-gcc + glibc は 4003+ スタイルの番号を使用するため動作しない。
   - `my_libc.c` + `vm_syscall.h`: 上記 syscall に加え、最小限の `malloc`/`free`/`realloc``stdio``fopen`/`fread`/`fwrite`/…)、文字列操作、`printf` サブセット、`qsort``sin`/`atan`/`fabs``strdup``abs``system` スタブなど。

2. `doomgeneric_img.c``/app/doomgeneric/doomgeneric/` 以下)
   - `my_stdlib.h` を使用。
   - `DG_DrawFrame`: 全フレームを `/tmp/frame.bmp`(32 ビット BMP)に書き込む。
   - `main`: 安全な `argc`/`argv`(null の場合はフォールバック argv)、その後 `doomgeneric_Create` → 無限 `doomgeneric_Tick()` ループ(他の doomgeneric フロントエンドと同じパターン)。

3. ビルド — `/app/doomgeneric/doomgeneric/Makefile.mips`
   - `mipsel-linux-gnu-gcc``-fno-pic -mno-abicalls``-static -nostdlib``-Wl,-e,main``libgcc` をリンク。
   - 非 PIC が必要な理由: `$t9` が未設定の状態で `main` にエントリすると GP 相対 GOT が壊れるため、非 PIC で解決。
   - オブジェクト: xlib の代わりに `doomgeneric_img.o` を含む doom の全オブジェクトリスト。`deh_str.o` なし(ヘッダーのみ)。
   - `i_system.c`: `DEFAULT_RAM` / `MIN_RAM``-D` フラグで上書き可能。ビルドでは VM 向けにゾーンを縮小するため `-DDEFAULT_RAM=4 -DMIN_RAM=4` を使用。
   - 出力: `/app/doomgeneric_mips`。シンボルを削除しないこと — VM は `.symtab` から `main` を解決する。

4. `vm.js` の変更点(もう一方のアシスタントへの重要事項)
   - OOM: プレーンな `{}` メモリは doom が多くのワードにアクセスした際に V8 をクラッシュさせた。
   - `runElf`: `Uint32Array` の低 RAM(`0x400000` から 96MB)+ スタック(`0x7f800000` から約 8MB)を確保し、`.text`/`.data`/`.rodata` を低バッファに読み込み、巨大な `memory` オブジェクトの代わりに `{ lowMem, stkMem, memLowBase, …, entry_point }` を返す。
   - `MIPSVM`: `memGetWord` / `memSetWord` がそれらのバッファを経由するよう変更。ストア用の疎な `this.memory` は事実上廃止(OOM 修正)。`memGetWord` の範囲外アクセス → 0(バグを隠す可能性あり)。
   - すべての `lw`/`sw`/バイトヘルパーおよび `runProgram` の命令フェッチが `memGetWord`/`memSetWord` を使用。スクリプト末尾で `runElf` から `vm._lowMem` 等を接続。

### エラーと修正(概要)
- syscall の不一致 → カスタム libc + VM syscall 番号。
- リンクエラー → `strdup``strstr``abs``putchar``puts``atof``system``remove`/`rename` を追加。存在しない `deh_str.o` を削除。
- エントリ直後のクラッシュ → `-fno-pic -mno-abicalls`
- Node OOM → ArrayBuffer バックの VM メモリ。無制限の `this.memory` 書き込みを削除。
- `r_segs.c`: `SHRT_MAX``limits.h` に追加。

### 未解決の問題 / 次のアシスタントへのタスク
- `/tmp/frame.bmp` はテスト中に一度も生成されなかった。実行は `Program terminated at PC=0x400151``Executed 626581578` 命令で終了。`0x400150``abort` シンボル(自己ループの tight branch)。つまり何かが最終的に `abort` にジャンプしており、フレームを描画するクリーンなゲームループになっていない。
- 4MB ゾーン(`DEFAULT_RAM=4`)は実際の IWAD プレイには小さすぎる可能性がある → `Z_Malloc` / OOM 系の失敗やその他の未定義動作が発生しうる。`DEFAULT_RAM=6` の試用、`my_libc.c``HEAP_SIZE` を増やすこと(14MB に削減されていた)、`memGetWord` の範囲外アクセスが実際の障害を隠さないようにすることを検討する価値がある。
- 具体的な次のステップ: `abort` を呼び出している箇所を特定する(例: trap、libgcc パス、アロケーション失敗)。非 Windows での I_Error パスを修正する(現在 `ZenityErrorBox` / 異常なパスを呼び出している可能性あり)。`doomgeneric_Create``D_DoomMain` → … → `doomgeneric_Tick`/`D_Display``DG_DrawFrame` の呼び出し経路を確認する。VM 内で `fopen("/tmp/frame.bmp","wb")` + `SYS_close` のフラッシュを検証する。`/tmp/frame.bmp` が生成され stdout が正常に見えるまで再実行する。

### パス
- ELF: `/app/doomgeneric_mips`
- ビルド: `/app/doomgeneric/doomgeneric/Makefile.mips`
- フロントエンド: `/app/doomgeneric/doomgeneric/doomgeneric_img.c`
- Libc: `/app/mips_freestanding/my_libc.c``/app/mips_freestanding/include/*`
- VM: `/app/vm.js`(メモリモデルにパッチ適用済み)
- ローカルで使用した IWAD: `/app/doom1.wad`(テスト用)

長いホライズンの未来に向けて

コンパクションを学習ループに組み込むことで、Composerは重要な情報を効率的に先へ引き継ぐための明示的な仕組みを学習し、困難なタスクに対してより高い能力を発揮できるようになります。自己要約に関する私たちの取り組みは、マルチエージェント連携のような、さらに長く複雑なプロセスでもComposerを学習させるという、より大きな目標に向けた一歩です。より優れたモデルの学習は、これらのエージェント型システムの適用範囲と知能を向上させるものだと、私たちは引き続き考えています。

Composerの次のバージョンについても、まもなくさらに詳しくお伝えします。

カテゴリー: 研究

著者s: Federico Cassano & Sasha Rush