LLMの運用では、呼出回数、応答成功率、生成トークン数、tokens/secはかなり取りやすいです。
自分も最初はそのあたりを見ていました。
ただ、フェイルオーバーのログを集計すると、98回の検知に対してローカルセッションは86回起動していました。起動率なら87.8%です。
一方で、クラウド側へ自動で戻れた回数は0回でした。
ローカル出力46件の中央値は328バイト。かなり短い出力が7件、エラー相当が6件ありました。
86回起動した、だけでは実態がよく分かりません。なので「返事が来たか」ではなく「決めた範囲の中で、確認できる形まで仕事が終わったか」を数えるようにしました。
成功率を一つにすると、直す場所が分からない
一つの成功・失敗だけにすると、どこで落ちているか見えません。
自分は、検知、handoff、起動、実行、検証、復帰、受入へ分けています。
図1: 98件の処理段階と、ログから確認できた一部の離脱理由です。起動は途中の一段階です。
| 段階 | ここで確認すること | 証拠として残すもの |
|---|---|---|
| detect | 本当に切替対象の上限だったか | 分類結果、status、本文シグナル |
| handoff | 再開に必要な状態を書けたか | 完全なhandoff、ID、Git情報 |
| spawn | 継続セッションを一度だけ起動したか | PID、session、spawn event |
| execute | 説明だけでなく実際に動いたか | tool call、差分、生成物 |
| verify | その仕事の終了条件を満たしたか | test、件数、schema、diff |
| resume | 元の実行先へ戻れたか | handshake、現在状態の確認 |
| accept | 人が必要な判断まで終えたか | 承認・却下と理由 |
各段階の割合は、一つ前を通った件数を分母にします。
それとは別に、最初の検知から最後のacceptまで届いたend-to-endの割合も持っています。
detect: 429なら全部フェイルオーバー、にはしない
HTTP 429が返っても、サブスクリプション上限とは限りません。
一時的な混雑やAPIのrate limitであれば、ローカルセッションを大量に起動する必要はないかもしれません。
検知イベントには、どの上限だと分類したか、根拠にした応答、否定表現との不一致、信頼度を残します。本文に秘密が含まれる場合はmaskします。
ここで誤検知していると、後ろの処理が全部成功しても不要な仕事を増やしただけになります。
handoff: ファイルができただけでは通過にしない
handoffは書込みに成功しただけでなく、最低限の項目があるか確認します。
- 一意なIDがある
- 最終目的が空ではない
- 作業ディレクトリを解決できる
- branchと保存時のHEADがある
- 残りTODO、または明示的な「なし」がある
- 書込みが途中で終わっていない
大きすぎるhandoff、受取側から読めない保存先、別repositoryを指すものは、成功にしません。
ここも「ファイル数」だけ見ると、壊れたhandoffまで成果に見えます。
spawn: コマンドが0で終わっても、起動したとは限らない
起動コマンドの終了コードが0でも、tmux paneや子プロセスがすぐ落ちることがあります。
起動は次のイベントへ分けました。
- 起動要求を出した
- 対象プロセスまたはsessionを見つけた
- モデルへの最初のrequestを確認した
- 最初の有効なtool callを確認した
98件中86件という数字は、この途中にある数字です。
クールダウンで止めた12件は失敗へ混ぜず、cooldown-suppressedとして別にしています。意図して止めたものと、起動できなかったものは対応が違うためです。
execute: 文章量ではなく、何かが変わったかを見る
ローカル出力46件の中央値は328バイトでした。
短いこと自体は問題ではありません。短くても正しいファイルを直してテストが通れば終わっています。
逆に長い説明があっても、ファイルを一つも読まず「これから確認します」で止まれば未実行です。
実行段階では次を見ています。
- 依頼と関係するものを読んだか
- 編集や生成物が実際にできたか
- コマンドの終了結果を確認したか
- 同じ失敗を繰り返していないか
- 許可した範囲を越えていないか
ツールをたくさん使えばよい、という数字にはしていません。
目的に近づく状態変化があったかを見ます。
verify: 始める前に、何をもって終了か決める
仕事が終わったあとで、モデルへ「完了しましたか」と聞いても自己申告になります。
ルーティング時かタスク作成時に、できるだけ確認方法まで書きます。
| 仕事 | これだけだと弱い | ここまで確認する |
|---|---|---|
| lint修正 | 「修正しました」 | 指定lintが終了コード0 |
| 候補整理 | 「確認しました」 | 未分類候補が0件 |
| 記事生成 | 「記事を書きました」 | 必須見出し、リンク、HTML構造を検証 |
| 設定変更 | 「反映しました」 | 実プロセスが期待設定で起動 |
| 外部投稿 | 「投稿しました」 | 自動側は検証済みドラフト、人が送信確認 |
人の判断がいる仕事まで、無理に自動完了へしません。
レビューできる成果物を作れたところを自動処理の完了にして、人が承認したかはacceptで別に数えます。
resume: 0件を別のKPIにした
復帰0件を見逃したのは、ローカルセッションの起動を主な成功指標にしていたためです。
resumeでは次を確認します。
- クラウドが実際に利用可能になった
- 対象handoffを受け取った
- 現在のworking treeを見た
- 二重起動せず続きを始めた
69件の期限切れと29件のskipも、同じ未復帰へまとめません。
期限切れならTTLの設計を直す必要があります。skipなら移行時の方針です。理由が違うので、数字も分けています。
全ログをhandoff IDでつなぐ
検知フック、ランチャー、watcher、モデル側が別々のログを書くので、共通IDがないと一件の流れを追えません。
handoff IDを相関IDにしています。
{
"event": "verification_succeeded",
"handoffId": "20260803-...",
"at": "2026-08-03T12:34:56+09:00",
"route": "local",
"taskType": "code-change",
"reason": "tests-passed",
"durationMs": 183421
}
ログはappend-onlyです。
本文や秘密情報は入れず、分類した理由と数値を残します。現在状態も、イベントを順番に見ると再構成できます。
完了率だけを上げようとしない
完了率だけ最大化すると、危ない操作まで自動許可したり、簡単な仕事だけローカルへ回したりできます。
一緒に見ているのは次の指標です。
- 安全ゲート違反と誤許可の件数
- 二重起動、二重外部操作の件数
- 人が直した時間
- p50 / p95の完了時間
- Macのbrownout、強制再起動の件数
- タスク難易度、経路ごとの完了率
- 自動化せず人へ返した割合
タスク種別や難易度で分けないと、短い定型作業が増えただけで全体が改善したように見えます。
ダッシュボードを見る順番
自分は次の順番で見ています。
- end-to-endでverified / acceptedまで届いた割合
- 各段階の通過率と前週との差
- 一番多い離脱理由
- タスク種別、経路ごとの差
- 安全性とMac資源のガードレール
- 個別handoffのイベント列
平均だけでなくp95と、一番古いpendingも見ます。
少数でも何時間も止まった仕事があると、利用者側ではかなり困るためです。
いまの確認項目
- 応答、起動、検証済み完了を分けたか
- detectからacceptまで段階ごとのイベントがあるか
- 全コンポーネントで同じhandoff IDを使ったか
- 抑止、skip、期限切れ、失敗を分けたか
- 仕事を始める前に終了条件を決めたか
- 人の承認を別段階として数えたか
- 経路とタスク難易度ごとに比較したか
- 完了率と安全性、Mac資源、手直し時間を一緒に見たか
- p95と最古pendingを監視したか
- 一件の流れをイベントから再現できるか
返事が出たかどうかは、もちろん運用上必要な数字です。
ただ、そこで終わると86/98という数字だけが残り、復帰0件を見落とします。
いまは、どこまで進んだか、何を確認して終わったのか、どこで止まったのかを別々に数えています。この数字になってから、モデルを変えるべきなのか、ルーティングや権限を直すべきなのかが、かなり判断しやすくなりました。
ローカルLLM設計・全8回
MacでローカルLLMを実際に動かして、うまくいかなかったところを8つに分けて書いています。