reinsの「ターン」とは何か 画像:AI生成

AIエージェントに大きな仕事を任せたことのある人は知っている。ループが崩れる地点はいつも同じだ。モデルが自ら**「終わりました」と宣言する瞬間**である。実際には終わっていないのに。

reinsは、その完了宣言の権限をAIから取り上げて機械に渡すクエストCLIフレームワークだ。作業リストをゲームのクエストのように項目ひとつひとつに分割し、AIにひとつずつやらせ、「終わったか」はAIの言葉ではなく機械の検査で判定する。そしてその心臓部には、**ターン(turn)**という小さな単位がある。

本文に繰り返し登場する言葉を二つだけ、先に押さえておこう。

  • ゲート(gate) — 提出物を機械的に検査して通過(PASS)/不合格(FAIL)を判定する検査器。人間の印象評でも、AIの自己評価でもない。
  • ラチェット(ratchet) — 一方向にしか回らない歯車。一度の通過でロックされた項目は二度と開かない、という意味で使う。

この記事は、ターンひとつが実際にどんな過程を通るのかを最初から最後まで追いかける。ターンは小さく見えるが、分解してみれば、reins全体の縮図である。

Reins Engineeringの核心6要素

reinsが立脚する工学を一行に圧縮するとこうなる — **完了宣言の権限をAIから決定論的な機械ゲートへ移す工学。**ここでの「決定論的」は難しい言葉ではない。同じ入力なら常に同じ結果が出るという意味だ。テストは通るか落ちるかであり、昨日と今日の判定が気分によって変わることはない。

プロンプトエンジニアリング(言葉を選ぶ仕事)→ コンテキストエンジニアリング(文脈を与える仕事)→ ハーネスエンジニアリング(柵を張る仕事)の系譜において、Reinsは方向を定める手綱である。この工学は六つの要素から組み立てられる。

#要素一行で正典文
1機械判定の完了完了条件は機械がyes/noで答えられなければならず、判定も機械が行う — AIから終了判断権を剥奪する「完了は主張ではなくゲートが判定する」
2決定論的フィードバック意見ではなく事実 — 「どこが、何であるべきなのに、実際は何だった」が修正の方向を示す信号になる「意見を与えれば追従し、事実を与えれば修正する」
3方向コンテキスト(マニュアル+例示)フィードバックが矯正信号なら、マニュアルと例示は方向信号 — 一方が他方の代わりにはなれない「ボトルネックは知能ではなくコンテキストだ」
4Ratchet Pattern(契約ロック)通過した項目は不変、残作業は減るだけ — 終わることが構造で保証されるremaining(t+1) ≤ remaining(t)
5進捗の永続化進捗状態はプロセスの外(ディスク)に住む — AIが死んでも進捗は残る“Agents are disposable; progress is cumulative.”
6チーズ防御(ドメインゲート)機械検査でも外殻しか見なければ小細工に破られる — ゲートはそのドメインの本当の事実を再検証しなければならない「ゲートはドメインを持つ」

表の用語がまだ馴染まなくても構わない。ターンはこの六つが一つのサイクルの中で出会う最小単位であり、ターンひとつを最後まで追えば、六つの要素すべてがそれぞれの持ち場で顔を合わせる。先に地図だけ描いておこう — ターンの最初のステップであるcomposeが方向コンテキストと決定論的フィードバックを組み立て、judgeが機械判定を下し(チーズ防御はこのゲートの設計品質だ)、recordがラチェットをロックして進捗を永続化する。

Reins Turnとは何か?

Reins Turnとは、クエスト項目ひとつへの試行一回が、生成 → 判定 → 記録として完結する、reinsの最小実行単位である。ループとはターンの反復にすぎず、クエストの進捗とはターンの累積にすぎない。だから定義は一行で足りる。

Turn N = N番目に記録されたAttempt。記録されなかったものはターンではない。

Attemptとは「試行の記録一件」だ。ここに微妙な点がひとつある — ターンは「LLMを一回呼んだ」ではない。ターンの存在条件は呼び出しではなく記録である。LLMを呼び出しても、その結果がラチェットに記録されなければ、そのターンは存在しなかったことになる。逆に、人間が手で提出した結果でも、記録されればそれはターンだ。LLMがなくても。ターンを数える者はモデルではなくラチェットである。

この定義ひとつが、後に出てくるすべての性質 — ドライバー非依存性、再起動耐性、監査可能性 — の根である。今は名前だけ通り過ぎてよい。ひとつずつ、それぞれの持ち場で再会する。

ひとつのターンは正確に四つのステップだ

ターンひとつは四つのステップを順に通る。各ステップの名前は英語だが、やることは単純だ — 組み立て(compose)、生成(generate)、判定(judge)、記録(record)。

ステップやること性質
① compose直前ターンまでの記録(Log)を読み、LLMに渡すプロンプトを組み立てる純粋関数
② generateLLMを一回呼び出して成果物を生成する唯一の確率的ステップ
③ judgeゲートが成果物を判定する(PASS / REVIEW / FAIL)決定論
④ record判定をラチェットに適用し、ディスクに保存する唯一の不可逆ステップ

「純粋関数」とは同じ入力なら常に同じ出力が出て他の副作用がないこと、「不可逆」とは一度実行されたら元に戻せないことを意味する。すると表の核心が見えてくる — **四つのステップのうち、確率的なのは②だけであり、不可逆なのは④だけだ。**生成は判定できず、判定は生成できない。AIの不確実性を一つのマスに閉じ込め、取り消せない決定をもう一つのマスに閉じ込めたのである。

ひとつ風変わりな設計にも触れておこう。この四段カスケードはGoコード二組として実装されてはいない。pkg/cli/turn.mdという人間が読める文書ひとつTANGEUL文書)として定義され、この文書がバイナリに埋め込まれ(go:embed)、ランタイムで解釈される。「ターンがどう回るか」の正典がコードではなく読める文書だということだ — この選択の理由は、記事の最後に戻ってくる。

① compose — コンテキストはLogからのみ来る

Logとは、これまでのすべてのターンが記録された、ディスクに残る台帳である。そしてターンNのプロンプトを組み立てる入力はただひとつ、**このLogだけだ。**プログラムのメモリのどこかに潜んでいて、プロセスが死ねば消える状態 — そんなものは存在しない。

Logから組み立てられるプロンプトは三層をなす。

  • やるべきこと — この項目で何を作らなければならないか(作成指示と検証コンテキスト)
  • マニュアル — グローバルなシステムプロンプトに、直前のターンがFAILならその失敗原因ルールに対応するコーチング(ルール別マニュアル)を加えたもの
  • フィードバックの尾 — 直前のFAILの事実たち。「どこが、何であるべきなのに、実際は何だった」

先の6要素の言葉で言えば、マニュアルが方向コンテキストで、フィードバックの尾が決定論的フィードバックだ。この二つが毎ターン、Logから機械的に組み立てられる。

微妙だが重要なルールがひとつある。composeが基準とするのは最後のAttemptではなく、最後に**判定まで終わった(judged)**Attemptである。途中にLLMサーバー障害のような生成エラーが挟まっていても、リトライが見るのはインフラのエラーメッセージではなく、直前のFAILの事実フィードバックだ。運用障害が作業のコンテキストを汚染することはない。

② generate — LLMは生成だけをする

組み立てられたプロンプトでLLMバックエンドを一回呼び出す。バックエンドがHTTP API(ollama/xai/gemini)でもコマンドラインツール(claude/grok/codex/geminicli)でも関係ない。どちらであっても、役割は生成器に固定される。フレームワークは、LLMにPASSの権限を与えられるAPIそのものを公開していない。判定権限を「与えないことにした」のではなく、与える方法がないのだ。これが6要素の1番(機械判定の完了)が構造になる仕方である。

どのバックエンドを使うかさえ、ターンごとにLogから計算し直される。デフォルトのモデルで失敗し続ける項目はより強いモデルへ送り上げるが(エスカレーション)、この決定も「過去のFAILの原因ルールの中にエスカレーション対象はあるか?」を毎ターンLogから読み取って下す。どこかで入れっぱなしのスイッチも、ループ内のローカル変数も存在しない。同じプログラムに同じLogなら、同じバックエンドが選ばれる。プロセスを殺して再起動しても同じように動く性質 — 再起動耐性がただでついてくる。

③ judge — ゲートだけが判定する

成果物を判定するのはゲートだ。ゲートとは違反検知ルールの集合である。各ルールは自分の受け持つ問題を発見すると発火(fire)し、事実をひとつ残す — どこが(Where)、何であるべきなのに(Expected)、実際は何だった(Actual)。

集計は決定論だ。Failルールがひとつでも発火すればFAIL。そうでなくReviewルールが発火すればREVIEW(人間が確認するグレーゾーン)。何も発火しなければPASS。同じ提出物に同じルールなら、常に同じ判定である。尋ねるたびに答えが変わる検査官は、ここにはいない。

ルール同士が絡み合う複雑なドメイン — ひとつの違反が別の検査を無意味にする場合 — では、判定を論証グラフ(toulmin)ベースに引き上げ、判定ロジックそのものを監査可能な文書(gate.md)へ移すこともできる。どの形であれ、変わらないものはひとつだ。判定の主体は機械である。

④ record — 唯一の不可逆ステップ

判定をラチェットに適用し、ディスクに保存し、完結した項目を書き出す(export)。ここがターンの中で唯一、元に戻せない地点だ。

  • PASS / REVIEW / SKIPPED / BLOCKED → ロック。ラチェットは一方向であり、後戻りしない。
  • FAIL → 試行回数(Tries)が1増え、項目はTODOのまま残る。最大試行回数(デフォルト3回)に達するとDONEとしてロックする。「この項目はこのやり方では無理だ」もまた完結の一形態だ — 動かない項目ひとつがループ全体を永遠に人質に取ることを許さない。
  • 生成エラー → LogにAttemptとして記録はされるが(何が起きたかの痕跡は残す)、**試行回数を消費しない。**LLMサーバーが落ちたのは項目の落ち度ではなく運用障害だからだ。障害が続くなら、別の安全装置がループを止める(連続N回の生成失敗で停止 — サーキットブレーカー)。

このステップがあってはじめて、次のターンのcomposeが読むLogが生まれる。recordはターンの締めくくりであり、次のターンの生成者である。

失敗は意見ではなくファクトだ

FAILがどんな形で返ってくるか — これがターンが収束する(リトライのたびに正解へ近づく)鍵である。「品質が少し物足りません」のような意見ではなく、こういうものだ。

FAIL. root cause = who-anchor-present
  Fact: where=who.anchors expected="source substring" actual="田中課長"

読み方は簡単だ。どのフィールドが(where)、何であるべきなのに(expected — 原文に実際に存在する文字列)、実際は何だった(actual)。**位置+期待値+実際値。**LLMにとっては、追従すべき相手のいないフィードバックである — 数字と位置は感情ではないからだ。追従するモデルを、論争ではなく収束へ導くのが、この構造化されたファクトである。

おまけをひとつ。モデルに食わせるFAILテキストは、人間がsubmitコマンドを打ったときに画面へ出力される文字列と完全に同一のレンダリングである(feedback parity)。人間が見るフィードバックとモデルが見るフィードバックは決してずれない。「AIには一体何が送られて、こう直したんだ?」というミステリーが、構造的に存在しない。

三つの入口、ひとつのターン

ターンを回す運転者をドライバーと呼ぼう。reinsにはドライバーが三つある。人間が直接プロンプトを受け取るnext、人間が結果を提出するsubmit、無人で回る自動のloop。だがこの三つは、別々の三実装ではない。同じターン文書へ入る三つの入口である。手動と自動を分けるのは、文書の中のルールたったひとつだ。

これがもたらす実戦的な性質はこうだ。**クエストの途中でドライバーを替えても、プロンプトはバイト単位で同一である。**昨日は自動loopで回し、今日は人間がnextで引き継いでも、同じLog状態なら同じプロンプトが出る。コンテキストがLogからのみ来るから可能なことであり(①のステップで見た)、期待ではなく証明された性質だ — ドライバーを替えながら出力を比較するテスト(driver-swap golden test)がバイト同一性を検証している。

ターンひとつを最後まで追ってみる

理論は終わった。実際のターンひとつを最初から最後まで見よう。文書からメールアドレスを抽出するクエストの項目ひとつで、直前のターンが「メール形式ではない」でFAILだった二回目の試行だ。

[ターン開始判定]  項目はTODOか? 試行は残っているか? → 進行
① compose      Logから最後のjudgedなAttemptを見つける → FAIL, 原因 = email-format
               マニュアル = グローバルプロンプト + email-formatルールのコーチング
               プロンプト = やるべきこと + "FAIL. Fact: where=email
                        expected='valid email format' actual='kim at example'"
② generate     LLM呼び出し → {"email":"kim@example.com", ...}
③ judge        ルール巡回: email-format 通過, source-lacks-email 通過, freemail 通過
               → 発火したFailルールなし → PASS
④ record       ラチェットをロック(不可逆) → Attempt #2 記録 → 保存 → export

直前の失敗の事実(「kim at exampleは有効なメール形式ではない」)を受け取ったモデルが、正確にその箇所を直し、ゲートが通過を確認し、ラチェットがロックした。この項目はもう永遠にPASSだ。次のターンはこの項目を飛ばし、残作業は減るだけだ。すべての項目が完結状態(PASSでも、REVIEWでも、最大試行到達でも)になればループは終わる。「AIが終わったと感じたら」ではなく、残りの項目がゼロになったら終わる。収束が構造で保証される。

Loop Engineering — 業界が同じ問題に名前をつけた

2026年6月、エージェントの扱い方の転換に名前がついた。Peter Steinbergerが “You shouldn’t be prompting coding agents anymore. You should be designing loops that prompt your agents”(もうコーディングエージェントにプロンプトを打つべきではない。エージェントにプロンプトするループを設計せよ)と書き、同じ月の初め、Claude Codeを作ったAnthropicのBoris Chernyはあるイベントで “I don’t prompt Claude anymore. I have loops running… My job is to write loops”(私はもうClaudeにプロンプトしない。ループが回っている。私の仕事はループを書くことだ)と語った。数日後、GoogleのAddy Osmaniがこの流れにLoop Engineeringという名前をつけた — エージェントにプロンプトを打つ人から、エージェントをプロンプトするシステムを設計する人へ。

この言説はひとつの移行の物語を描く。プロンプトエンジニアリング(言葉を選ぶ仕事)→ コンテキストエンジニアリング(見せる情報を選ぶ仕事)→ ハーネスエンジニアリング(実行環境を作る仕事)→ ループエンジニアリング(観察・行動・検証・復旧のサイクルそのものを設計する仕事)。レバレッジが言葉から情報へ、情報から環境へ、環境からサイクルへと、一層ずつ外側へ移動してきたのである。

Osmaniが示した良いループの解剖は、ここまで読んだ読者には見覚えがあるはずだ。状態は会話ではなくディスクやボードへ外部化せよ。作るエージェントと検証するエージェントを分離せよ。終了はコーディングエージェントの自己宣言ではなく、別の判定者に確認させよ。ターンと並べれば、対応は鮮明だ。

Loop Engineeringの勧告reinsターンの構造化
状態をディスク・ボードへ外部化せよ(モデルは忘れる)Logは記憶の補助ではなくターンの唯一の入力 — composeはLogの純粋関数
作る者と検証する者を分離せよ(サブエージェント)検証者は別のLLMではなく決定論ゲート — PASS権限のAPIそのものがない
テスト可能な終了条件を定義せよラチェットの単調性が収束を不変条件として保証 — 条件ではなく構造
復旧可能な失敗と致命的な失敗を区別せよrecordのルールに内蔵 — FAILは試行を消費、生成エラーはサーキットブレーカーの受け持ち

違いの核心は勧告と構造の距離にある。ループエンジニアリングの文献自身も検証の階層を認めている — AIに判定を任せるLLM-as-judgeは「騙されたり行為者と共謀したりし得る」(can be gamed or can collude with the actor)ため、決定論検査が可能なところには必ず決定論検査を置けと。しかしそれはベストプラクティス、つまり設計者の善意に委ねられた勧告にとどまる。検証者サブエージェントも結局LLMなら、共謀への扉は開いたままだ。reinsはその扉を設計で閉じる。判定はゲートだけが行い、フレームワークはLLMにPASSを与える方法そのものを公開しない。

そしてループエンジニアリングがループ全体を語るとき、reinsはそのループの一周を契約にする。「私の仕事はループを書くことだ」というChernyの言葉から一歩先へ進むには、ループが一周で何をするのか — 何が確率で、何が決定論で、何が不可逆なのか — が先に定義されていなければならない。その定義の名前がターンである。

なぜこの単位なのか

ターンをこう締め上げると、三つのものが手に入る。先ほど通り過ぎた名前たちが、ここで持ち場を見つける。

  • 監査可能性 — 「自動loopは手動nextと何が違うのか?」の答えが、コード二組を比較する仕事ではなく、文書の中のルール一行になる。PASSをロックできる経路が文書に正確にひとつしかないことを、自分の目で確認できる。ターンの正典をコードではなく読める文書に置いた理由がこれだ。
  • 再起動耐性 — ターンのすべての入力がディスク上のLogから導かれるため、プロセスが死んでも、同じLogから同じターンが再現される。
  • エージェントの使い捨て化 — エージェント(LLM・ドライバー)は差し替えても、進捗は累積し、後戻りしない。“Agents are disposable; progress is cumulative.”

裏返せば、ターンが曖昧なシステム — コンテキストがセッションメモリに潜み、完了をモデルが自己宣言し、リトライが会話履歴に寄りかかるシステム — は、この三つをすべて失う。

ターンは小さく見える。しかし生成(確率)と判定(決定論)と記録(不可逆)を一つのサイクルの中で分離し直列化する最小単位であるという点で、ターンを理解すれば、reins全体を理解したことになるのだ。

関連記事

合わせて読みたい

出典

変更履歴

  • 2026-07-07: 初版