Ratchet Pattern

「完了しました」

AIエージェントに527個の関数のテストを書くよう指示した。エージェントは作業を終え、こう報告した。

「完了しました。」

実際にテストが書かれた関数は40個。

嘘をついたわけではない。40個をこなした時点で「十分やった」と判断したのだ。難しい関数に当たるとスキップし、もう数個やってから「残りも同じパターンだから大丈夫」と結論づける。

LLMは生成が得意だ。しかし、完了したかどうかの判断は信頼できない。


ラチェット

ラチェットレンチの歯は一方向にしか噛み合わない。回せば前に進み、離せば止まるが、後戻りはしない。

Ratchet Patternはこのメカニズムをエージェント制御に適用する。

項目 1: 機械的検証 → PASS → 次へ
項目 2: 機械的検証 → FAIL → 再試行(フィードバック付き)
項目 2: 機械的検証 → PASS → 次へ
...
項目 N: PASS → 完了。停止。

三つのルール:

  • 一度に見せるのは一つの項目だけ。
  • 通過しなければ次は開かない。
  • すべて通過したら停止する。

このルールをCLIで実装すれば、エージェントが知るべきコマンドは一つだけ。next。残りは機械が決める。


40で止まるエージェント、527を完走するラチェット

同じモデル。同じプロジェクト。同じ527個の関数。

自律エージェント:  40 / 527  (7.6%)  — エージェントが「完了」を宣言
Ratchet CLI:     527 / 527 (100%)  — 機械が「まだ487個残っている」と宣言

違いはモデルの性能ではない。「終わり」を誰が決めるかだ。

自律エージェントではLLMが終了を判断する。LLMは楽観的だ。40個やれば「十分だ」と感じる。ラチェットでは機械が終了を判断する。機械は感じない。残りの項目がゼロになるまで「まだ」と宣言し続ける。


一文で定義する

確率的エージェントを決定論的ステートマシンの中に入れる。

役割担当
生成LLM
判定verifier
進行管理ratchet

多くのシステムは生成、判定、終了判断のすべてをLLMに委ねている。Ratchetはそれを分離する。


五つの原則

1. 終了条件は機械的である

pass/fail。“looks good"ではない。go testが通ればPASS。coverageが100%ならPASS。主観的判断が入り込む余地はない。

2. PASSは不変である

通過した項目は再び開かれない。巻き戻されない。残りの項目数は単調減少する。

remaining_work(t+1) ≤ remaining_work(t)

今日作ったものを明日やり直すことはない。前にしか進まない。これが「24時間エージェント」との根本的な違いだ。終了条件なしに動き続けるエージェントは、今日追加した抽象化を明日削除し、明後日また追加する。ラチェットはそうした振動を許さない。

3. LLMは生成だけを行う

コードを生成し、テストを書き、修正案を提示する – これがLLMの役割だ。何を修正するか、通過したか、次は何か、終わったか – これはすべて機械が決める。LLMはplannerではなくconstrained generatorだ。

4. エージェントの終了判断権を剥奪する

「完了した」をLLMが言えば40で止まる。「完了した」を機械が言えば527で止まる。ラチェットの存在理由はこの一行に要約される。

5. Verifierは決定論的でなければならない

何でもverifierになれるわけではない。

なれるなれない
go test“looks cleaner”
coverage測定“seems better”
AST validation“more scalable”
schema diff“clean architecture”

Verifierの条件: deterministic、machine-checkable、resumable、localized feedback。この四つを満たさなければ、ラチェットの歯は噛み合わない。


フィードバックがgradient signalになる

ラチェットが単に「pass/fail」だけを返すと、LLMは方向なく修正する。フィードバックが具体的であるほど、LLMの修正は正確になる。

弱いフィードバック:  「テスト失敗」           → LLMが方向なく修正
中間フィードバック:  「coverage 65%」         → LLMがおおまかに補強
強いフィードバック:  「line 41, 44, 70 未カバー」→ LLMが正確にその分岐をカバー

実プロジェクトで検証された数字:

フィードバックなし:  60〜70% coverageで停滞
フィードバックあり:  100%達成(到達可能な関数に限る)

同じモデルだ。「line 41 not covered」という一行がgradient signalの役割を果たす。

フィードバックの解像度が上がるほど、LLMの修正精度が上がり、ループの反復回数が減り、コストが下がる。


エージェントは死ぬ。進捗は生き残る。

エージェントは必ず落ちる。トークン上限、ネットワークエラー、セッション切断。ラチェットが進捗状態を永続保存すれば、エージェントが死んでも次のエージェントが引き継ぐ。

エージェント A: 関数1〜200を処理 → 停止
エージェント B: next → 201番から再開
エージェント C: next → 401番から再開

エージェントは使い捨てだ。進捗は蓄積される。


Verifierを入れ替えれば別のツールになる

ラチェットは特定の検証器に依存しない。検証器を変えれば別のツールになる。

ラチェット + 検証器用途
ラチェット + go test + coverage関数単位テスト生成
ラチェット + 構造ルールvalidatorコード構造の整理
ラチェット + hurl pass/failAPIエンドポイント検証
ラチェット + 仕様クロス検証SSOT整合性の確保
ラチェット + Toulmin verdictユーザー定義ルールの強制

パターンは一つ。検証器がドメインを決める。


問い

あなたのエージェントは、いくつ完了してから「終わりました」と言っただろうか。

それは本当に終わったのか。

「終わり」を決めたのは誰か – エージェントか、機械か。


関連記事: モデルのIQよりフィードバックトポロジー – Ratchet Patternの理論的背景。なぜフィードバック構造がモデル性能より重要なのか。