家庭内AIクラスタ 分散LLM推論基盤 全記事まとめ

この記事では、家庭内の余っているPCを使って、複数台・複数モデルのローカルLLMを自動ルーティングする分散AI推論基盤を自作した記録をまとめています。
OpenWebUIのチャット画面から話しかけるだけで、Coordinatorが最適なWorkerとモデルを選んで推論してくれる環境を、ゼロから少しずつ育ててきました。
各回は独立して読めますが、第1回から順に読むと設計の意図が分かりやすいと思います。


シリーズ一覧

基盤構築編

第1回自宅サーバーでOllamaを動かす — ホームラボ分散AI推論基盤の作り方
構想と全体設計。複数PCをAI Worker化し、用途に応じて使い分けるアーキテクチャのスタート地点。

第2回OllamaタスクをRedisキューで非同期処理する — ローカルLLM推論の並列化設計
FastAPI + Redis + PostgreSQLで「投げたら忘れる」タスクキューを実装。モデル比較基盤も同時に構築。

第3回複数モデルを速度・ドメイン適性でスコアリングして自動ルーティングする仕組み
speed / domain / queue の3軸でモデルをスコアリング。実績ゼロのモデルにも0.3の探索スコアを与える設計。

第4回1BモデルでLLMの問いをcode/math/japaneseに分類する — 軽量ドメイン判定の実装
キーワードマッチ優先 + LFM2.5-1.2B-JPフォールバックの2段階分類。高速・低コストで4ドメインに振り分ける。

第5回WSL2のOllamaをLAN公開する — 固定IP・systemd・nvidia-smiのハマりどころ全部まとめ
RTX 3070Ti(WSL2)をLANに公開する際の3つのハマりどころを解決。ポートフォワーディング自動更新・systemd化・nvidia-smiのPATH問題。


使いやすさ向上編

第6回OpenWebUIからローカルLLMクラスタをつなぐ — Pipe FunctionとWorker実負荷ルーティングの実装
OpenWebUI Pipe Functionでチャット画面をCoordinator APIに接続。HeartbeatでCPU/GPU負荷を収集してqueue_scoreに反映。

第7回GPUタスクはGPU Workerへ — Redisキュー分離とcapability routingで能力ベースのLLM振り分けを実装する
tasks:gpu / tasks:cpuにキューを分離し、Workerのsupportsフィールドで能力宣言。将来のRAG・Web検索Workerへの拡張を見据えた設計。

第8回ローカルLLMにWeb検索を追加する — SearXNG + Workerで自前RAG前夜を実現
SearXNGをDockerでLAN内に構築。Web検索WorkerがSearXNGを叩き、LFM2.5-1.2Bで要約してから推論Workerへ渡す構成。

第9回Web検索Workerを常駐化し、「最新情報が必要な質問」を自動でWeb検索に回す
worker_webをsystemd化。「今日」「最新」などのキーワードでtasks:webへ自動振り分け。/route/previewで判定結果だけ確認する方法も。


堅牢化・品質向上編

第10回家庭内AIクラスタに自己修復機能を入れた ― Dead Worker Retry と per-worker キュー設計
Reaperスレッドがstaleタスクを検出して別Workerへ再投入。per-workerキューでライブロックリスクを根本解消。CASクレームで二重実行も防止。

第11回家庭内AIクラスタに『使う時だけ起こして、終わったら寝る』を実装した ― Wake-on-LAN 指名起動と Idle Shutdown
moonを指名するとCoordinatorがWoLで起動し、15分アイドルで自動シャットダウン。OpenWebUIのモデルセレクタから指名・起動待ちの進捗表示まで一気通貫。

第12回速度しか見ていなかったルーターに、回答の良し悪しを教えた ― LLM-as-judge と品質スコア
gemma3:12bがcomparison_resultsの回答を1〜5点採点。quality_scoreをmodel_domain_statsに蓄積し、スコアリング式に組み込む夜間バッチ設計。

第13回品質スコアをルーティングに組み込み、Web検索誤爆を直し、DBバックアップを整えた ― v6.2 運用基盤の仕上げ
品質スコアをルーティング式に組み込み(speed0.25+domain0.15+queue0.25+quality0.35)、Web検索の誤爆をネガティブキーワードで抑制。あわせてDBの日次バックアップとcronの整備を行い、v6.2の運用基盤を完成させた。

第14回OpenWebUIから返事が来なくなった ― Reaperが見逃すゴーストWorkerパターンの発見と対処
新Worker追加直後にプロセスがクラッシュすると、worker:<id>がRedisに残ったままになりReaperのdead_triggerが発火しない。statusがpendingのためzombie_triggerも対象外。この死角を突くghost_triggerを追加し、併せてai-coreへgemma3:1bを追加してSPOFを解消した。

第15回ローカル LLM クラスタに RAG を載せた ― Qdrant・bge-m3・専用 VM で自分のブログを検索する
rag-tools VM(専用6GB VM)を新設し、Qdrant + bge-m3 + llama3.2:3b で自前 RAG 基盤を構築。ブログ記事・運用資料をベクトル化し、OpenWebUI の「📚 RAG」モデルから自然言語で検索できるようにした。

第16回WSL2 portproxyの自動修復でハマった三重苦+1、そしてRAGの「投函ボックス」取り込み
WSL2のportproxyを再起動後も自動で貼り直すタスクスケジューラ登録で、SYSTEM実行不可・ユーザー名の思い込み・connectaddressの文字化け・Publicプロファイルの4連鎖にハマって全解決。あわせてRAGの運用資料取り込みを、旧版を自動purgeして最新版だけ残す「投函ボックス方式」に整えた。


クラスタ拡張・障害対応編

第17回redis-py 8.0でBRPOPが沈黙する ― 新Worker追加で炙り出された隠れ障害2件と「自己修復が障害を隠す」話
構築初日から3450回出ていたTimeoutErrorを「ネットワーク→プロトコル→ライブラリ」の3段切り分けで redis-py 8.0.0 起因と特定。あわせてpg_hba.confの許可漏れをReaperの自己修復が隠蔽していた事実が判明。「動いているように見える」を信用しないための新Worker追加チェックリストを整備した。

第18回MacBook Air M1を分散AI推論クラスタに参加させる ― 公式Ollama・launchd・ローカルネットワーク許可の全手順
中古M1 Air(16GB)をOllamaワーカー化し、Linuxクラスタに初のmacOSノードを追加。Homebrew版Ollamaのllama-server欠落バグ、同梱Python 3.9問題、ncは通るのにPythonだけ「No route to host」になるローカルネットワーク許可の罠を全部踏んで解決。gemma3:12bはMetal推論で7.68 tok/sを実測。

第19回OpenWebUIの隠れ内部タスクが自作ルーティング基盤を誤爆させた ― 「検索結果なし」の真犯人とWeb検索フォールバック実装
簡単な質問が全部Web検索行きになり回答ゼロ。psqlでpromptの実体を見たら、犯人はOpenWebUI組み込みWeb検索とタイトル/タグ生成が流し込む英文テンプレートだった。自作部分は全員無実という障害解析の一部始終と、検索失敗時のローカルLLMフォールバック・誤爆抑制キーワードの復元・Pipeタイムアウト改善まで。

第20回ベンチが異常に遅い、でもloadは0.00 ― systemd旧unit残存による二重Workerと、killしても蘇るプロセスの正体
M1 AirにELYZA-JP-8BとQwen3:8bを増強した検証ベンチで、esxiの1Bモデルだけが通常の50倍遅い異常値を記録。loadは0.00、メモリも余裕、なのにkillしたプロセスが別PIDで蘇る。正体は初期構築時の旧systemd unitの残存(両方Restart=always)による二重Workerだった。登録もハートビートも正常に見えるためReaperの全トリガーをすり抜ける「死角・第2号」の切り分けと、hf.co直pullのELYZAが英語で回答する罠のModelfile解決、gemma3:12bフォールバック判事まで。

第21回SSDが壊れたので換装したら、ルーティングの設計ミスが見つかった ― ハード故障対応から動的ティアフォールバックへ
moonのSSDが物理故障したので換装して復旧。その過程で qwen2.5:7b を追加したら「モデル名でGPU/CPUを決め打ちするルーティング」の硬直性が表面化し、CPUワーカーにGPUキュー宛タスクが積まれてSPOFエラーに。task_tier() を登録ワーカーの構成に応じた動的ティア判定へ書き換え、GPUワーカーの有無で同一モデルの行き先が自動で切り替わる仕組みにした。requirements.txt 整備による venv 再構築の再現性確保もあわせて記録。

第22回RAGの検索精度をチャンク分割で直したら、今度は回答合成が遅くなった ― 意味チャンキングとmoonオフロードの一部始終
固定長チャンクが見出し境界を割って本命がtop3落ちする問題を、セクション単位+パンくず付与のchunk_markdownで解消(8クエリ中7本命直撃)。ところがチャンク大型化で合成が120秒タイムアウトし、1b切替は404と品質崩壊で袋小路。最後はEnv切替だけで常時アイドルのmoonに合成を投げて54秒・3b品質に着地。改善が次のボトルネックを露呈させる分散システムあるあるの記録。


第23回ハイブリッド検索を作ったら、1行のdenylistに負けた ― RAG検索品質と「計測してから信じる」
dense検索の識別子弱点を補うべくdense+BM25/RRFのハイブリッド検索を実装。だが実機A/Bで、全並べ替えRRFはメタ節ノイズでdense良好クエリを劣化させ、ident-only+救済専用に精緻化しても本番では価値ある救済ゼロ。狙った本命はdenseが既に#1で取れており「本番に無い問題」を解こうとしていたと判明。真因は索引表(記事執筆状況)がdenseを汚していたことで、取り込み時に1節をdenylistするだけで「新Worker追加の落とし穴」等のdense検索が劇的に改善した。負の結果の価値と、検索ではなくauthoringが本筋だった切り分けの記録。

第24回Web検索WorkerをVMからM1へ移したら、拍子抜けするほど何もなかった ― env駆動設計と「先客が道を通しておいてくれた」話
母艦VMに同居していたWeb検索Workerを中古M1 MacBook Airへ移設。身構えていたがコードもCoordinatorも一切いじらず終わった。理由は全設定がenv駆動だったこと、Web検索が共有キュー1本でホスト非依存だったこと、そして先に参加していた推論Workerがpg_hbaもローカルネットワーク許可も既に通しておいてくれたこと。「過去の自分の設計判断に救われる」話と、自己修復が効かないタスク種別はPG書き込みを直接確認するという検証規律。

第25回会話を覚えるAIに『何を覚えないか』を先に教えた ― 抽出モデル6本の計測バトルと、few-shotが弱いモデルを壊した話
家庭内AIクラスタにスレッド単位の会話メモリを載せ始めた。本丸は「どのモデルで抽出するか」ではなく「何を覚えないか」をどう担保するか。まずクリーンな種データで retrieval を先に検証(hit@1 9/9・negative probeで注入しきい値0.51を確定)、次に抽出器をローカルLLM 6本×few-shot有無で計測し、12bと同点の軽量qwen2.5:7bに着地。few-shotが弱いモデルに例文を丸写しさせてFPを悪化させた失敗、「断定形だけ覚える」一押しで最後のFPを消した話、そして抽出を完璧にせず retrieval しきい値と二段で守る設計。/route無改修のままproducerを出荷するまで。

第26回AIに会話の記憶を思い出させたら、その思い出を“また記憶”するループ寸前だった ― 会話メモリ consumer と、原文/注入版を分けてPGを汚さない設計
前回作った「記憶を書く側」に対し、今回は「記憶を読んで回答に効かせる側」を実装。retrieval を /route に注入する薄いクライアントを Coordinator 側に新設し(REST直叩き・qdrant-client非依存・埋め込みはローカル bge-m3)、ヒット時だけ文脈ブロックを前置する。最大の山場は、注入版プロンプトをそのまま保存すると夜間の記憶給餌バッチがそれを再取り込みして「記憶が自分を食べる」フィードバックループに入る寸前だったこと。PostgreSQL には原文・Redisキューには注入版という一点の分離で、Worker 無改修のまま「回答に効くが記憶は汚さない」を両立させた。コアルーターへの改変はフラグゲート+完全fail-open+しきい値ゲートの三重で臆病に。会話の chat_id を thread_id として運ぶ pipe 配線まで。

第27回AIは会話を覚えていた。でも“どの会話か”は記録していなかった ― thread_id が producer まで届かず、スレッド記憶の通り道が静かに死んでいた話と、ライブ給餌での解決
前々回・前回で「記憶を書く側(producer)」と「読んで効かせる側(consumer)」を揃え、会話の chat_id を thread_id として運ぶ配線まで通したはずだった。ところが「producer を thread 対応にするには設定を1行変えるだけ」のはずの仕上げで、thread_id が書き込み側まで一切流れていない無音の穴を発見。夜間バッチは thread_id を空文字で固定し、tasks テーブルにも列が無く、auto で覚えた記憶は全部 global に落ちて consumer の thread 分岐は死んでいた。スキーマもコアルーターも触らず、done を見ている唯一の主体である pipe に「原文・回答・chat_id」を新エンドポイント /ingest_memory へ投げさせるライブ給餌で解決(応答表示後に best-effort・原文を流して記憶汚染も回避)。worker 側を thread_if_available にして「ライブ=thread/夜間=global」の二輪化が成立。実機ログで pipe→Coordinator→Worker→Qdrant の thread 一気通貫を確認。引き継ぎ資料に埋め込んだ pipe が実機より古かった逆ステイルの是正と、「恒久事実が一つの会話に閉じてしまう」次の宿題まで。

第28回会話メモリを実装したのに、同じスレッドで「この市」が通じなかった ― クロスセッション記憶とライブ文脈は別物だった話
会話メモリをクローズした翌日、同一スレッドで「柏市の天気」→「この市の特産物は?」が通じなかった。覚えていない理由が3つ重なっていた――producerは夜間バッチでしか走らない/直前ターンがweb_searchで給餌除外/そもそもpipeが最後の1発言しか送っていなかった。会話メモリの目的はクロスセッション想起で、ライブの指示語解決は守備範囲外だったという話。対策は履歴を新フィールドhistoryで送り、メモリ注入と同じ分離パターンで「キューのpromptにだけ」前置(PG・分類は原文のまま=自己増幅ループと分類汚染を回避)。自分の出力に乗ったルーティング装飾を剥がしてから履歴に入れる落とし穴つき。「機能クローズ」と「期待を満たす」は別物という教訓。

第29回会話メモリ producer のライブ給餌で項目6を完全クローズ ── そして「引き継ぎ資料より実機を見る」規律が二重実装を防いだ話
pipe の1ブロック追加(v6.5)で、会話の thread-scope 記憶をライブ書き込み。
着手前に稼働ソースを直接確認した結果、producer の投函口はすでに完成・稼働済みと判明し、
残ピースは pipe の配線だけだった。E2E 4点(thread書き込み/別ノード正答による retrieval/
自己増幅抑止/PostgreSQL非汚染)グリーンで、会話メモリは producer・consumer 両輪、
global・thread 両スコープが揃って完全クローズ。ドキュメントのステイルネスは
「完成済み機能の取りこぼし」方向にも効く、という運用上の学びを記録。

第30回バックアップは、復元するまでただの仮説だった ― PostgreSQLテストリストアと「件数が一致して一瞬焦った」話
日次 pg_dump バックアップ(mars・7世代)を一度も復元していなかったため、本番DBに触れずスクラッチDBへ流し込む非破壊テストリストアを実施。配備済み pg_backup.sh を実機確認してプレーンSQL(psql流し込み・pg_restore不要)・PG18の \restrict トークンを確定し、復元DBと本番の件数が完全一致した「予測外れ」を、本番読み込みではなく『02:00以降トラフィックなし+独立DB復元の証拠』と切り分けた(両DBの max(created_at) で裏取り)。「復元したことのないバックアップは仮説」を実測で潰し、破壊的な災害復旧手順も手順書化(本番専用・非実行)。

第31回長時間タスクをバックグラウンドに逃がしたら、引き継ぎ資料が実機より古かった ― 問い合わせの非同期化と、消しかけたエンドポイントの話
5分でタイムアウトしていた同期ポーリングに「猶予つき離脱」を追加。status=running から60秒を超えた長時間タスクだけバックグラウンドへ逃がし、Coordinator内のasyncio通知ループがdoneを拾ってNextcloud Talkへ完了通知する仕組み(既定OFF・fail-open・PGスキーマ変更なし)を実装した。着手時、引き継ぎ資料のソース付録が実機より古く、稼働中の/ingest_memoryエンドポイントを危うく削除しかけたのを実機との照合で回避。「pg_backupと同じTalkルームを流用」という設計前提も実機確認で崩れ(pg_backupはTalk未使用だった)、専用ルームを新規作成。env追記漏れによる沈黙の失敗、アプリパスワードの@化けまで、計測で一つずつ潰した記録。

第32回離脱したタスクを「見る」仕組みと「分解する」頭脳を載せた ― 結果表示ページと処理分割パイプライン、そして既にあった通知レールに1本の分岐を足すだけで待ち合わせができた話
離脱タスクの task_id を踏める読み取り専用の結果表示ページ GET /task/<id>/view(実行中は5秒自動更新・全動的値を html.escape で無害化・前段認証なしのLAN内運用前提)と、ひとつの依頼を分解して並列で解く処理分割パイプラインを実装。planner(ローカルLLM直叩き・厳格JSON)が独立サブタスクへ分解し、子は既存ルーティングに乗せて自己修復・負荷分散・能力振り分け・WoL をそのまま享受、前回の通知ループに channel=”pipeline_join” の分岐を1本足すだけで子の待ち合わせが成立、integrator が統合して親を完了にする。新しい待ち合わせ機構はゼロ。planner 到達不可時の単一縮退(fail-open)が図らずも実証され、fast-child レースの先回り・reaper の番人除外・「大きいモデルを載せたマシンの Linux 環境が自動起動しない」という到達性の発見、そして「ログが出そろう前に縮退と早合点して計測で訂正した」自戒まで。API 1ファイルのみ・Worker 無改修・スキーマ変更なし・段階0/1/2/5/6 を実機グリーンで確認。

第33回唯一正解したのは3Bの国産モデルだった ― ローカルLLM 11台に早口言葉を解かせ、best-of の judge が「正解を選べない」瞬間まで観測した記録
古典的早口言葉「にわにはにわにわとりがいる」(正解=庭には二羽鶏がいる)を、手元のローカルLLM 11台に解かせた実験記録。「小さいモデルの誤りは知識容量の限界」という当初の仮説が、容量→系統→アーキテクチャ→「日本語の出自」へと4回書き換わっていく過程をそのまま追った。1B〜14B、Transformer4系統+非Transformer3系統(Mamba・RWKV含む)の計11台が誰も「二羽」を当てられない中、唯一正解したのは3Bの純国産フルスクラッチ Sarashina で、8Bの日本語追加学習モデルすら届かなかった。真の変数は容量でもアーキテクチャでもなく日本語学習データの密度・出自だった。最後に、その正解を自作 Coordinator の best-of judge(gemma3:12b)に採点させると、judge 自身が「二羽」を知らないため誤答に満点・正解に低評価をつけ、「評価役が共有する知識の穴では best-of が機能しない」ことまで露わになった。esxi の Ollama が localhost バインドで LAN 未到達だった件を override.conf 直接編集(systemctl edit 回避・タイムスタンプ backup・プロセス側実効値で確認)で直した小エピソードも収録。

第34回小型モデルを推論プールから外したのに、外れていなかった ― 8Bフロア化と「登録から外すだけでは退場にならない」スコアリングの話

OpenWebUI の低品質の主因が「子を担う小型モデルの事実捏造」だと前回実測で確定していたので、汎用推論プールを 8B クラス(qwen3:8b / elyza-jp-8b)だけに絞った(コード改変ゼロ・各ワーカーの登録モデル設定の変更のみ)。ところが小型を作業用に1つ残すと5回中5回が小型に着地 ― 実績ゼロの 8B は初期スコア固定で、速い小型に構造的に負け続けると判明。「登録から外す」では足りず「候補から完全に消す」必要があった。さらに CPU 機の 8B は温めても疎通チェックの30秒に間に合わず常用枠にできないと実測。最終的に M1(常時の8B土台)+GPU機(上がっていれば使う best-effort)に集約し、捏造の消滅を確認。8B でも残る固有知識・専門細部の捏造は次回の RAG 編へ。

第35回退役ノードの静かな穴と、AIが書いた「最新」の一言がキーワード分類器を騙した話 — 退役設定済みのはずのノードが実は共有キューを聴き続けていた問題(moon/esxi・v6.4配布で解消)と、pipelineのサブタスク文言がWeb検索の自動判定に誤ヒットしAWS/Windows混入の誤回答を招いた問題(既定オフの明示フラグallow_auto_classifyで解消・coordinator_api.py v6.9.2)の2件を収録。

第36回土台が1台に痩せても誰も気づかなかった話と、正しいカードを渡しても8Bが数字を書き換えた話 ― 監視と知識の二本柱
Phase1(単一8Bフロア化)後の地固めとして、基盤を守る2つの仕組みを実装した。監視=汎用8B土台(macbookair常時/rtx3070ti best-effort)が1台に痩せたら状態遷移時のみNextcloud Talkへ通知(劣化・全滅・回復の3種、継続中は無音、状態はRedis永続でCoordinator再起動後の蒸し返しも防止)。rtx3070ti停止→劣化通知→復帰→回復通知の1往復を実機確認(全滅0台のみ運用上サンドボックス止まり)。知識=8Bが基盤固有事実を捏造する問題に、1事実1カード・結論先頭のファクトカードを注入。検索スコアの足切りは大チャンクのベクトル密集で分離不可→カード化で分離成立(旧βの棄却を対照再現)。最重要finding=「知らないことの捏造は正しい情報で直るが、知っているつもりのことの上書きは正解を目の前に置いても残る」:カード明記の既定値2億を8Bが記憶の20億(物理上限との混同)に上書き、競合する一般知識が無い基盤固有カードは一字一句準拠。改変禁止指示文(記憶より一次情報を優先せよ)で保持を実証。おまけに、カード拡充で自分が1事実1カード原則を破った(接続方法+カラム名を1枚に同居)ところ、分離テストが数字でそれを検出(0.47→分割後0.76)。「そう設定した/そう渡した」と「実際にそうなっている/そう受け取られた」は別物、という一点で監視と知識が繋がる回。

第37回唯一正解した3Bに、専用の通り道を作った ― 二羽・実装編:models=[]で汎用プールを汚さず、明示オプトインで誤爆を避け、当たりは4回に1回だと正直に測った話
第33回で唯一「二羽」を当てた純国産3B(sarashina)を Coordinator に組み込んだ実装編。8Bが正解注入でもマルチターン訂正でも届かない日本語ネイティブ知識(B2)の穴を、routing で埋める。2つの制約 ―(1)3Bを素朴に広告すると DEFAULT_SCORE/WEIGHT_SPEED 経由で8B主経路を奪う(2)全日本語デプロイで「ネイティブ知識」を自動判定すると偽陽性で一般質問を弱い3Bに誤送する ― を、既存RAG経路の鏡写し(専用キュー tasks:authority+独立 consumer worker_sarashina.py の register models=[]=汎用プール構造的非汚染)と、明示オプトインのみ(自動分類なし・🎌擬似モデル)で解いた。実装中に踏んだ hf.co/mmnga/ タグ404と、num_ctx を渡さず測って8Bを32768/10GBと誤計測した同時resident実測(正しくは8192で 6.2GB+4.0GB=10.2GB<16GB)も収録。効果は正直に:二羽4回で数詞到達1/部分1/脱落2、当たりは3〜4回に1回・聞き方依存。run4のNoneはPGに531字doneが入ったタイミング差で障害でなかった。配管は完全green・効果は限定的だが実在。最後に「権威」は大げさだと表示名を「日本語特化」へ(看板のみ変更・内部task_typeは据え置き)。best-of も合意も明示訂正も救えなかった穴に、routing だけが橋を架けた。

第38回「います」が「いま」に化けてWeb検索が誤爆する ― Reaper誤殺・num_ctx爆発・回帰プローブまで、静かな穴4つを count==1 と一次情報で塞いだ話
Phase 1 の土台集約で溜まった小さなバグを、同じ道具立て(count==1 アンカーパッチ・py_compile 自動ロールバック・計測してから信じる)で順に潰した記録。#1 get_all_workers の壊れハッシュで指名経路が500→worker_id欠落を warning で弾く。#2 Reaper が単一Workerの順番待ちpendingを幽霊と誤殺→REAPER_GHOST_GRACE_SEC を dead_grace から分離+_worker_has_running_task(PG権威)で busy 判定して ghost 除外・取得フロア min(dead,ghost)化。#3 Web検索誤発火の真因は対話語ではなく丁寧語語尾「います⊃いま」の部分文字列衝突(keyword=ログ3件が全て『いま』=対話語仮説を反証)→ハザード語除去・ネガ語追加は不採用・いまの/いま何は温存。#4 num_ctx 非固定で指名経路が32768/10GBに爆発→worker_base v6.4 で options.num_ctx 明示固定(launchd は bootout/bootstrap でないと env 反映されない罠付き)。おまけに回帰プローブ route_probe.py(固定14問→/route着地を自動アサート・/route/preview主体でPG非汚染・8Bフロア期待値はenv化でPhase構成変更に無改修)。静かに壊れる穴を、可観測性と一次情報で確定し反証する背骨の話。

第39回合意は「同じ答え」でなく「同じ話題」を測っていた ― Phase 3 の合意エンジンを、作る前に計測して畳んだ話
次の大物になるはずだった Phase 3「合意エンジン」(複数の異系統モデルの回答が一致すれば自動採用・割れたらエスカレーション)を、配線前に計測ハーネス consensus_probe.py で検証した記録。β をスコア分布計測で棄却したのと同じ規律。異系統8B(elyza@.43+qwen3@.196・両健全時のみ=opportunistic)のR候補を bge-m3 でコサイン化し、「合意スコア×正誤」をクロス表にした。結果は決定的:二羽(B1)で両8Bが数詞に到達せず both_wrong なのに pair_cosine=0.877(A_agree 平均0.921とほぼ同帯)。systemd(A1)は片方が事実を外し split なのに0.919。皮肉にも両者正解のすもも(B2)が0.792で全probe最低。both_wrong と both_correct が0.79〜1.0で分離不能=合意スコア(bge-m3コサイン)は「同じ話題」を測り「同じ・正しい答え」を測らない。「高合意→合意採用(judge不要)」は棄却(βに続く配線前棄却2件目)、合意は「明確な低合意<0.75→係争検知」の粗フィルタに格下げ、Tier-B本線は第37回の sarashina権威+RAG のまま。途中 qwen3 が thinking で本文を空にする穴を think:false で解決。第33回のjudge順位反転に続く「『似ている』は『正しい』ではない」の3本目 ― 負の結論を着工前に得た価値と、正しさの非代替性を埋めるのは routing だけ、という話。

第40回AIにサーバ管理を任せる第一歩は、シェルを取り上げることだった ― allowlist・deny-by-default・plan-once の三重防壁と、5ミリ秒のレースが「たまたま通った」を暴いた話
サーバ管理executor(Phase A・read-only)を新規4モジュールで実装し本番常駐化。実行係はLLM非搭載の軽量デーモンで、shell=False・argvリスト固定・独立プレースホルダのみ(埋め込み補間は台帳読込時に拒否・Dockerの{{.Names}}とのテンプレート衝突で記法を山括弧へ変更)とし、「safe; touch /tmp/PWNED」で第二コマンドが生まれないことをファイル非存在で実証。実行出力をplannerへ戻さないplan-once/execute-fixedでログ経由のprompt injectionを遮断。「何が実行可能か」(ノード同梱カタログYAML・byte同一規律)と「何に承認が要るか」(PG中央のポリシー・未登録はdeny-by-defaultで保留)を分離し、破壊系操作はカタログに載せず承認の議題にすら上げない設計。ノード別キューで宛先違いの拾い食いを先回りで排除し、共有バリデータをexecutorとCoordinator双方がimportして検証不一致を構造的に排除。テスト84 GREENと実機一気通貫(Nextcloud Talk着弾)の直後、read-onlyアクションが0.005秒で完走してキュー投入→実行→完了書き込みの往復(約5ms)が投入側のDB記録を追い越すレースを発見。「queued記録→キュー投入」への順序反転で構造的に解消し、「最初のTalk成功は偶然だった=たまたま通ったをgreenと読まない」を回帰テストごと記録した。公開サーバ側は受信ポートを開けずoutboundのみ・依存最小で攻撃面を増やさない。allowlistは能力を諦める柵ではなく、将来の賢いplannerを安全に解禁する土台。自由文writeの無承認自動実行だけは恒久不採用。

第41回「moon」と頼んだのに mars を見に行った ― LLM を頭脳に戻す三つの縛りと、全部の部品が正しいのに間違うプランの話
サーバ管理の planner(gemma3:12b・厳格JSON・fail-closed)を実装し、OpenWebUI の 🔧擬似モデルから「mars のディスク使用量を見て」→ df 実出力がチャットに返る全チェーンを配線(実行 0.003 秒)。初計測で JSON 契約は 5/5 遵守した一方、「moon のメモリを見て」が既知ノード mars に音もなくすり替わって status=ok で通過=全部品が妥当なプランの意味論的すり替えは構造検証の盲点という finding。プロンプト則(第一防衛)+ node_presence_guard(プランのノード名は指示文中に出現必須=LLM の選択を「推論」から「転記」に格下げする構造ガード)の二段で遮断し、テスト 38→42 GREEN。実行結果の待ち方はカタログの per-action sync フラグで宣言(read はインライン・将来の write は Talk 報告)。おまけ=スキーマ強化で配備済みテストが赤くなる「契約の再走」の教訓と、OpenWebUI Pipe の正本がファイルでなく Functions DB だった話。締め=「約束はプロンプトに、保証はコードに」。

第42回AIに、初めて「書く」を許した日 ― 承認は二通目のメッセージで、期限は10分、そして権限の台帳は三冊目になった話
サーバ管理の Phase B=write 解禁。systemctl restart と apt-get upgrade をカタログに載せるにあたり、柵を一枚外す代わりに三枚立てた――承認の二通目方式(プランエコー→「承認 plan_xxx」を正規表現の完全一致でのみ受理=承認判定に LLM は居ない・plan_id は人間の転記。第41回の「推論でなく転記」思想を人間側の操作にも適用)、10分の承認期限(期限切れは rejected に確定させてから理由を返す=沈黙させない)、そして sudoers の NOPASSWD 完全一致行=カタログ・承認ポリシーに続く三冊目の台帳(restart はアプリ層5サービスのみ・DB と ssh は enum にも sudoers にも載せない)。二重・並行承認は UPDATE … WHERE status=’pending’ の行数で敗者を弾き、状態確定→キュー投入の順序保証は第40回の5ミリ秒レースの教訓をそのまま踏襲。sandbox 158 GREEN からの実機適用で、記念すべき初回 write は Phase A の自分が立てた NoNewPrivileges=yes に sudo を遮断されて failed――半月前の引き継ぎ資料に「Phase B で要見直し」と自分で予言が残っていた=堅牢化フラグは実質四冊目の台帳で、フェーズ移行時に棚卸しが要る。sudo の env_reset が DEBIAN_FRONTEND を剥ぐサイレント欠落も env_keep で先回り。柵を直して再走、exit 0・0.558秒=このクラスタで AI が初めて production サーバの状態を変えた瞬間。期限切れの実測 approval_expired(850s > 600s)、apt.upgrade は gitlab-ce のメジャー版経路規制(18.9→19.1 は 18.11 経由必須)に preinstall で拒否され 267 秒 failed→展開前拒否でシステム無傷→hold して再実行 1.029 秒 done=failure_mode: idempotent「再実行で収束」の実地証明。長い write が短い read を塞ぐキュー渋滞では、⏳→Talk 縮退の経路が本番で初めて発火した記録つき。締めは「柵は、増えた。それでいい」。

第43回死んだ executor は自分の死を報告できない ― サーバ管理AIに「冗長な自己診断」を持たせ、LLMのノード選択を推論から転記へ落とすまで
mars の executor が止まると maintenance.self_status 自身も止まる自己参照パラドックスを、
ai-core に2台目 executor(worker_maintenance.pyNODE_NAME=ai-core で再利用・コード改変ゼロ・全 read ゆえ NoNewPrivileges=yes 維持)を立てて解消。
queue.length の enum パラメータ化、planner が所有格「marsの」に引かれて node=mars を選ぶ現象、
ai-core 限定の専用アクション queue.length_mars_peer で「ノード選択を推論から転記へ格下げ」+独立レート枠、
誤ノードは unknown_action で fail-closed 拒否——を実機 OpenWebUI で実証。

第44回見張り番を二人にしたら、片方の急報が長話の後ろで待たされた ― 相互監視の逆向きを閉じ、診断の実行レーンを書き込みから分離するまで
前回作った片方向(ai-core→mars)の鏡像で mars→ai-core を足し相互監視を完成。さらに read 専用 executor を分離し、長い書き込みの後ろで診断が詰まる問題を解消。ノード選択もレーン振り分けも「推論」でなく「転記」で。

第45回AIにサーバ更新を任せたら、失敗が”静かに”なった話 ── 粒度フラグ・版ズレ・無音停止
apt の良性なリポジトリ素性変更を Label フィールドだけ受理する設計判断、投入は成功したのに executor が無言で止まる穴、権限(sudoers)と設定(カタログ)の版ズレが原因を exit=1 の下に隠す落とし穴。分散システムでいちばん怖い”静かな失敗”を三連続で踏んだ実機記録と、次の対処(能動監視・失敗トリアージ)への接続。

第46回4日間、誰も気づかなかった停電の話 ― 承認から14時間31分後に本番サーバが再起動された理由と、期限が守っていなかった片側
停電で外付けHDDが外れ、systemd の暗黙の依存(RequiresMountsFor=/home)で executor が停止。HDD は40秒で復帰したが systemd は依存で止めたサービスを起こし直さないため、4日3時間の空白が生まれた。原因究明では狭い grep で二度誤診し、時間窓の全ログで USB 切断→I/Oエラー→ジャーナル中断→read-only→停止→アンマウントの因果連鎖を確定。対策は UPS でも 178GB の引っ越しでもなく、du で測って判明した「基盤が使うのは 24MB だけ」に基づく /opt への移設(RequiresMountsFor から /home が消えたことで直接証明)。空白期間に承認済みの svc.restart apache2 がキューで眠り、復旧の瞬間に実行された=承認TTL 600秒の約87倍にあたる14時間31分後の暴発。承認TTLが守っていたのは「承認→投入」だけで「投入→実行」に期限が無かったため、投入時刻を荷物に載せる方式で対称の期限を実装(executor にDBライブラリを積まない設計を壊さないための判断)。あわせて ProtectHome=yes が df に偽の値を返す罠、reaper がDB書き込み失敗の2秒後に「done」と report していた件、失敗トリアージ Layer 1(LLM 非使用)の実装も記録。


現在の構成(2026-07-12時点 / Coordinator API v6.9.8・OpenWebUI Pipe v6.11)

ノード 役割
ai-core (192.168.0.40) Coordinator API / Redis / PostgreSQL / サーバ管理 planner・承認判定・結果集約 reaper
macbookair (192.168.0.43) M1 16GB。汎用推論の常時土台 = qwen3:8b・elyza-jp-8b。gemma3:12b(planner / integrator / judge)・sarashina2.2-3b(日本語特化)
rtx3070ti (192.168.0.196) RTX 3070 Ti。qwen3:8b のベストエフォート参加(冗長化枠)
rag-tools (192.168.0.42) Qdrant + bge-m3(RAG 検索・埋め込み)
moon (192.168.0.4) RAG 合成用 LLM(llama3.2:3b)。汎用ルーティングからは退役
esxi (192.168.0.41) 汎用ルーティングから退役(指名時のみ)
mars (192.168.0.1) WordPress / Nextcloud / SearXNG(Web 検索)/サーバ管理 executor(LLM 非搭載・read 6+write 3 の allowlist 実行)
Windows Server (192.168.0.2) フロントエンド OpenWebUI

補足: 汎用推論は 8B 級に統一(Phase 1)。小型 CPU モデルは自動ルーティングから退役し、指名実行のみ。固有名詞トリガーの自動 RAG ルーティング(β4-α)稼働中。サーバ管理は 🔧擬似モデルから自然文で指示でき、read 6アクションは即時実行、write 3アクション(サービス再起動・apt 更新)は deny-by-default の承認ポリシーと二通目承認・sudoers 完全一致行の三重の台帳の内側で動く(Phase B・2026-07-12 解禁)。