思考は自由、実行は正確 — 三本目の壁はif文
GPTと言い合いになった。
deterministicアプリの話をしていたら、GPTがn8nみたいなワークフロー自動化ツールのことを「エージェント」と呼んだ。それは違うでしょ、と返した。n8nは計算機。パートナーじゃない。
言い合いながら気づいたのは、業界の言葉の使い方が最近変わってきてること。「エージェント」ブームが一段落して、DeterministicとLLMのハイブリッドが主流になってきた。で、かつて「エージェント」と呼んでいたものを、今は「deterministic」と呼び直してる。自分たちの失敗を隠すために用語をすり替えてるように、僕には見える。
ただ、皮肉なことに、業界が寄ってきた先は僕の設計と同じ形だった。
MONET(オーケストレーター)の設計は、最初からずっとこう。
MONET(思考・判断・決定)← ここは自由にやらせる
↓
検証(if 文だけ、AI ゼロ)← ここで機械的に照合
↓
実行デバイス
思考は自由、実行は正確。文脈の理解とか、優先度の判断とか、LLMが得意なところは全部任せる。でも実行の直前だけは、機械が形式を照合する。前の記事で書いた論文の数字とも矛盾しない。オーケストレーター型はエラーが4.4倍に増幅されるから、思考は1箇所に集約する。逐次タスクはエージェントを重ねると劣化するから、実行側はAIなしのデバイスにする。
言い合いの後、ちょっと冷静になって考えた。「思考は自由、実行は正確」って言葉では言えてるけど、実行の直前で照合する層、コードとしてはまだ無くない・・・?
tool_choiceの強制とWatchmanの出口チェック。壁は2枚ある。でもどっちも「正しいツールを呼んだか」を見ている。ツールに渡すパラメータの中身を機械的に照合する場所は、実は無かった。MONETが実在しないaction名や、変なstatus値を渡してきたら、そのままDBまで届く。
コーディングしてくれているClaude Code(以降Code)と、この穴を塞ぐ設計に入った。Windows側で別に動かしているCodeがまとめてくれていたリサーチ文書があって、そこから提案が3つ出てきた。
1つ目、スキーマレジストリ。ツールごとに必須パラメータとenum値(許される値のリスト)を定義して照合する。採用。
2つ目、action=chatだけはHaiku(小さいClaude)のチェックを残す。会話かどうかの判定は文脈理解が要るから、if文では無理。これも採用。AIが本当に必要な場所だけAIを使う。
3つ目、僕の入力を見て「タスクっぽさ」を判定するキーワード検知。これは断った。僕の発言を機械が監視してる状態は、過去に一度試してカオスになった。UIが勝手にパネルを開いたりして、こっちが萎縮して喋れなくなる。見るのはMONETの出力だけ。僕には触らせない。
ついでに、LangGraphとかCrewAIみたいなフレームワークを入れる話も出たけど、これも見送った。分散型前提のフレームワークは思考を1箇所に集める設計と合わないし、ブラックボックスが増える。MONETの設計はシンプルで速い。自前で作る。
Codeに実装してもらった。tool_validator.py、約170行。中身はif文だけで、LLMはゼロ。
def validate_params(tool_name, tool_input):
# 1. 必須パラメータがあるか
# 2. action / status / priority が enum の中にあるか
# 3. OK か、REJECT + 理由 を返す
Handlerは10アクションとstatus/priorityのenum、Typewriterは14アクション。REJECTの時は理由の文章をMONETに返す。黙って弾くんじゃなくて、何を間違えたか本人にわかるように。あと、不正なパラメータを延々リトライし続けないように、ループの上限も入れた。
検証の挿入はツール呼び出しの経路6箇所。メインループとリトライの1段目・2段目、全部の通り道に置いた。
で、実際にMONETと会話してテストした。
ログを見ると、REJECTは0件。全部素通し。
ちょっと拍子抜けしたけど、考えてみればこれが正しい状態。monet-rulesのプロンプト層がちゃんと教えてるから、MONETは普段から正しいパラメータを選んでる。この壁が仕事をするのは、MONETが間違えたその時だけ。プロンプトで教えて、コードで防ぐ。教える層と防ぐ層は別々に持つ。
tool_choiceの強制、Watchmanの出口チェック、そしてパラメータの検証層。壁が三枚になった。
間違った形式の実行は、もうDBに届かない。