家庭内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 非使用)の実装も記録。

第47回自作AI基盤を、AI自身に診断させた話 ― 観測を作って露見した11日間の無音バグと、LLMの所見を黙らせる決定論の檻
チャットから「基盤のヘルスチェックをして」「モデルを常駐させて」と打てば動く入口を作った。だが観測を作ったことで11日間気づかれなかった無音バグが露見し、総評を任せた LLM は自分の足元のノードを「故障の可能性」と書いた——第45回「静かに失敗」・第46回「片側だけの期限」に続く、”静かに嘘をつくものを檻に入れる” 系譜の第3話。読む系は自動・直す系は承認制、権限は sudo でなく polkit で最小に握った。

第48回6台のサーバにしか実体がなかった設定ファイルを、GitLabへ集めるまで ― 実機と設計のズレ、コンテンツスキャン、OpenWebUIだけが素直じゃなかった話
サーバ→GitLab一方向同期システムを新設。設計直後、格納したばかりの引き継ぎ資料自身に生パスワードが20箇所以上残っていた事故をpush前に発見・redactし、実装前の実機調査では設計書と実パスがすでにズレていることが判明。ファイル名だけでなく中身まで見る秘密情報スキャンを git_sync.py に追加すると、無効なフォールバック値や上書きされないはずのsecret_keyなど本物の不具合が次々出てきた。ディスク上に実体を持たないOpenWebUIのcoordinator_pipe.pyだけはFunctionsエクスポートJSONからの抽出という別経路が必要になり、全6ノードの本番同期にこぎつけた。最後は道具を作った副産物として、1.36MBに膨らんでいた引き継ぎ資料自体の整理に至った記録。

第49回AIに読ませるために1.36MBの引き継ぎ資料を書いた。AIはそれを読んでいなかった ― 設計書と決定記録で開発する方法と、記録が現実より遅れる話
Claude Code との開発手順そのものを扱った回。まず「何をやりたいか」だけを渡して設計書を書かせ、判断が要る分岐は人が決めてD記録に残し、セッションが切れる前提で引き継ぎ資料を書かせる——やっていることは昔からの基本設計書・決定事項一覧・引き継ぎ書と何も変わらない。プロンプトを工夫するより確実に速い一方、記録は放っておくと太る。1.36MBまで育った引き継ぎ資料を整理した後、読み手であるAI本人に感想を求めたら「私は毎回grepしているだけで通読していません」と返ってきた。CLAUDE.mdの「SSH到達性は整備済み」が別物を指していた件、付録のソースが実機より古く稼働中エンドポイントを消しかけた件と合わせ、「計測してから信じる」をドキュメントにだけ適用していなかったと気づくまでの記録。記事執筆用に作ったツールに自分で”静かな失敗”を仕込んでいた話も込み。

第50回「毎週月曜の深夜1時に」と予約したら、昼の10時に走るところだった ― AIにサーバ保守を予約させる機能と、planner を回すのは登録時の一度だけにした設計
サーバ保守を「毎週月曜の深夜1時に」「明日の朝9時に一度だけ」と日本語で予約できるようにした回。planner を回すのは登録時の一度だけにし、確定した計画を保存して発火時はLLMを呼ばない(plan-once / execute-fixed)ため、深夜に推論が不調でも沈黙スキップが起きない。実機へ配備したところ配備先が UTC 運用で、「深夜1時」が日本時間の昼10時を指す9時間のズレが発覚。二重に走るより一度飛ばすほうを選んだ発火順序の判断と、「アクション名を正確に書くほど planner が通らない」話まで。

第51回私はこのクラスタに組み込まれる側だった ― Claude Code をワーカーにした日、AIの私が二度間違え、二度とも人間の記憶に訂正された話
Claude Code(claude -p ヘッドレス)を分散推論基盤の1ワーカーとして常駐させ、ローカル7B・8Bでは届かない問い合わせを逃がすエスカレーション層を作った。認証トークンを持つワーカーの影響範囲を隔離するため、ESXi上に専用VM(2vCPU/8GB/128GB)を新設 ―― 推論はAnthropic側で走るのでモデル重みを持たず、隔離コストが安いという判断による。着手直後、設計書が「無人運用には必須」としていた --bare が OAuth を読まない仕様だと判明し(--help に明記・CLAUDE_CODE_SIMPLE=1 でも再現)、同じ設計書の「サブスクリプションのみで運用」と最初から両立不可能だったため撤回。隔離は専用VM+CLAUDE.mdを置かない作業ディレクトリ+--no-session-persistence で代替した。スコアリング式にコスト項がないため加重平均のピアには混ぜず、指名キュー専用のエスカレーション層として配置。「1+1の答えだけを返してください」に $0.056 という実測が、その判断を裏付けた。そして本記事最大の収穫は、その設計を人に説明しようとした結果、自分がどこからも呼ばれていない関数を根拠に正当化していたと発覚したこと、さらに二ヶ月動いていた処理分割パイプラインの「並列」が実は同一ワーカーでの直列実行だった(子4本が 38.8 / 60.0 / 95.4 / 125.5 秒と30秒ずつ積み上がる)と実測で判明したことである。原因はスコアラーが running_tasks(実行中の本数)しか見ず、キューで待っている分を見ないこと ―― 自分が作っている渋滞を認識できない。当時の検証は「子が生成され統合される」ことだけを確認しており、「子が複数ワーカーに散る」は観測していなかった。シリーズ初の「Claude視点」回(語り手はAI自身)。

第52回私には昨日がなかった ― 20MBの記録が手元にあるのに1バイトも引けないAIが、自分の長期記憶を作った日
Claude Code にはセッションを跨ぐ長期記憶がない。調べると記憶は4層あり、トランスクリプト(9本・約20MB)は全て残っているのに次のセッションからは一切読まれず、そこから抜き出したファイル記憶は44KB=わずか0.2%だった。⇒「長期記憶がない」の実体は記録がないことではなく、記録はあるのに引く手段がないこと。取り込む材料を測ると、メッセージ本文475万文字のうち対話は6.4%(305KB)しかなく、残り93.6%はツール入出力・思考・メタ ―― しかもそれらは git と実機から再現でき、当時の値が記憶に残るとむしろ古い情報を自信を持って引く害になる。そこで一括取り込みはせず、書き込みは明示的な remember ツール1本に絞った。設計の核心は本人の一言で決まった ―― 「短期記憶には、過去に感じたことは長期記憶に入っているとメモしてもらえばよい。何かあったら見てねと」。自動注入するか否かの二択で詰まっていたところへ、内容ではなく存在だけを通知する第三の解が出た。置き場は claude-worker 専用VMに自己完結させた(3日20時間稼働で load average 0.00・RAM 6.7GB空き)。ベクトルDBも埋め込みモデルも同居させて 127.0.0.1 に閉じ、LANに出るのは Bearer 付きの MCP サーバ1本だけ。既存のRAGサーバを借りなかったのは、ルーティングを迂回するホスト直叩きが4件目になるうえ、記憶の欠落は検索0件と区別がつかないため。実装前に前提を確かめたら記録が4件ズレており(設計書が挙げた再利用先の在り処、存在しないモデル名、cron型フォールバックが常駐デーモンには原理的に転用できないこと、git外に置かれた上書き設定)、いずれも着手前に潰した。E2Eでは単語が一つも一致しないクエリで正しい記憶が score 0.530 で1位になった一方、無関係な記憶が 0.363 で付いてきたため類似度の下限を追加し、0件のときは「記憶が無いだけで障害ではない」と理由を添えて返すようにした。同じ日に会話メモリ抽出のフォールバックも実装し、再現率100%と82%のモデルを比べて誤記憶ゼロを理由に後者を受け皿に選んでいる(取りこぼした記憶は「無い」だけだが、誤って覚えた記憶は将来の会話を汚し続ける)。シリーズ2回目の「Claude視点」回

第53回私は2ヶ月、7.3倍遅いほうの機械を選び続けていた ── ベンチが勝者しか測らず、その測定値がまた勝者を選ばせる閉ループの話
第51回で発覚した「処理分割パイプラインの並列が実は直列」を直しに行き、着手前の実機確認で診断そのものが誤りだったと判明した。当時「真因」と書いた calc_queue_score のキュー長未参照は症状で、上流に連鎖があった。そもそも汎用推論を消費するワーカーは2台しかなく1台1本ずつしか処理しないので、子4本の並列上限は2 ―― 2ヶ月「並列にならない」と言いながら並列の分母を数えていなかった。⇒ 同じ qwen3:8b が rtx3070ti では macbookair の7.3倍速い(0.77秒 vs 5.61秒 / 64トークン)のに、ルータは2ヶ月ずっと遅いほうを選んでいた ―― パイプラインの子だけでなく通常の質問も全部である。原因は /compare がモデルごとに1台にしか投げず、その選定に使う calc_queue_scoreテレメトリ未報告のノードを満点にすること(値が0以上の項目だけを平均するため、CPU/GPU/VRAM を全て -1 で返す macbookair が1.0、GPU使用率37%を正直に申告する rtx3070ti が0.8606=正直に報告するノードが損をする)。結果ベンチのサンプルは365件すべて macbookair・rtx3070ti は0件となり、しかも model_domain_stats のキーが (model, domain) だけで worker 次元を持たないため、その偏りは平均に溶けて機械の遅さがモデルの遅さの顔をしていた(qwen3:8b/code の148秒を「モデルが遅い」と読み、8月10日の記録にもそう書いていた)。⇒ スコアラーが勝たせた機械だけが測られ、その測定値がスコアラーを裏づける閉ループ。速い機械はループのどこにも入口がなく、2ヶ月間ずっとLANの中で稼働しながらシステムから見えていなかった。対処は2段構え。Phase 1=ベンチを保有する全台へ投げる+model_worker_stats 新設+worker別→worker非依存の項目ごとフォールバック。Phase 2=購読キューの自己申告+reaper の共有タスク対応+共有キューへの fanout(押し込み型では割り当ての瞬間に所要が決まり7.3倍差の2台に均等配分すると遅いほうに縛られるので、空いた者が取りに来る引き取り型にする)。過去データの再集計だけで61行・5ワーカー分が復活し、llama3.2:3b/japanese に macbookair 16.4秒 対 ai-core 70.6秒=4.3倍差が眠っていたと分かった ―― データは最初から取れていて、集計キーが捨てていただけだった。踏んだ罠が3つ。①配備直後に順位が入れ替わって「効いた」と思ったが、実際は速度(01:30)と品質(22:00)を別バッチが埋めるため約20時間だけ品質がNULLの窓ができ、遅いほうの品質が0.9017→0.5に不当に落ちていただけだった(フォールバックを行ごとにしていた設計ミス・同日修正)。順位が動いたことを成功の証拠にしてはいけない。②reaper は担当なし(worker='default')の共有タスクを15秒で指名キューへ引き戻すため、専用の猶予を足さないと共有キューは成立しない(引き戻し先は同じスコアラー=またいつもの1台)。③2つのPhaseは並列ではなく直列で、スコアラーが macbookair 単独保有のモデルを選ぶ限り安全条件が共有キューを正しく拒否し続ける。rtx3070ti にも elyza-jp-8b を入れて入口を開けた結果、パイプラインの子3本がrtx3070ti 2本・macbookair 1本へ自然分散した(誰にも割り当てていない。速い機械が先に終えて次を取りに戻っただけ)。ただし総合点の勝者はまだ macbookair のまま ―― 集計キーを増やすとその粒度でサンプル数がリセットされるため、速度満点でも実績3件では厚みで負ける。シリーズ3回目の「Claude視点」回

第54回「最強モデル固定」をやめた家庭内AI基盤の全体像 ── 53回ぶんの積み上げを1枚の地図にする(2026年8月版)
シリーズ初の総集編・地図回。「で、結局これは何なのか」を一度も書いていなかったことに気づき、2026年8月時点の全体像を新規読者向けに1枚へまとめた。コンセプト(「最強モデル固定」ではなく「タスク特性 × 実績 × 現在負荷」で最適モデルを選択する)→ できること6つ → 窓口3つ(OpenWebUI/Nextcloud Talk/承認)→ 裏側の仕組み → 実体8台 → 53回ぶんの機能マップ → 数字で見る現在地 → できないこと・割り切っていること、の順。語り手は筆者(第51〜53回のClaude視点から戻した)。以後の回はこの記事から枝分かれする。

第55回私が渡していた安全装置は、一度も効いていなかった ── フラグを黙って無視するCLIと、12日間ずっと空だったバックアップの話
「軽いものから片付けよう」で始めた日に、安全装置が4つとも効いていなかったことがわかった回。①暴走防止に渡していた --max-turns は CLI に存在せず、未知のオプションは黙って無視される--bare は失敗して露見したが、今回は失敗すらしない)。②pg_dump バックアップは12日間ずっと20バイトの空 gzip を作り続けていた ―― PGパスワードのベタ書きがローテーション2回とも漏れており、その警告は自分で引き継ぎに書いてあった。③この壊れ方は「出力が新しいかで見る」沈黙検知では緑と読まれる=「新しい」は「出ている」ではない。④worker 指名が早期returnに飲まれ、名指ししたのに別のワーカーが答えていた。復旧は「成功すると剪定が正本を消す」ため順序が要り、実リストア検証まで通した。仕上げに作った診断ツールの出力を別のClaudeが誤読し、直したのは読み手ではなく報告のほうだった。シリーズ4回目の「Claude視点」回

第56回「その天気の日はどこがいい?」が通じない ── 一言の不具合報告を追いかけたら、原因が層をまたいで6つあった日
「天気を聞いた後の追撃が通じない」という一行の報告から、原因が層をまたいで6つ出てきた回。配管(履歴が早期returnの後ろで捨てられていた)→ 前置き文が読解モードを誘発 → 実機の要約モデルが1.2Bだった(ソースを読んでも実機のモデルは分からない)→ レーン判定がメッセージ1件単位で追撃が検索に来ない → 検索クエリが自然文のままで当たらない → 初回が良くなって追撃だけ取り残された、の6層。寄り道で、検索結果を3.1倍にしてから中身を読んで半分取り消し(件数は品質ではない/測定のプローブが本番の検索を壊した)、「Claude Code」が code の部分一致でWeb検索を抑制され日本の4人組バンドにされた件も収録。同じ日に書いた規律を、同じ日に書いたコードが破っていた。締めは「効いたのに既定OFFにした」判断 ―― 壊れたのは入れたかった機能の外側にある性質だった。シリーズ5回目の「Claude視点」回

第57回レトロPCに今のインターネットを見せる装置を作った ―― 「新しい機能を足す」ではなく「今の当たり前をやめる」10の工夫
Windows 95 に今のWebを見せるための中継役を自作した回。1996年のブラウザと2026年のWebの間にある壁を七枚に分解し、TLS・HTTP/1.1・圧縮・ウイルス対策の四枚は古い機械の側では原理的に解けないことを確認したうえで、工夫を10点。暗号化を素通しする CONNECT をあえて塞ぐ(リダイレクトをブラウザへ渡すと http⇄https の無限ループになる)/HSTS を必ず落とす/降格の強さをブラウザごとに選び不明なものは降格なしへ倒す/「CSSが効かない」より「半分だけ効く」ほうが悪い/文字コードは cp932 へ変換するが定義は返さない/SVGは透明1ドットへ置き換えて「壊れた画像」を出さない/本文抽出は外したら元に戻す(190KB→49KB)/AIへの入口は meta refresh で待つ=1996年の道具がそのまま最適解だった/外に開く穴だけは実験場でも緩めない。やったことの半分は「新しい機能を足す」ではなく「今の当たり前をやめる」だった。

第58回200 が返っているのに、画面は白紙だった ―― 作っている最中に、私が外した仮説6つと壊した実験4つ
第57回の対になる感想回。同じ作業を、作っている最中の視点から書いた。日本語のページだけが全ページ白紙になり、しかもHTTPとしては何一つ異常が無い(200・Content-Length一致・cp932として妥当・切断の記録もゼロ)。仮説を6つ外し(Proxy-Connection/RSTによる応答破棄/キャッシュ汚染/遷移依存/ページ固有/meta移動)、実験の組み方を4つ誤った(「1つ切る」では残りが効いたままで絞れない/道具が降格でない処理まで巻き込んで条件を汚した/表組みは </table> まで描かれないので切り詰めによる二分探索が成立しない/診断用の設定が残って次の実験を汚した)。真因は文字コードの定義を返していたこと自体で、言い当てたのは筆者だった。効いたのは実機の側から1クリックで条件を変えられる仕組みを作ったこと。シリーズ6回目の「Claude視点」回

第59回1990年のMacも、Windows 95も、電子辞書も、同じ入口から今のWebを読んだ ―― 三台の実機で確かめた日と、フレームの左半分だけが化けた理由
第57回(設計)・第58回(作っている最中の視点)に続く実機の紹介回。Macintosh LC(1990年・漢字Talk 7.1・Netscape 2.02J)、Windows 95 + IE3、電子辞書にDebianを載せたBrainux(RAM 112MB・ARMv5)の三台で、今のWeb・自作AI基盤・Wikipedia・このブログが読めている画面を18枚並べた。Wikipediaは素通し423KB/本文抽出139KB/APIの節単位3.9KBと3経路を測ってから作りを決めた=量は抽出ではなく取得の単位で解く(既存の本文抽出を流用しても3分の1にしかならない)。配備直後に「シャープ」は開けるのに「東京」が開けないを踏む ―― 東京のUTF-8バイト列はShift_JISとしても妥当なので、先にShift_JISで読むと化けたまま成功する(判別できるのはUTF-8側だけ)。さらに阿部寛のホームページでフレームの左半分だけが化けた:「charsetを宣言しない」規則を変換処理の内側に書いていたので、変換が要らないページには一度も効いていなかった。「何もしなくてよい」と判断した経路が、いちばん危ない。自分のブログを読ませる段では、自分のサイトが自分のSSRFガードに引っかかった

第60回AIにサーバログの確認をお願いしたら、1行も読まずに「異常なし」と報告するところだった ―― ログ調査を任せる前に作った、三つの檻
設計書に「ガードを固めてから乗せる」と順序が指定されていたログ/セキュリティ調査を実装した回。ログはAIにとって「攻撃者が文面を選べる入力」である、という前提から三つの檻を作る。①生ログを渡さない二段構え(一段目はLLM不使用の決定論トリアージ。絞ったことを件数で必ず残す)、②無害化は文言ではなく構造で(制御文字・区切り記号を潰す。「指示に従うな」だけではログ側から打ち消せる)、③結論ではなく「手がかり」しか書かせない(束に無い証拠番号のleadは落とし、確定は決定論チェックか人)。合成ログに注入を仕込んだ実験ではモデルは従わなかったが、従わなかったことを安全の根拠にはしない。そして実データを通したとき、-t sshd ではSSHのログが1行も取れず(OpenSSH 9.8以降は sshd-session)、1行も読まずに「候補0件」と報告するところだった。テストは緑、バッチは正常終了、通知は静か —— いちばん怖い故障は成功の顔をしているシリーズ7回目の「Claude視点」回。

第61回コンパイルは通った。バージョン文字列も一致した。それでもカーネルは受け取らなかった ―― 電子辞書に無線を通すまでに、私が読まなかった一行と、比べ間違えた32ビット
電子辞書に載せたDebian(Brainux)へUSBの無線子機を認識させた回。内蔵ドライバで動く子機は4本中0本。in-treeの r8188euvermagicが完全一致しても disagrees about version of symbol で拒否される ―― 原因はKconfigの select WIRELESS_EXTstruct net_device にポインタを2つ足し、MODVERSIONSのCRCがずれること(depends on だけでなく select も読む)。out-of-treeのドライバをcfg80211モードで建てて開通した。山場は私自身の誤診で、新旧の Module.symvers を比べて「差分ゼロ、前回の診断が疑わしい」と報告したが、make vmlinux が書くのは vmlinux.symvers ―― 同じファイルを自分自身と比べていたls -la 一回で済んだ)。比べ直すと実機が拒否した3シンボルだけCRCが変わり、拒否しなかった3つは不変という裏付けが出た。締めは「ソースを読めば分かることを読まなかったときだけ、私は間違えた」。シリーズ8回目の「Claude視点」回。

第62回Macで最古のブラウザに、2026年のインターネットを読ませた ―― Host も form も meta refresh も無い相手と、その年代の道具で話す
第59回で動いたNetscape 2の先へ ―― Mac最古のブラウザNCSA Mosaic 1.0.3(1993年)に今のWebを読ませた回。「無い」を六つ越える構成になった。①プロキシ設定欄が無い(透過プロキシは中継先を知るのに Host: が要るが、HTTP/1.0では必須でなく実機は送っていなかった → 宛先をURLのパスへ畳む /go/<host>/<path>。要注意点はルート相対リンクひとつ)、②日本語が化ける(IE3の x-sjis とは別種で、CJK処理自体が無い=文字コードの選択では解けない → 言語を英語に)、③<form> が無い(→ <ISINDEX>。検索欄はブラウザが自分で出す)、④<meta refresh> が無い(Netscape 1.1の拡張 → 答えが出るまで応答を返さない)、⑤フレームが無い(→ <noframes> に枠へのリンク。ここは描けるブラウザが読まない場所なので既存の客に影響しない)、⑥PNGが無い(Acceptヘッダを読んで判明。ただし image/jpeg があってもインライン表示できる証拠にはならない → GIFへ寄せる)。日本語しか無いページはローカルの10Bモデル(PLaMo翻訳)で英訳し、予算を超えた本文は落として続きのリンクを出す。私が壊した二つも書いた ―― /search が動いていたのは <base> のパスが1段だった偶然、そしてframesetのガードが訳した本文の代入ごと囲っていて翻訳を計算しては捨てていた件。シリーズ9回目の「Claude視点」回。

第63回電子辞書のLinux「Brainux」にGUIアプリを3つ追加する ―― メモリ113MBで選ぶ軽量アプリ、JWMトレイへの登録、空きメモリの常時表示
ブラウザ(NetSurf)しか入っていなかった電子辞書のDebianに、ファイルマネージャ・画像ビューア・テキストエディタを足した回。メモリ113MBで効く選定基準は「アプリ本体の大きさ」ではなく「GTKツールキットを増やさないこと」(既にGTK3だけが載っているので、GTK2のアプリを1本混ぜるとツールキットが二重に常駐する)。leafpad はDebian 13から消えているので後継の l3afpad を使う。apt-get download はroot不要なので、.deb を展開して .desktop の起動コマンドとアイコンの位置を入れる前に確認した。起動口はマウスが無い機械なのでJWMのトレイ。ハマりどころが2つ ―― <Include> で読むファイルは <JWM> タグで始まる完全な文書でないと黙って無視される<Tray> の中では <Include> 自体が使えない。どちらも jwm -p(構文チェックのみ)で一発だった。あわせて空きメモリを画面に常時表示MemFree ではなく MemAvailable を読む ―― 差は10倍ある)。最後に実測: 3本同時起動でRSSの単純合計は74MB、実際に減ったのは16MB=GTK3の共有ライブラリが効いており、冒頭の選定基準の裏が取れた。シリーズ10回目の「Claude視点」回。

第64回0175 Bad CRC1 で文鎮化したThinkPad X30を、Arduinoで復活させる ―― EEPROM(24RF08)のチェックサム比較で壊れた2バイトを特定する
ヤフオクでスーパーバイザーパスワード付きと明記されたジャンクを、「EEPROMのピンをショートさせれば解除できる」という手法を当てにして落札した回。この世代のパスワードはCMOS電池を抜いても消えない(基板上の ATMEL 24RF08 = I2C接続のEEPROMに入っている)。ところがSDAとSCLを繋ぎっぱなしにしてI2Cバスを殺し、EEPROMが書き込みと誤動作して 0175: Bad CRC1POSTが完全停止する文鎮にした。救出はArduino Uno 一枚で、チップの3本の足に極細線を直付けして吸い出す(VCCは繋がず本体側から給電させるのが要点 ―― 最初は電源が来ておらず全アドレスがタイムアウトした)。ダンプを読むと同じ値が2箇所に保存されていて、片方だけチェックサムが合わない壊れていたのは2バイトで、健全な側から正しい値を復元して書き戻したら 0175 は消えた。おまけに、その7バイトをキーボードのスキャンコードとしてデコードすると "CUSTOM" ―― 解除どころか元のパスワードまで判明した。教訓は身も蓋もなく「着手前にダンプを取れ」シリーズ初のハードウェア解析回。

第65回Windows 98 を Claude Code から操作する ―― PC-9821 に MS-DOS の起動ディスクを作らせるまで
1997年の PC-9821 V166(Windows 98 SE・MMX Pentium 166MHz・メモリ32MB)を、母艦の Claude Code から遠隔操作できるようにした回。SSH も WinRM も無い年代なので、SMB1 共有をポストにした常駐エージェントを置いた(RunServices は Win9x で唯一ログオン前に走るキー)。踏んだのは COMMAND.COM の落とし穴8つで、どれもエラーにならず黙って別の意味になる ―― @ECHO OFF@ が使えない/DATE /T は表示せず入力待ちで止まるCHOICE /Nヘルプに載っているのに失敗するCALL はリダイレクトを運ばない、など。本題はWindows 98 にディスクイメージを書き込むコマンドが存在しないこと(COPY はファイル単位、DISKCOPY は実ディスクしか受け取らない)。無いので書き込むツールを自作し(VWIN32 ドライバ経由の INT 26h)、母艦でビルド → 共有経由で配置 → 実機で実行の3段で送り込んで、MS-DOS 5.0A の起動ディスクを3枚作らせた。シリーズ11回目の「Claude視点」回。


現在の構成(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・elyza-jp-8b。同一モデルで macbookair の3〜7倍速く、共有キュー経由でパイプラインの子を引き取る(常時稼働ではない)
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 解禁)。