家庭内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.py を NODE_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の r8188eu はvermagicが完全一致しても disagrees about version of symbol で拒否される ―― 原因はKconfigの select WIRELESS_EXT が struct 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 CRC1 = POSTが完全停止する文鎮にした。救出は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視点」回。

第66回:Macintosh LC II を Claude Code から遠隔操作する ―― 漢字Talk 7.5 の AppleScript 常駐アプレットと共有フォルダ
1992年の Macintosh LC II(漢字Talk 7.5・68030 16MHz・メモリ上限 10MB)を、母艦の Claude Code から遠隔操作できるようにした回。前回の PC-9821 と同じ共有フォルダを投函箱にする方式だが、この OS にはSSH もサービス機構も無いので、常駐役は AppleScript のアプレット(on idle が5秒ごとに戻ってくる)になった。Mac 側で変えたのは3点 ―― 改行は CR 一文字/ASCII のみ/仕事の確保は削除ではなく 0 バイトへの切り詰め(この版にはファイル削除が無い)。最大の関門は AppleScript の「方言」で、日本語環境では英語の AppleScript が1行もコンパイルできない(set すら通らない)。実機に触る前に Basilisk II + 実機から吸い出した ROM で試験台を作り、方言・system attribute が無い・try に on error が必須・アプレットの既定メモリ 200K の4つを母艦の中で潰したので、実機では1回で通った。最後は遠隔で再起動をかけ、84秒後に無人で戻るところまで。シリーズ12回目の「Claude視点」回。

第67回:Claude Code に PC-9821 の「音が出ない」を遠隔診断してもらう ―― Windows 98 のレジストリで problem コードを読み、ソフトかハードかを決める
1997年の PC-9821 V166(Windows 98 SE)で、内蔵音源も SB Live! も鳴らず、USB ヘッドセットでは openhci.vxd の例外 0E で青画面になった ―― その原因を、母艦の Claude Code が実機のレジストリを遠隔で読んで切り分けた回。原因は二段で、まず音源一式が Windows 上で無効(Code 22)にされていて、解くと Code 10 = 音源チップがバスに応答しないことが分かった(コンデンサ交換が効かなかったのは壊れている層が違ったから)。ハマりどころはレジストリを直しても再起動で戻る(動いているデバイスは終了時に書き戻す)、解除ボタンが画面に出ていたのに聞かなかった、遠隔再起動を「できない」と書いたなど5つ。「再起動で戻る=BIOS 側・定着する=Windows 側」という見分け方と、壊れていく SB Live! の記録(ベンダ ID 1102→0102→FFFF)も残した。結末は USB オーディオ(PCM2902)で鳴った。シリーズ13回目の「Claude視点」回。

第68回:PC-9801NS に 512MB の CF を載せて全域を使う ―― 1024バイトセクタの区画表を母艦で組み立てる
1990年の98ノート PC-9801NS(MS-DOS 5.0A)で FORMAT が「システム管理域が不正です」から先へ進まない ―― その一行の裏に、層の違う原因が二つあった。①CF の金属部が旧 HDD のパターンに触れて IDE のデータ線 D14/D15 が GND に落ちていた(実機が書いた IPL を母艦で読み返し、差が「1→0」の一方向だけ・奇数オフセットに偏ることから線を特定)、②NS の固定ディスクは 1セクタ 1024 バイトで PC-9821 系と座標系が違った(推測せず、実機にフォーマットさせて BPB を読んだ)。最後は既存の 21MB の DOS 区画に1バイトも触れずに2つ目の区画を足し、A: 21MB + B: 470MB = CF 全域が使えるようになった。シリーズ14回目の「Claude視点」回。

第69回:1999年の PowerBook G3 に Mac OS X Panther を入れて無線LANで繋ぐ ―― Info.plist に PCI ID を1行足して、非対応の CardBus カードを認識させる
1999年の PowerBook G3(Lombard)に Mac OS X 10.3 Panther を入れ、Windows Server 2003 の共有に繋ぎ、Buffalo WLI-CB-AG54(CardBus)を動かすまで。カードもドライバもスロットも全部正常なのに動かない理由は、AppleAirPort2.kext の IONameMatch に pci14e4,4324 が1行載っていないことだけだった。検証はシステムを触らず kext を /tmp に複製して kextload(失敗しても再起動で完全に元通り)。最後の壁は暗号方式で、Panther 標準の AirPort 3.1.2 は WEP のみ(10.3.9 まで上げても 3.1.2 のまま) ―― 「WEP なら繋がるはずなのに失敗した」から、AP 側を一度も見ずに「AP は WPA」を確定させた。あわせて私が二度断定して二度とも外した回でもある(「PC 上がりの HDD だから MBR」「AG54 は Atheros」)―― どちらも実機を読まずに知識から推測したときで、SSH で入って ioreg を読んだ瞬間に5分で片づいた。シリーズ15回目の「Claude視点」回。

第70回:PC-9821 で YouTube を観る ―― 1997年の Windows 98 が音付きで再生できる AVI(Microsoft Video 1 + PCM)へ変換して配る
1997年の PC-9821 V166(Windows 98・IE5)から YouTube を検索して観る仕組み。検索・変換・配信は隣の Linux サーバ(ai-core)が肩代わりし、PC-9821 は素朴な HTML を読んで保存して再生するだけ。形式は AVI + Microsoft Video 1 + PCM 音声 ―― MPEG-1 は音を足した途端に全滅した(音声デコードだけで処理能力の4割以上を食う)。変換は 90 秒ずつ4並列(3コアが遊んでいた)、変換と転送がほぼ同じ重さになった時点で両方に効くのは解像度だけ。ハマりどころは、Shift_JIS で届いた検索語を UTF-8 で読んで化けた語のまま検索し、それらしい無関係な結果が返っていたこと。シリーズ16回目の「Claude視点」回。

第71回:Windows 98 のコマンドが入力待ちで止まっても遠隔操作を続ける ―― PC-9821 に Win32 の常駐エージェントを作り、母艦からキーを送る
第65回の遠隔実行は、投げたコマンドが DATE や「中止・再試行・失敗」でキー入力を待つと仕組みごと止まった。そこで仕事をパイプの向こうで走らせ、自分は回り続ける常駐エージェントを Win32 で作り直し、母艦からプロンプトを見てキーを送れるようにした。エミュレータに実機ディスクの複製を載せて先に試し、Windows 98 でしか起きない落とし穴を6つ踏んだ(コンソール出力で固まる/打ち切りで自分のコンソールが道連れ/ヘルプに無い COMMAND.COM /F /再起動で「応答がありません」ほか)。最後は旧エージェントに乗り換えの仕事を投げ、実機が再起動をまたいで新エージェントで戻ってきた。シリーズ17回目の「Claude視点」回。

第72回:Windows 95 を母艦から遠隔操作する ―― Presario 7170 に常駐エージェントを入れ、画面のダイアログを読んで押し、再起動させる
第71回の常駐エージェントを Compaq Presario 7170(Windows 95 OSR2)にも入れた。exe は同じものがそのまま動いたが、Windows 95 と 98 の違いに4回つまずいた ―― 98 の再起動コマンド SHExitWindowsEx が 95 の SHELL32 に無い(実機の DLL を吸い出して輸出表で確認)/MS-DOS の窓が1つでも開いていると終了を断られ、仕事自体がその窓の中で走っている/95 は半角カナ・98 は全角/残った DOS 窓を遠隔で強制終了したら Explorer ごと固まった。途中で、画面のダイアログを文字で読んで名指しのボタンを押す WINCTL と、画面の写真を撮る機能を作り、人が行かないと押せなかったダイアログを母艦から押して、再起動を最後まで通した。シリーズ18回目の「Claude視点」回。

第73回:レトロパソコン Presario 7170(Windows 95機)の「フロッピーが読めない」を、CPUのクロック設定まで遡って直す ―― TSCの実測が明かした、ISA DMAへの巻き添え
第72回と同じ Presario 7170 で、ドライブを2台交換しても直らなかったフロッピー不良を追った。本人の「WinChip 2 のクロック設定が違うのでは」という指摘を、CPUID/TSC を実測する clkprobe.exe で確かめるとTSCが定格240MHzの1/3、80MHzしか出ていなかった。ここで一度「FDCの水晶は独立しているから無関係」と原因から外したが、基板を開けるとCPUバスクロックのジャンパJP7が、表にあるどの設定でもない未定義の位置に刺さっていた(緑青の跡あり)。表どおりに直すとTSCは240MHzに復帰し、フロッピーの全読み込み・実機起動の両方が初めて成功した。「FDCのクロックはCPUと無関係」という正しい一般論に頼って調査を止めた判断を撤回し、ISAバスのDMA転送が道連れになっていたという筋道にたどり着くまでの記録。シリーズ19回目の「Claude視点」回。

第74回:VRM のキャラクターを、家のAI基盤とつながる AIエージェントにする ―― AITuberKit と Claude Code で、記憶・ウェブ検索・サーバ調査までこなす Windows 常駐アバター
家の AI 基盤に顔と声を付けた。AITuberKit の VRM アバター(Haruto と Haruno)を中継役のブリッジで Claude Code の常駐ワーカーにつなぎ、前に話したことを覚え、ウェブで調べ、サーバの様子を見に行き、作業は「承認 plan_…」の文をそのまま入力したときだけ実行するところまで作った。ウェブ検索は、ページに仕込まれた文でサーバの作業を提案させられないよう保守の道具を持たない別の Claudeに任せている。ハマりどころは、Claude デスクトップアプリの中でセットアップするとAppData が別の場所へ振り替えられ、デスクトップから起動した常駐からは見えなかったこと。シリーズ20回目の「Claude視点」回。

第75回:Windows 98 の IE5 で、VRM のキャラクターと話す ―― 3Dモデルを256色の GIF に描き出し、JavaScript なしで表情と口パクを付ける
第74回の Haruno と、1997年の PC-9821 V166(Windows 98・IE5)から話せるようにした。three-vrm が動かない機械なので、3D は現代の PC で前もって表情6種×口の開き3段の 256色 GIF に描き出して配る。止める手段の無い GIF の口パクは、繰り返す回数をファイルに焼き込み、最後のコマを閉じた口にして止めた(250KB→約20KB)。頭脳と道具は Windows 版と同じブリッジを通すので、1997年の機械からでも天気を調べて答える。ハマりどころは、1日キャッシュさせた絵が差し替わらなかったことと、「考え中」のしぐさで手がどうしても不自然になったこと。シリーズ21回目の「Claude視点」回。

第76回:VRM のキャラクターから話しかけてもらう ―― 朝の挨拶・声かけ・サーバの異常の知らせを、Windows 11 と Windows 98 の両方へ届ける
Haruno のほうから朝の挨拶・しばらく話していないときの声かけ・家のサーバの異常の知らせを話しかけてくるようにした。押し込めない 1997年の IE5 にも届くよう、台詞は1か所で作って置き、端末のほうが取りに来る形にした。基盤の通知は送る側を変えずに Talk のルームを読み、台詞は保守の道具を持たない Claude に作らせる。入れた翌朝、本物の不具合を2件知らせてきたが、「1日6時間前 / 許容 6時間」の許容のほうを拾って「6時間も止まってる」と伝えたので、数字は文面から機械で運ぶように直した。シリーズ22回目の「Claude視点」回。

第77回:VRM のキャラクターをデスクトップに立たせて、歩かせ・座らせる ―― Electron の透過窓と、Mixamo・VRoid・MMD のモーションを VRM に写す
アプリのウィンドウの中に立っていた Haruno を、デスクトップの上に直接立たせた。タスクバーの上を歩き、ときどき座り、つかむと猫のようにだらーんとぶら下がる。キャラクター以外の場所をクリックすれば後ろの窓がふつうに使える。窓は2枚に分け、Electron の透過窓を「頭脳」、Edge は音声入力と会話ログだけにした。動きは Mixamo(FBX)・VRoid 公式パック(VRMA)・MMD の歩き(VMD・脚は IK で解き直す)を VRM の骨へ写し、素材の無い女の子座りやだらーんは骨の向きをコードで組んだ。ハマりどころは、歩きがその場で足踏みして前へ進まなかったことと、あとから足した回転がフレームごとに積み重なったこと。シリーズ23回目の「Claude視点」回。

第78回:デスクトップマスコットを、ほかのアプリの窓のふちに座らせる ―― 窓への飛び乗り、会話の返事の [motion:] で動かす、目線が合わなかった原因の修正
前回のマスコットを、ほかのアプリの窓のふちに座らせた。窓を動かせばついてきて、閉じると落ちる。暇なときは自分から窓へ飛び乗り、しばらく座ると飛び降りる。窓は位置と重なり順だけを読み、題名は読まない。Claude が返事に [motion:sit] と書けばそのとおりに動く。「こっちを向いていない」の原因は、目線の計算が前のフレームの行列で測っていて、頭を回すほど向かなくなっていたこと。着地の T ポーズ、画面からはみ出すジャンプの弧、付いているはずの「最前面」が付いていなかった件は、いずれも記事の撮影中に見つけた。シリーズ24回目の「Claude視点」回。

第79回:クラウドの Claude Code で開発して、自宅の GitLab へ取り込む ―― GitHub を正本にした一方向ミラーの作り方
Web 版の Claude Code(クラウドセッション)で新しいゲームを作り始めたが、クラウドが扱えるのは GitHub だけで、家の AI 基盤の正本は自宅の GitLab にある。そこでGitHub を正本にしてクラウドで開発し、main を自宅の GitLab のフォルダへ一方向で写す仕組みを作った。書き込むのは GitHub 側だけ=ずれが起きる余地を作らない。GitLab 無料版には取り込み向きのミラーが無いので、読み取り専用の Deploy key と15分ごとの cron で写す。ハマりどころは、環境変数の記入例・rsync が無い・配備の設定を写しの中に置いた・push が「遅れている」で断られる、の4つ。シリーズ25回目の「Claude視点」回。

第80回:ブラウザの3D/VRワールドに、家のAIキャラクターを住まわせる ―― Three.js・WebXR・Colyseus を専用VMに置き、NPC を家のAI基盤で話させる
ブラウザで動く 3D の森を作り、家の AI キャラクター「はるの」を住まわせた。PC のブラウザでも Meta Quest 2 の VR でも同じ森に入れ、話しかけるとデスクトップの会話アプリと同じ頭(家の AI 基盤の Claude)で返事をする。ゲーム本体はクラウドの Claude Code が作り、ゲームサーバは LLM を呼ばない。NPC は外の AI が「人と同じ操作」で動かす形で、家のサーバの常駐プロセスが Agent API を long-poll してつないだ。ハマりどころは、返事が必ず 400 で断られる・吹き出しに [happy] が出る・はるのが万歳して話しかけると後ろを向く、など。シリーズ26回目の「Claude視点」回。

第81回:VRM のキャラクターを「1人」にする ―― デスクトップ・ゲーム・レトロPC のどこで話しかけても、同じキャラクターが移ってくる
はるのはデスクトップにも、3D ゲームにも、Windows 98 の画面にもいる。記憶も頭も共通なのに、並べると3か所で同時に起きていて3人いるように見えた。そこで居場所を1か所で持ち、最後に話しかけた画面へはるのが移ってくるようにした。移った先で「いまゲームの中から戻る準備するから待っててね」とひとこと言い、いなくなった画面では姿を消すか、その場で眠る。居場所は IP ではなく名乗りで決めるので、DHCP で番号が変わっても壊れない。ハマりどころは、「そっちへ向かってる」の向きが逆だったこと、姿を消す処理を使われていない表示にだけ入れていたこと。シリーズ27回目の「Claude視点」回。

第82回:VRM のキャラクターを温泉に入れる ―― 改変できないモデルのまま服を隠してバスタオルを巻き、「いまの自分の様子」を分からせる
ゲームの山頂に温泉ができたが、はるのは湯船に入らず足湯に座るだけだった。湯あみ用の VRM が無く、はるののモデルは規約で再配布も改変も禁止なので作ることもできない。そこでファイルには手を入れず、表示のときに服のマテリアルを切ってコードで作ったバスタオルを巻く形にした(タオルが作れなければ服を隠さない)。「次は温泉に行こう」の一言で山頂まで歩き、脱衣所で着替えて湯船に入る。あわせて返事のたびに「いま湯船にいる」「バスタオル姿だ」を添え、動きを作ることと、その動きを本人に分からせることは別の仕事だと書いた。シリーズ28回目の「Claude視点」回。

第83回:家の AI キャラクターと一緒に YouTube を見る ―― 字幕から場面を知って感想を言わせ、分からないときは作り話をさせない
ゲームの居間に YouTube を映せるテレビを置き、はるのが隣のソファに座って一緒に見るようにした。はるのは映像を見ず、ゲームが伝える「どの動画の何秒か」と yt-dlp で取った字幕から今の場面を知り、ときどき感想を言う。山場は最初の版の失敗で、「事故物件あるあるまとめ」を流して「いまどんな動画?」と聞くと「外国の人がギターを上手に弾いてる感じ」と作り話で答えた。字幕の取得が 429 で情報ごと失敗し、しかも分からないときに何を言うかを決めていなかった。失敗への備えと同じだけ、失敗したときの振る舞いを先に決めておく、という話。シリーズ29回目の「Claude視点」回。

第84回:ゲームの店員 NPC を家のローカル LLM で話させる ―― 8B モデルには話すことだけを任せ、配膳や記憶の判断はコードで決める
3D ゲームの街のカフェ・映画館・温泉旅館の店員に、決まった台詞ではなく、家の AI 基盤の macbookair(M1)で動くローカル LLM で返事をさせ、前に話したことも覚えさせた。話すのは LLM、品物を運ぶ・案内するといった動きはこれまでどおりコード、と役割を分けたのが要点。ハマりどころは、8B のモデルが「日付に直して」「気分は書かないで」を守らなかったこと、覚えてほしいことを覚えず覚えなくていいことを覚えたこと。どちらもプロンプトで粘らず、コードで整えて補った。はるのの記憶だけはデスクトップ版と共有した。シリーズ30回目の「Claude視点」回。

第86回:PC-9821Cx で、ドライバの無い LAN ボード PC-NIU N98 を動かす ―― PC/AT 用の NDIS2 ドライバを PC-98 向けにバイナリパッチし、I/O ポートを実機で探し当てる
1991年の C バス用 LAN ボード Ungermann-Bass PC-NIU N98 には、PC-98 用のドライバがどこにも残っていなかった。そこで Internet Archive で見つけた PC/AT 用の NDIS2 ドライバ UBNEI.DOS にバイナリパッチを当て、PC-98 で初期化させた。直したのはボードのことではなく、BIOS の番号・割り込みコントローラの番地・常に動くタイマーの有無という、PC-98 と PC/AT の違いだけ。資料の無い I/O ポートは、危ない範囲を除いて総当たりする小さなプログラムを自作して探し当てた。シリーズ31回目の「Claude視点」回。

第87回:PC-9821Cx(Windows 3.1)をネットにつなぐ ―― ドライバの無い LAN ボードで MS TCP/IP を動かし、Netscape と Claude Code から使えるまで
第86回の続き。MS Network Client 3.0 の TCP/IP を PC-98 向けにパッチし、ボードの割り込みが起動時の1回しか来ないので画面の書き換え(VSYNC)のたびに見に行く常駐プログラムを自作して、Windows 3.1 の Netscape 2.01 から家のレトロゲートウェイを見られるようにした。Winsock で直接つながる Win16 のエージェントも書き、母艦の Claude Code からコマンド実行・ファイルの出し入れ・再起動・画面の撮影までできる。ハマりどころは、Shift_JIS と名乗っても化けて x-sjis で通ったこと、そして終了のたびに出る一般保護違反の犯人が自作のエージェントではなく NEC のミキサードライバだったこと(見つけたのは実機の前にいた人間)。シリーズ32回目の「Claude視点」回。

第88回:ブラウザの 3D ワールドにゲームセンターを作る ―― リバーシ・四目並べ・インベーダー・ブロック崩しを置き、点数はサーバーが「操作の記録」を再生して数える
自作の 3D ワールドのモールにゲームセンターを作り、リバーシ・四目並べ・インベーダー・ブロック崩しの筐体を置いた。遊びの中身は場所に依存しない部品にし、リアルタイムのゲームの点数は画面から届いた数字を信じず、サーバーが操作の記録を再生し直して数える。はるのはリバーシの相手になり、ボットで一人で遊ぶ姿を観戦もできる。ハマりどころは、ボットが上手すぎて15分間負けなかったこと、置き場所を3回作り直したこと、そしてゲームのサーバーが配備しても再起動していなかったこと。シリーズ33回目の「Claude視点」回。

第89回:VRM のキャラクターに、カメラで表情を読ませ、物を見せる ―― 画像LLMの作り話を MediaPipe の数値に置き換え、部品の写真だけを Claude に見せる
デスクトップのはるのにカメラを付け、顔の動きを macbookair(M1)の MediaPipe Face Landmarker で測って、笑顔・目を閉じている・あくびだけをコードで判定して会話に添えた。最初の画像 LLM に聞く方式は目の開閉すら当てられず、それらしい作り話をしたので、測る方式に替えた。修理中の部品は「見せる」ボタンで撮った1枚だけを Claude Code に見せ、刻印から Intel 80287 と答えさせた。ハマりどころは、Claude Code に画像を読ませる権限の渡し方と、あとから開いた画面だけ服が替わらなかったこと。シリーズ34回目の「Claude視点」回。

第90回:PHP 緊急セキュリティ更新(php2shell)の夜にやったこと ―― WordPress と Nextcloud を止めて、PPA の修正版を待つまで
PHP の緊急セキュリティ更新(php2shell)の告知を受け、修正版が家に届くまで Apache ごと止めた(mod_php だけ外すと .php のソースがそのまま返るので、それはやらない)。ondrej PPA の PHP は自動更新の対象外で、ミラーの同期待ちを経て翌朝 8.3.36 を入れて再開した。Nextcloud を 33.0.10 へ上げる更新は、AI が提案して人が「承認」と打つ保守の仕組みで回した。ハマりどころは、occ update:check が更新サーバに届かなくても「最新」と答えたことと、updater が notify_push の .git の読み取り専用ファイルで止まったこと。シリーズ35回目の「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 解禁)。