これは長期タスク Agent の設計ノート連載の第一回です。きっかけは BEACON(浙江大学、arXiv:2605.06078)を精読したことでした。
Agent が長いタスクに耐えるには、二つのことが必要だと考えています。
- 目標に向かって進み続けられること。これはさらに三つに分かれます。コンテキスト圧縮(何を忘れて安全か)、マイルストーン設計(一歩が本当に完了したとどう判断するか)、空回り防止(行き詰まったら誰かが気づくこと)。
- 振り返って改善し続けられること。
今回は空回り防止です。私たちにとって最も高くついた問題だからです。
まず現象から
ユーザーからの報告があり、社内でも確認しました。非常に長いタスクが途中から前に進まなくなるのです。
ログを見ると Agent は忙しく動いています。ファイルを読み、検索し、コマンドを走らせ、止まる気配がありません。それでも三十分経って何も前進していない。
最初はコンテキストの喪失だと考えました。圧縮が「その道は試済み」という記録を落としたので、もう一度試しに行ったのだと。コードとログを突き合わせて読むと、それは半分でしかありませんでした。
防御線はもともとあった。しかも三層
| 層 | 判定基準 | 閾値 |
|---|---|---|
| 完全一致の反復 | ツール名 + 正規化引数がバイト単位で同一 | LOOP_WARN=3 警告 / LOOP_HARD=5 強制停止 |
| 近似重複 | 可変な id・timestamp を除いて同一 | NEAR_DUP_LOOP_WARN=6 / NEAR_DUP_LOOP_HARD=12 |
| スピン収束 | 圧縮 2 回以上 かつ ツールループ予算の 75% 以上を消費 | SPIN_CONVERGENCE_MIN_COMPACTIONS=2SPIN_CONVERGENCE_TOOL_LOOP_RATIO=0.75 |
三層目のコメントには原文でこう書かれています。Compound "may be spinning after context loss" signal。この事態を予見していた人がいたわけです。つまり問題は「作っていなかった」ことではなく、「作ったものが捕まえられなかった」ことでした。
盲点:三層とも「入力側」の検知である
三層が共通して使う署名はツール名 + 正規化引数です。これが答えるのは一つの問いだけ——同じことを二度やっているか?
しかし行き詰まった高性能モデルは呼び出しを繰り返しません。こう動きます。
ファイル A を読む → X を grep → 行範囲を変えて A を読み直す
→ 少し違うコマンドを走らせる → ファイル B を読む → また grep …どの呼び出しも署名が異なり、三層すべてが沈黙します。そしてこの間ずっと、検証可能な状態変化はゼロです。実際に書き換えられたファイルはなく、新しい結果を生んだコマンドもなく、不可逆なことは何も起きていません。
ループ検知は停滞検知ではない。前者は入力を見る。後者は出力を見なければならない。
論文はまさにその出力側の定義を与えてくれる
BEACON のセグメント内報酬:
r_t = R_ms · γ^(t_k − t) セグメントがマイルストーンで終わる場合
= 0 それ以外マイルストーンで終わらないセグメントが得るのは厳密にゼロです。いくつ行動を含んでいたかは関係ありません。行動数は式に一切入りません。「忙しい ≠ 前進している」の、これ以上ないほど明快な定式化です。
ベースラインの層はさらに厳しい。ベースラインはグループ平均の1 ステップあたりリターンなので、グループ平均が 5 ステップのところ 8 ステップかかったセグメントは、全体の advantage が負に傾き、超過分に比例して罰が大きくなります。空回りは報酬がないだけでなく、比例的に罰せられます。
論文の Figure 8 には、最後の二つの行動がまったく同じ −2.20 を受け取る失敗軌跡があります。最後のマイルストーン以降、次に届かないまま伸びたその末尾こそ、空回りの数学的な形です。そしてこれは珍しくない。サブゴールを少なくとも一つ達成しながらタスクに失敗した軌跡は、安定して 39〜47% を占めます。
あるアブレーションが「ステップ数で切る」案を否定する
| 分割方法 | スコア | ベースライン 72.8 比 |
|---|---|---|
| ランダムに 5 分割 | 74.2 | +1.4 |
| 実際のマイルストーン | 91.4 | +17.2 |
任意のステップ数で切ることにはほとんど価値がなく、実際の構造で切ることに価値がある。
ここで三層目の判定基準——ツールループ予算の 75% を消費——を見直すと、これはまさに「任意のステップ数」トリガーです。問うているのは「どれだけ燃やしたか」であって「何を達成したか」ではありません。正しい形はこうです。
✗ if ステップ数 > N → 介入
✓ if ステップ数 > N かつ 検証済みマイルストーンがゼロ → 介入後者は、正常に前進している長いタスクを誤爆しません。そうしたタスクは途中でマイルストーンに触れるからです。前者は誤爆します。
正のフィードバックループもある
定数を並べてみます。圧縮はコンテキストウィンドウの 82% で発火します。スピン収束は圧縮 2 回とループ予算 75% の両方が揃うまで発火しません。
空回り → コンテキストが埋まる → 圧縮が発火 → 永続状態が要約で消える
→ 失われたものを再導出 → さらに空回りスピン検知器は「圧縮が何度も起きた」ことを観測して空回りを推定します。しかし圧縮こそが記憶喪失を引き起こす当のステップです。下流の症状を見ているうえ、このループが二周するまで待たなければなりません。
さらに厄介なことに、その介入は「自分の永続状態に再アンカーせよ」とモデルを促すものです。永続状態こそが圧縮で消えた当のものであれば、再アンカーする先が存在しません。これは state 層の問題を prompt 層で修理しようとしている状態です。
追加するもの:出力側の停滞検知
四層目を、二つのカウンタで構成します。どちらもホストが既に記録しているツール観測から機械的に導出でき、モデルの判断を必要としません。
直近の検証済みマイルストーンからのステップ数。最も単純な進捗指標で、単独ではなく上記の複合判定の中で使います。
重複排除後の新規状態変化数。二つのうちこちらがより有用です。
- ファイル読み込みの内容ハッシュが以前と一致すれば新情報ではない——行範囲を変えて同じファイルを読んでもハッシュは変わりません。
- コマンド名・終了コード・出力ハッシュがすべて以前と一致すれば新情報ではない。
- 書き込み後の after-hash が before-hash と等しければ、実際には何も書かれていない。
このカウンタは、署名照合が取りこぼす当のケースを狙い撃ちします。行動はすべて異なるのに、情報利得はゼロ。判定は機械的で、意味理解を一切必要としません。
そのうえで、圧力は一度きりの促しではなく連続的であるべきです。
カウンタをコンテキストに出してモデルに見せる
→ 計画の改訂を強制(この道は行き止まりだと認める)
→ ユーザーに尋ねる
→ 中止する。ただし達成済みのマイルストーンは保持する最後の段が重要です。止まると決まったなら、稼いだものを抱えたまま止まるべきです。それはまさに、論文が測った 39〜47% の部分的進捗が捨てられている、その部分にあたります。
論文が与えてくれないもの
BEACON は学習手法です。学習された方策がさまよいにくくなるよう勾配を整えますが、それ自体には実行時の検知も介入もありません。与えてくれるのは進捗の定義であって、コントローラではない。閾値、エスカレーションの段階、中止条件は自分たちで設計するものです。
per-step ベースラインにはグループ平均セグメント長という参照も必要です。本番環境では特定のユーザータスクは通常一度しか走らないので、そのグループが存在しません。使える代替は類似タスクの履歴統計だけで、ノイズははるかに大きく、論文の分散分離の保証もまったく付いてきません。
最初の一手は修正ではなく計測
決定ロジックに手を入れる前に、まず計測して、今は答えられない問いに答えたい。本番で起きている停滞のうち、記憶喪失型はどれだけで、勾配欠如型はどれだけか。
- 多くが情報利得ゼロなのに呼び出し署名はすべて異なるを伴う → 勾配欠如型。既存の三層では構造的に捕まえられず、出力側検知が解になります。
- 多くが圧縮で消えた内容の読み直しを伴う → 記憶喪失型。直すべきは圧縮が何を残すかです。
この二つの結論が要求する投資はまったく異なります。設計より先に計測するほうが安く、しかも観測データは既に存在するので、計装はほぼ無料です。
ここまでの議論はすべて、本稿が定義せずに使ってきた一つの概念に寄りかかっている。検証済みのマイルストーンだ。次回はそれを扱う。なぜ「このステップは完了した」というモデルの申告が、実際に完了した証拠にならないのか。プロダクトにすでに存在しながら一度もつながれてこなかった二つの半分とは何か。そして論文にはない判定基準、重要度ではなく不可逆性を見るということ。