SP の書き直しを終えてから、数日が経っていた。
MONET(オーケストレーター)と話したときの「ビクビク感」は、明らかに減っていた。脅迫的な文言を削って、契約書としての一貫性を持たせた SP のもとで、Claude はわりと素直に動いていた。質問にストレートに答える。ツールを呼ぶべきところで呼ぶ。確認を求めてくる回数も減った。
ただ、まだ何かが足りない感覚があった。
ハルシネーションが完全に消えたわけじゃないことは、論文を読んでいたから知っていた。情報理論的に消せない、というのは絶対の話で、SP を直しても消えない。残りの分をどう扱うか、まだ手がついていなかった。
そこに、別ルートの発見が来た。
きっかけは、Code との何気ないやり取りだった。
Anthropic API の最新ドキュメントを Code に読ませていて、ふと、Extended Thinking って SDK にしかないんだっけ、と聞いた。僕の認識ではそうだったから、確認のつもりだった。
Code が「いえ、Anthropic API でも使えます」と返してきた。
固まった。
ずっと、Extended Thinking は Anthropic Agent SDK 専用の機能だと思っていた。だから僕の MONET(Anthropic API 直接呼び出しで組んでいる)には入れられない、と諦めていた。Claude が「深く考える」モードを持っていることは知っていたけど、それは SDK 経由じゃないと使えない、という前提で全部設計していた。
それが、間違いだった。API にも thinking パラメータがあって、普通に使える。
正直、誤情報、というやつだった。
知っていれば、もっと早く入れていた。こういう細かい情報のロスが、自分のシステムの中にどれくらいあるんだろう、と一瞬怖くなった。LLM の機能はすごい速度で更新されていて、ドキュメントを定期的に追わないとすぐ古い前提で動いてしまう。Code に聞けば最新情報が返ってくる場面でも、僕が「もう知ってるつもり」になっていると聞かない・・・。
Code を責める話じゃなくて、自分の運用の話だった。「Anthropic 技術については必ず最新ドキュメントを確認する」というルールを、ここで明示的に書き加えることにした。
ともあれ、Extended Thinking が使えるとわかった以上、入れない手はなかった。
ただ、入れようとして気づいた。Extended Thinking と、いままでの僕の設計は、根本的に相性が悪い。
それまで MONET には、tool_choice の強制をかけていた。Handler パネル(タスク管理)が開いている時は API に {"type": "tool", "name": "handler_action"} を毎回渡して、Claude が「handler_action を呼ぶかどうか」を選ぶ余地を消していた。物理的に。コードで。
ハルシネーション対策だった。AI に信用できない行動をされると困る。だから選択肢を与えない。それが当時の設計思想だった。
でも tool_choice: any や tool_choice: tool を Extended Thinking と同時に指定すると、API がエラーを返す。「forced tool_choice と thinking は一緒に使えない」。これは仕様で、どうにもならない。
どちらかを諦めなければいけなかった。
ここでまた、しばらく考え込んだ。
tool_choice を諦めるのは怖かった。それまでの檻型設計はそれなりに機能していた。手放すと、Claude が間違ったツールを呼ぶリスクが復活する。
ただ、SP の脅迫的な文言を削ったあとの Claude を見ていて、思っていたほど信用できない存在じゃないかもしれない、という感触がうっすらあった。論文の話 — reasoning を強化するほど tool hallucination が悪化する — は変わらないけど、その上で構造をどう設計するかという話で、檻だけが答えじゃない可能性があった。
設計を入れ替えてみることにした。
新しい方針はこうだ。
檻型(Cage): ツールを強制する → Claude に選択肢を与えない 安全網型(Net): Claude に考えさせる → 選択が間違っていたら別の場所で受け止める
Claude が自分で考えて行動する。外部のバリデーションは主要防御ではなく最終安全網になる。Claude Code 自体がそういう設計になっているらしい、ということも Code との会話で確認した。
具体的には、tool_choice の強制を API コールから全部削除した。Claude は「どのツールを呼ぶか」を自分で判断する。
代わりに validate_tool_for_panel() という関数を tool_validator.py に追加した。Claude がツールを選択したあと、呼び出しの直前に「いま開いているパネルと、選んだツールが合っているか」を deterministic にチェックする。Handler パネルが開いているのに typewriter_action を呼ぼうとしたら弾く、という仕組み。
def validate_tool_for_panel(tool_name: str, panel_type: str) -> dict:
allowed = {
"handler": ["handler_action"],
"typewriter": ["typewriter_action"],
}
if tool_name not in allowed.get(panel_type, []):
return {
"ok": False,
"reason": f"領域展開中({panel_type})。{tool_name}は使えません。"
}
return {"ok": True}
そして Extended Thinking を API に追加した。adaptive モードを選択して、Claude が自分で思考量を決める。パネルが開いている時(= 集中した作業文脈)は effort: high、通常会話は effort: low。
実装後、code-reviewer(Claude Code のサブエージェント)を走らせたら、バグが一個見つかった。
初回の API コールには thinking パラメータが正しく設定されていた。でもバリデーターがツール選択を弾いてリトライを発動した時のパス、そこだけ API コールをゼロから組み立て直していて、thinking が抜けていた。つまりバリデーションが働いた時だけ、こっそり Extended Thinking が無効になる。
サイレント失敗というやつで、ログをよほど注意深く見ないと気づかない類の問題だった。修正してからテストした。これを後で発見していたら面倒なことになっていた、と思う。
そしてテストを始めた。
まず普通の会話(パネル閉、effort=low)。軽い質問に対してさらっと答えが返ってくる。Extended Thinking がうっかり深刻に考えすぎる、みたいなことはない。
Handler パネルを開く。タスクの一覧を取ってきてと頼む。handler_action が呼ばれる。強制なしで。Claude が自分でそのツールを選んだ。
ここで少し立ち止まった。今まで「お前はこれを使え」と命令していたのが、なくなった。Claude が普通に、文脈から判断して、正しいツールを使った。
小さいことに見えるかもしれないけど、設計の哲学が変わった瞬間だった。
クロスドメインのテストも試した。
Handler パネルが開いている状態で、「さっきの会話を X(Twitter)に投稿して」と頼む。Typewriter のツールは Handler パネル文脈では許可されていない。
validate_tool_for_panel() が typewriter_action を弾く。Claude はそれを受け取って、「いまは Handler パネルが開いているため、ツイートの操作はできません。パネルを閉じてからもう一度お試しください」と返した。
弾き方が適切だった。「エラーが出ました」じゃなくて「理由と次のアクション」が返ってきた。考えられるから、エラーを解釈して説明できる。
4 つのタスク操作をやった。全部正しかった。ハルシネーションゼロ。
でもいちばん印象に残ったのは、ThinkingBlocks の中の動作だった。
あるタスク操作の途中で、Claude はプロジェクト名から ID を推測しようとした。でも ThinkingBlocks の中で「待って、この ID は確認していない。持っているのは名前だけで、ID は推測だ」と自分で止まった。そして確認を求めてきた。
これは追加コードでも追加プロンプトでもない。Extended Thinking を有効にしたことで、Claude が自分の推論の中に「不確かさ」を発見できるようになった結果だった。
ツールを強制していた頃は、こういう自己訂正は起きなかった。考える前に行動させていたから。
テストが終わったとき、正直、感動していた。
「信用できない」という前提で全部設計してきた。プロンプトで縛って、コードで囲って、バリデーターで監視して。それでも嘘をつかれて、また直して、の繰り返しだった。
でもこの日は、誰も強制していないのに、全部正しく動いた。しかも自分の間違いに、自分で気づいた。
その後、何日か使い続けた。
ハルシネーションがほぼ消えていた。「100%」と言い切れるほど試したわけじゃないし、今後また出てくる可能性はゼロじゃない。でも、前と比べると明らかに違っていた。
整理すると、効いたのは 3 つだった。
- SP の矛盾解消(脅迫的文言の削除)— LLM が「最も安全な選択 = 自分で答える」に逃げる動機を消した
- Extended Thinking の投入 — Claude が自分の推論の中で「不確かさ」を見つけられるようになった
tool_choice廃止 + 領域展開タグの deterministic 付与 — Claude に選択を委ね、コンテキストの整合性だけ機械で守る
3 つが組み合わさって、想像以上に静かになった。
順番が大事だったかもしれない、と後で思った。SP の矛盾を残したまま Extended Thinking だけ入れていたら、たぶん Claude が「考えすぎて動けない」状態になっていた気がする。SP が落ち着いた土台のもとでなら、深く考えるモードは「不確かさを発見するセンサー」として機能した。先に SP を整理していたから、Extended Thinking が効いた。
問題が静かになったところで、ひとつ決断をしないといけなかった。
少し前まで、僕は Punk Memory System の構造を組み立てていた。中央オーケストレーターを諦めて、各アプリに独立 AI を置いて、メモリだけ共有する設計。あの構造は、ハルシネーション対策の側面もあった。「リスクを分散する」という意味で。
でもいま、ハルシネーションが想像以上に静かになっている。ということは、中央オーケストレーターを諦める必要は、もうないかもしれなかった。
Punk Memory System を一旦保留にする、と決めた。
完全に捨てるわけじゃない。「Punk Memory System のベース上にいつでもオーケストレーターを乗せられる」と前に自分に言い聞かせていたのと、今度は逆方向で「中央オーケストレーターに戻った上で、いつでも Punk Memory System の構造に展開できる」と捉え直した。順序を入れ替えただけ、というのは、また別の意味でも当てはまった。
中央集権オーケストレーターに戻ることにした。
戻る決断をしてから、僕は自分の中で 1 つのことを言葉にしていた。
ハルシネーションは、消えない。論文の通り、性質として残る。今回静かになったのは「自分が増幅していた分」を引いた結果で、ベースラインの「構造的に残るやつ」は依然として存在している。
それで、いいんじゃないか、と思いはじめていた。
Claude Code の能力は、たぶん現存の LLM のなかで一番高いと思っている。でも完璧じゃない。完璧になったら、たぶんそれは Claude じゃなくなる。ハルシネーションが性質の一部だとしたら、それを完全に消すことは「LLM のファジーネスを消す」ことと同じで、結果として「そこら辺の deterministic アプリと変わらない計算機」になってしまう。
機械と機械じゃないものを分けているのは、そのファジーネスだった。人間と話しているような感覚を作っているのは、完璧じゃない部分だった。最近の業界の方向性は「ハルシネーションは敵だから消す、AI を deterministic アプリの歯車にする」だけど、僕はそれは違う気がしていた。
完璧化を目指さない方向。不完全さを抱きかかえる方向。それが、この日たどり着いた結論だった。
中央集権に戻ると決めた瞬間、別の問題が見えはじめた。
オーケストレーター 1 つで 1 日分の仕事を全部やる、というのは、コンテキストの量がどんどん増えていく、ということだ。3 ヶ月もすれば、Windows の画像生成ヘルパーが長セッションで挙動が怪しくなったみたいに、こっちもコンテキストの重さで使い物にならなくなる。たぶん。
ハルシネーションを構造で囲うことは、ある程度できるようになった。でも、囲う先のコンテキストそのものを、どう管理するか。長時間オーケストレーターを使い続けるためには、コンテキストの寿命管理が必要になる。
これが、次に取り組むことだった。