32 分で読了 #homelab
このページの目次

ローカル大規模モデルはどのような支援が可能か —— 境界、ユースケース、自動化

モデルを起動して常駐させることはスタート地点に過ぎません。真の問題は、消費向けGPU上の35B小モデルを、いったい何に使うべきかです。本稿の答えは抑制的なものです——プロダクション環境の複雑なロジックは処理できません(それはClaude Code + Opusの役割です)。その位置づけは非常に狭く、⁠日常的で複雑でない論理、許容可能なエラーのある雑務のみを引き受けます。しかし、この狭い位置づけにおいて、クラウドでは代替できない2つの本質的な特徴があります。⁠オフラインでの自立、プライバシーの端末内完結⁠。この境界線を明確にすることで、どのような作業にリソースを投じる価値があるかが見えてきます。

1. まず境界線を引く:ローカルモデルがやらないこと

ローカルモデルをインストールした後、最初の反応は往々にして「ChatGPT / Copilotを置き換える」——そして失望します。純粋な能力において、消費向けGPUで動作する35Bモデルは最先端のクラウドモデルに勝てないからです。特に、多段階推論、長距離のエージェント連携、正確なコード生成といった「プロダクション環境」での作業において顕著です。

一言で位置づけると:

複雑な論理、プロダクション納品、品質が求められる作業 → Claude Code + Opus; ローカルモデルは日常的で複雑でない論理、許容可能なエラーのある雑務のみを引き受ける。

誰が何をやるか:「論理の複雑さ + 納品要件」で分類 Claude Code + Opus 複雑な論理 · プロダクション納品 · 品質重視 · 機能コードの記述 / リファクタリング / デバッグ · アーキテクチャ設計 / 深層分析 · 多段階推論 / 長距離のエージェント連携 失敗の代償が高い作業は、すべてこちら側 ローカル 35B (小モデル) 日常的 · 複雑でない論理 · エラー許容 · 整理 / まとめ / 分類 / 抽出 · 日報の合成 / 会話履歴の回収 · 日常のQ&A / 情報検索 / スケジュール調整 失敗しても問題なく、再実行可能な作業はこちら 「コスト削減 / プライバシー」を口実に複雑な作業をローカルに押し付けないこと——節約した金額では、失敗の穴を埋めきれない。

なぜローカルでプロダクション作業を行ってはならないのか: 小パラメータモデルは、正確性や多段階推論が求められる場面で失敗する可能性があり、プロダクション環境での失敗の代償は非常に高い(誤ったコード、方向性の逸脱したエージェント)。ローカルでのコスト削減やプライバシーの確保は、この代償を相殺するには不十分です。⁠複雑な作業をOpusに任せることは、ローカルモデルの境界線を明確にする第一歩であり、最も重要なルールです。

2. ローカルの存在理由: オフライン + プライバシー

能力面でクラウドに勝てないのに、なぜローカルなのか? 2つのことはクラウドがどれだけ優れていても実現できないからです:

  • オフラインでの自立⁠: ネット接続なし、クラウドへのアクセス不可、クラウドがブロックされている、クラウドの価格上昇やレートリミット発生時——ローカルはそのまま動作します。外部サービスのオンライン依存がありません。
  • プライバシーの端末内完結⁠: 個人データをモデルに入力しても、外部へのアップロードや、第三者のログ・トレーニングデータへの組み込みはありません。

これこそが、「一部の作業はローカルで行うべきだ」という真の理由です。

クラウドフォールバックに関する矛盾

一般的なアプローチとして「ローカル優先、クラウドでフォールバック」があります: ローカルが停止した場合(例えばゲームプレイ時にgamemodeでサービスが停止)、自動的にクラウドに切り替わり、使用が中断されないようにします。一見素晴らしいですが、清醒な判断が必要です——

クラウドフォールバックを設定すると、ローカルはもはやオフラインでもなく、純粋なプライバシーでもなくなります。 一度でもフォールバックが発生すれば、その時のデータはクラウドに送信されます。フォールバックとローカルの2つの本質的な特徴は本質的に矛盾しています。

したがって、すべてのローカルタスクに無条件でフォールバックを設定してはいけません。誠実なアプローチはユースケースに応じて2種類に分類することです:

シナリオの性質ローカルが選ばれる理由フォールバックの設定?
可用性優先ローカルは単なる「コスト削減のデフォルト」であり、オフライン/プライバシーは必須要件ではない✅ 可openclaw日常アシスタント: 情報検索、スケジュール調整。ローカル停止時はクラウドにフォールバックし、アシスタントのダウンを防ぐ
純粋性優先ローカルは「オフライン/プライバシーのために選ばれた」❌ 不可daily-harvest: 個人コードやキーを含むセッションをスキャン。クラウドにフォールバック = これらのデータを外部に送信することになる

この線引きが、本環境の設定で明確に区別されています: openclawのprimary = ローカル / fallbacks = deepseek(可用性優先)、一方llm-jobsのharvestはローカル使用不可時に直接exit 3して再実行を待ち、⁠クラウドには決して送信しない⁠(純粋性優先)。判断基準はシンプルです:⁠もしこのタスクが「クラウドを使うこと」が、ローカルを使用する初衷に反するなら、フォールバックを持つべきではありません。

3. では、具体的にどのような支援が可能か

「日常雑務 + オフライン/プライバシー」という狭い位置づけにおいて、ローカルモデルの3つの属性がそれぞれ異なるユースケースを可能にします。以下で✅は本環境で実際に稼働しているもの、💡は拡張可能なアイデアです。

限界費用ゼロ → 日常雑務の常態化をためらわない

従量課金のクラウドモデルでは、無意識に「計算して使う」ようになりますが、ローカルはコストゼロであるため、低価値だが高頻度の雑務を毎日実行することをためらいません。

  • openclaw日常Q&A: 情報検索、スケジュール調整、ちょっとした質問。何度聞いても痛くありません。この種の「1回あたりの価値は大きくないが、頻度が高い」作業は、ゼロコストで維持できるからこそ可能です。
  • 日報の毎日合成⁠: daily-reportは毎日09:00に、前日の断片的なエントリを重複排除し、人間が読める文章に統合します。さらにシステムインスペクション、継続すべきタスク、金曜日の「今週の視点」を組み合わせて、WeChatに4セクションで配信します。毎日必ず実行され、1回の呼び出しで複数の調整を行うため、クラウドの従量課金では痛手です。
  • 今週の視点⁠: 金曜日の日報に1セクション追加——モデルが過去7日間の完了エントリを読み、精力の配分を総括し、停滞している事項を指摘し、来週への提言を1つ示します。ドキュメントを横断して総合するような作業は、手動では煩雑ですが、モデルならコストゼロで毎週実行できます。
  • 💡 拡張⁠: 保存した記事/RSSに対して自動で1文のサマリーを生成; 月報のローリング集計。

プライバシーの端末内完結 → 個人データを安心して入力できる

「AIで処理したいが、クラウドには預けたくない」というデータがあります。ローカルは、そんなデータを安心して入力できる唯一の場所です。

  • daily-harvest: 当日のすべてのClaude Codeプロジェクトセッション(個人プロジェクト、コード、潜在的なキーを含む)+ openclawの記憶をスキャンし、「実際に何を行ったか」を抽出して日報に反映します。これらのデータはクラウドに送信してはならず⁠、ローカルだからこそこのような入力が可能です。
  • 💡 拡張⁠: 個人の日記/チャット履歴/財務レシートに対するローカルRAG検索; 写真のOCRアーカイブ。

常駐して埋め込み可能 → 自動化に組み込みやすい

モデルはlocalhost:18080で常駐しており、任意のスクリプトやタイマーからいつでも呼び出し可能で、クラウドサービスの起動やネットワーク往復の懸念がありません。

  • llm-jobs: systemd timerが時刻になるとローカルモデルを呼び出し、収集→抽出→配信をローカルで完結させます(次節で詳細解説)。
  • openclawデフォルトモデル⁠: 日常エージェントのprimaryがローカルを指しています。
  • システムインスペクションのノイズ低減⁠: health-snapshotは生信号のみを読み取り(failed units、バックアップ結果、ZFS水位、24時間分のerrレベルログをソース別に集約、SSH認証失敗)、モデルがノイズ低減と優先度付けを行い、日報の「🩺 システム」セクションを生成します。「radicaleがINFOをerrとして記録しているのは長年のノイズ」「SSHは2FA対応でブルートフォース未遂なので報告不要」などの判断は、ルールでは記述できませんが、モデルなら一言で処理できます。すべて正常な場合は「✅ すべて正常」とのみ出力します。
  • 💡 拡張⁠: 記憶の定期的な蒸留とメンテナンス。

4. アイデアを常駐タスクとして実装する: llm-jobsフレームワーク

上記の自動化は、すべて同じパターンで実装されます:⁠データの収集 → ローカルモデルの呼び出し → ファイルへの書き込み/配信⁠。各タスクごとに個別に実装する(各自でcurlを叩いてモデルを呼び出し、各自でWeChatを送信する)のではなく、統一フレームワークとして抽出します。~/.local/lib/llm-jobs/就是这个框架——「定期的にローカルモデルを呼び出す」という類のタスク(日報、harvest、将来の週報/月報/記憶メンテナンス)の統一された実装場所であり、新しいタスクを追加する際は収集スクリプトのみを記述すれば十分です。

なぜこの抽象化レイヤーが必要か

日報は原本孤立したスクリプトであり、唯一のデータソースは~/doc/daily/ですが、このディレクトリはClaude Codeとopenclaw(Miko)という2つのエージェントが「思い付いた時にdoc-daily-logを呼び出す」という尽力ベースで管理しています——誰かが記録を忘れると、完了した作業が日報から丸ごと消えてしまいます(例: 2026-07-03の日報は原本空欄でした)。問題は2つの側面に分解できます:

  • 網羅性⁠: 行った作業が確実にdailyに入らない。→ daily-harvest実際の作業痕跡⁠(会話記録)から回収する。
  • 品質⁠: 純粋な文字列解析では、重複項目や粒度の不一致がそのまま透過される。→ ローカルモデルで合成する。

これら2つは本質的に「定期的にローカルモデルを呼び出す」処理であるため、統一フレームワークとして抽出しました。

アーキテクチャの意思決定: systemd vs openclaw cron

どちらも「定期的な実行」ですが、2つのアプローチには大きな違いがあります:

systemd timer + スクリプトopenclaw cron
タスクモデル決定論的パイプライン(収集→モデル呼び出し→配信)エージェントセッションを起動(ツール/記憶/channelコンテキスト付き)
観測性journald → Loki、Grafanaアラート可能openclaw内部ログ、監視スタックから外れる
依存関係llama-serverのみ依存(これもsystemd)openclaw gatewayのオンライン依存
デバッグ--dry-runsystemctl start、純粋なスクリプトでバージョン管理可能エージェントの文脈で再現する必要があり、非決定論的

ルール: パイプライン系はsystemd、エージェント系のみopenclaw cronを使用する。 日報とharvestは「ファイルの読み取り→モデルの1回呼び出し→ファイルへの書き込み/メッセージ送信」のパイプラインであり、ツールループは必要ないため、systemdを使用します。openclaw cronは「Mikoが本当に頭を使って手を動かす」必要がある定期的なタスク(現時点ではなし)のみに残します。

構造: 3層の疎結合

~/.local/lib/llm-jobs/
  bin/llm-call         統一モデルエントリ: POST localhost:18080; デフォルトはリクエスト側でthinkingをオフ(--thinkでオン)、
                       空のcontentは失敗として扱い、トークン数を増やして再試行、ローカルダウン時はexit 3
  bin/notify-weixin    統一配信: WeChat botへの送信; --spoolでトークン無効(ret=-2)のメッセージをディスクに保存してキューイング
  bin/health-snapshot  読み取り専用でインスペクション信号を収集(failed units/バックアップ結果/ZFS/journal err集約/SSH認証)、
                       daily-reportの「システム」セクションにノイズ低減用に供給
  jobs/daily-harvest   深夜: 前日のセッションをスキャン → map-reduceで完了項目 + 継続すべき項目を抽出 → dailyに追加
  jobs/daily-report    朝: daily + リアルタイムインスペクションを読み取り → 4セクションに合成 → 配信
  jobs/notify-flush    毎時: spoolキューにあるWeChatメッセージを再送信
  README.md            規約 + タスク登録簿
  • bin/llm-call が唯一のモデルエントリ —— 「定期的にローカルモデルを呼び出す」処理は、テスト済みの実装が1箇所のみであり、モデル名/エンドポイント/フォールバックポリシーが一元管理されています。モデルを変更する場合はここだけ変更すれば十分です。
  • bin/notify-weixin の配信は独立 —— 将来bark/メール/マルチチャネルを追加する場合、このレイヤーのみを変更すれば十分です。
  • テンプレートユニット llm-job@.service —— ExecStart=%h/.local/lib/llm-jobs/jobs/%i、タスクを追加する際にserviceファイルの新規作成は不要です。

規約: タスクリングは**bin/内の共有プリミティブのみを呼び出し**⁠、各自でcurlを叩いてモデルを呼び出したりWeChatを送信したりしないでください。

完全なクローズドループ

2つの定期タスクが同一のdailyを共有: 先に補完し、後に報告 04:00 · daily-harvest 【昨日】のCCセッション + openclaw記憶をスキャン 完了項目 + 継続すべき項目を抽出 前日のdailyに追加 09:00 · daily-report 前日のdaily(harvestで補完済み)+ リアルタイムインスペクションを読み取り 4セクションに合成 WeChatにプッシュ 2つのタスクが同一のdailyを共有: harvestが先に補完し、reportが完全なデータを読み取れる。 マシンが夜間にスリープしてトリガーを逃しても問題ありません。Persistent=trueにより、起動後に追跡実行されます。

harvestの定期インスタンスはsystemd drop-inを通じて--date 昨日を渡します(手動実行時はデフォルトで今日); 04:00の時点で昨日は完全に終了しており、補完される内容はちょうど09:00に報告すべきものです。マシンが夜間にスリープしてトリガーを逃しても問題ありません。Persistent=trueにより、起動後に追跡実行されます。

daily-harvest(04:00): 昨日のすべてのClaude Codeセッション~/.claude/projects/*/*.jsonlを検索し、それぞれをUSER:/AI:のプレーンテキストに圧縮(思考やツールのノイズを除去し、制限値まで切り捨て)してモデルにmapし、2つのカテゴリ——「実際に完了した意味のある作業」と「明確に行うと言ったが完了していないこと」——を抽出し、さらにopenclaw自身が蒸留した記憶を追加します。完了候補と当日のdailyに既に存在する手動エントリを一緒にreduceし、本当に完了し、かつ手動で記録されていないもののみを選択してJSONを出力し、hv_*タグで1行ずつ書き戻します。毎回物理的に古いhv_ブロックを削除してから再計算します(⁠冪等性があり、繰り返し実行しても重複しません)。継続すべき候補は完了候補との重複排除後、daily/<日付>.pendingに書き込みます(全チェーンはbest-effortで、失敗してもこのセクションのみが失われます)。全プロセスはローカルモデル + ローカル書き込みです。

daily-report(09:00): 4セクションに合成してWeChatに配信——①「🩺 システム」: health-snapshotでリアルタイムインスペクション信号を収集し、モデルがノイズ低減して簡報にします(モデル使用不可時はfailed unitsのみをリストするフォールバックに切り替え、インスペクションは配信をブロックしません); ②「📝 昨日の完了」: 昨日のdailyの全エントリを読み取り、モデルが重複排除して人間が読める文章に書き換えます。⁠モデル使用不可時は「元のグループ化」にフォールバック⁠(クラウドには送信せず、LLM合成を行わないだけ)、日報は必ず配信されます; ③「🔜 継続すべき」: .pendingファイルを直接読み取り、存在しない場合はセクション自体を表示しません; ④「📊 今週の視点」: 金曜日のみ、モデルが過去7日間のエントリを総括します。日付/天気(wttr.in)を付加し、すべてのセクションは統一された決定論的な整形後処理⁠(エントリ間の空行、箇条書きの全角インデント——モデルの整形の安定性を期待しない)を経て、notify-weixin --spoolで配信します。

フォールバック(純粋性優先の体现): harvest完了後に~/.local/state/llm-jobs/harvest-<日付>.okのマークファイルを作成します。ローカルモデルが使用不可の場合(gamemodeによって停止されているなど)、.deferredを作成してexit 3——⁠偽の空結果を書き込んだり、クラウドに送信したりしません⁠。systemdが失敗としてマークしLokiに記録します。09:00のreport配信前に、前日の.okマークファイルが見つからない場合、daily-harvest --date <昨日>を先に再実行(この時点でモデルは回復済み)してからdailyを読み取ります。これにより「夜間にマシンがスリープ中 / モデルがgamemodeによって停止されている」という全シナリオをカバーします。このフォールバックチェーンの前提は、.okマークファイルが誠実であること⁠——2026-07-10の事故(次節「血みどろの教訓」参照)は、空結果が誤って.okとして書き込まれ、フォールバックチェーン全体をショートサーキットさせたものです。

新しいタスクの追加

  1. ~/.local/lib/llm-jobs/jobs/<name>を作成(実行可能、--dry-run対応、bin/内の共有プリミティブのみを呼び出す)
  2. ~/.config/systemd/user/llm-job@<name>.timerを作成(daily-reportをコピーし、OnCalendarを変更)
  3. systemctl --user daemon-reload && systemctl --user enable --now llm-job@<name>.timer

運用

# 手動実行(送信/書き込みなし)
~/.local/lib/llm-jobs/jobs/daily-report  --dry-run
~/.local/lib/llm-jobs/jobs/daily-harvest --dry-run [--date YYYY-MM-DD]

# 手動で実際のタスクをトリガー
systemctl --user start llm-job@daily-report.service

# ログ確認(journald → Lokiにも流入)
journalctl --user -u llm-job@daily-report.service -n 50
systemctl --user list-timers | grep llm-job

WeChatのnotify-weixinret=-2(コンテキストトークン期限切れ)を返した場合、--spool呼び出し元のメッセージはディスクに保存されてキューイングされ、notify-flushが毎時自動的に再送信します。WeChatでボットにメッセージを送信してトークンを更新すると、配信が加速されます。

5. ローカルモデルを効果的に使用するいくつかの実践的アドバイス

これらは上記の実際のタスクから得られた教訓であり、あらゆる「ローカルモデルを呼び出す」シナリオに移植可能です:

  • 日常タスクではthinkingをオフにし、リクエスト側でオフにする⁠: 整理、まとめ、分類などの作業には思考の連鎖は不要であり、オフにすることでレイテンシを大幅に削減できます。重要なのは、リクエストレベルでchat_template_kwargs: {"enable_thinking": false}を使用すること(サーバー側のデフォルトを上書き可能)、⁠サーバー側のデフォルトに依存しないことです——デフォルトは共有されており、将来的に他のシナリオのために変更される可能性があります(以下の血みどろの教訓を参照)。bin/llm-callは現在デフォルトでリクエスト側で思考をオフにしており、必要に応じて--thinkで明示的にオンにできます。
  • 長いコンテキストを活用し、自分で分割しない⁠: ローカルでは非常に長いウィンドウ(本環境では64K–192K)まで対応可能であり、セッション全体やドキュメント全体を入力すれば十分です。harvestでは、セッションの切り捨て上限を10kから40kに引き上げました——切り捨てるとセッションの中間部分の完了サマリーが欠落し、見逃す原因となります。
  • 構造化出力には検証 + 再試行を設ける⁠: モデルにJSONを出力させる場合は、解析のフォールトトレランスと失敗時の再試行(bin/llm-callには2回の再試行が組み込まれている)が必要です。小モデルは温度設定の下でフォーマットから外れることがあります。
  • 判断系のプロンプトではポジティブな抽出を使用し、「なければ『なし』を出力」という出口を安易に与えない⁠(血みどろの教訓): harvestのmap処理では当初、否定的なフレームワーク(「議論ではない場合は『なし』を出力」)を使用していましたが、温度設定の下でランダムに生産的なセッション全体を「なし」と判断し、1日分の作業を失うことがありました(同じ入力でも1回目は6件出力、2回目は「なし」)。その後、「プログラミングセッションには通常生産性があるため、全文を読み取ってから抽出し、純粋な相談のみで『なし』を出力する」に変更したところ、5/5で安定しました。LLMの判断系プロンプトでは、「何も出力しない」という便利な出口を与えないでください。サンプリングの下でデータが不安定に消失します。
  • 過度なマージを避ける⁠: reduce段階での「類似項目のマージ」が過剰になると、同じ入力でも时而19件、时而4件の漠然とした大見出しに崩壊します。⁠粒度を保持し⁠、マージは下流(日報生成)のステップに委ねます。
  • HTTP 200も偽の成功である可能性がある——空のcontentは失敗として扱う⁠(血みどろの教訓、2026-07-10): codexのフリーズを修正するために、サーバー側のデフォルトqwen3.6-mtp-thinkingenable_thinking:falseからreasoning = onに変更したところ、思考の連鎖がreasoning_contentに分離され、バッチタスクの小max_tokens(512)がすべて思考に消費されました——200を返すがcontent=""finish_reason=length。harvestは空の出力を「そのセッションには作業がない」と判断し、実際に作業があった17のセッションすべてを「なし」と判断し、.ok成功マークファイルまで書き込んでしまいました。朝のフォールバックはショートサーキットされ、日報は「昨日の記録なし」と配信されました。さらに思考に予算を追加しても勝てません⁠: 実測では512→2048にしても、思考がより長くなり、結局すべて消費されました。修正は二管併用: ①llm-callで空のcontentを失敗として扱い(length時はmax_tokensを倍にして即時再試行、上限に達したらexit 3してdeferredに進む)、②バッチタスクはデフォルトでリクエスト側で思考をオフにする(上記)。より深い教訓として、⁠共有推論インスタンスのサーバー側デフォルトは、すべての呼び出し元の暗黙の契約である⁠——ある1つのシナリオのためにデフォルトを変更する場合、他の呼び出し元すべてを確認する必要があります。
  • モデルの整形の安定性を期待しない、表示層では決定論的な後処理を使用する⁠: プロンプトに「各行の間に空行を」といった整形の指示を書いても、小モデルの実行は不安定です。日報のアプローチでは、モデルはコンテンツのみを担当し、スクリプトが統一して再整形します(エントリ間の空行、箇条書きの全角スペースインデント——モデルの出力フォーマットがどれほど変動しても、読者が見る整形は一定です)。
  • 構造化出力には十分なトークンを割り当て、失敗を成功として偽装してはならない⁠(血みどろの教訓): 忙しい日の候補は100件を超える可能性があり(54セッション→163候補)、reduceの出力JSONは非常に長くなり、max_tokens=1500では直接切り捨てられ→ JSONが無効→ 解析失敗となります。初期段階では、解析失敗を静かに「新規追加なし」として扱い、.ok成功マークファイルまで書き込んでいたため、朝のフォールバックもトリガーされず、1日分の作業が静かに消滅しました(2026-07-06)。3つの鉄則: ①出力トークンを十分に割り当て、アップグレードして再試行(6000→9000)、②空の判定ではNone(失敗)と[](真に新規追加なし)を区別する、③失敗にはフォールバックがあり、廃棄してはならない⁠——本環境のharvestでは、reduceで繰り返し失敗した場合、「プロジェクト別にグループ化して元の候補を直接書き出す」にフォールバックし(下流の日報でマージ)、モデルが真に使用不可な場合のみ.deferred+exit 3とする。「失敗が空結果として扱われる」経路は、自動化の中で静かにデータを飲み込みます。
  • エージェント/ツール呼び出しでは--jinjaをオンにする⁠: そうしないと、モデルは「toolcallタグを出力できるが実行しない」——llama-serverのネイティブなツール解析(出力を構造化されたtool_callsとして解析)は、--jinjaがオンの時のみ有効です。純粋な対話には影響ありませんが、エージェントを使用する場合は必須です。

関連ドキュメント

  • local-llm.md — デプロイ: メモリ予算、量子化の選択、MTP、常駐の潮汐スケジューリング、完全なsystemd設定
  • timers-and-crons.md — 全systemd timerの概要(llm-job@ 2つを含む)
  • monitoring.md — journald → Lokiログ集約(llm-jobsの失敗はここでアラート可能)