更新するときは、設計文書の更新ルールに従うこと。
本書は、Brain・UIを実際に動かす運用者向けの手順をまとめる。設計はランタイム管理 設計、各部の詳細はruntime-management-detail-*.mdにある。
現在の内容は、Brainの起動・停止(Issue #15)、UIプロセスの観測・状態取得(Issue #16)、常駐監視対象の登録・UIへの終了の要求・登録情報のバックアップ(Issue #24)、Brainが使うOllama(Issue #27)、ダッシュボード(Issue #17)である。自動起動・更新・回復の通しの手順は、Issue #21がこの手順を使ってまとめる。
Brainはsystemd --userのサービスkeina-brainとして動かす。nohupでの起動やpkillでの停止は使わない。
Pythonの環境を作る(リポジトリのディレクトリで実行する)
cd ~/develop/AiChatWls
uv sync --frozen
.envにBRAIN_AUTH_TOKENが設定されていることを確かめる。未設定だと起動しない(終了コード2)
サービスを登録する
systemctl --user link ~/develop/AiChatWls/deploy/systemd/keina-brain.service
systemctl --user daemon-reload
旧方式(nohupでuvicornを直接起動したもの)のBrainが動いていれば、一度だけ止める。旧方式のBrainはロックを取らないため、新しいBrainと同時に動いてしまう(ポートが同じなら、新しいBrainはポートを開けずに終了コード1で終わる)
pgrep -af "uvicorn keina_assistant.server:app" # 動いているか確かめる
pkill -f "uvicorn keina_assistant.server:app" # 動いていれば止める
systemctl --user enable(ログイン後の自動起動)は、ここでは行わない(Issue #21)。
| 操作 | コマンド |
|---|---|
| 起動 | systemctl --user start keina-brain |
| 停止 | systemctl --user stop keina-brain |
| 再起動(更新後など) | systemctl --user restart keina-brain |
| 状態の確認 | systemctl --user status keina-brain |
| ログの確認 | journalctl --user -u keina-brain(追いかけるときは-f) |
停止すると、Brainは次の順に進む(詳細設計§6)。
503で断るBRAIN_SHUTDOWN_UI_WAIT_SEC、既定30秒。観測の詳細設計§7)BRAIN_SHUTDOWN_DRAIN_SEC、既定30秒)systemctl --user statusにstatus=0/SUCCESSと出れば、正常に終わっている。
cd ~/develop/AiChatWls
uv run python -m keina_assistant
Ctrl+Cで停止シーケンスが始まる。もう一度Ctrl+Cを押すと、後始末を待たずに直ちに終わる(終了コード130)。サービスとして動いているBrainがあると、二重起動として拒否される(終了コード3)。
uvicorn keina_assistant.server:appでの起動はできない(ロックを通らない起動経路を残さないため)。
systemctl --user status keina-brainで終了コードを、journalctl --user -u keina-brainで理由を確かめる。
| 終了コード | 原因 | 対処 |
|---|---|---|
| 1 | ポートを開けない等 | ログの「別のプログラムが使用中の可能性」を確かめる。ss -ltnp \| grep 8811で使用中のプログラムを調べる。旧方式のBrainなら1.1の4で止める |
| 2 | 設定の誤り | ログに誤りの一覧が出る。.envを直す |
| 3 | 既にBrainが起動している | ログに、ロックファイルと起動中のBrainのPID(参考)が出る。意図した起動なら、先に動いているBrainを止める |
| 4 | ロックファイルを作れない・開けない | ログのパスとエラーを確かめる(権限等) |
Brainが異常終了しても(強制終了・クラッシュ)、ロックはOSが外すので、そのままsystemctl --user start keina-brainで起動できる。ロックファイル(既定~/.local/state/keina-brain/brain.lock)を消す必要はない。
ロックファイルの置き場所(BRAIN_LOCK_FILE)は、WSL側のファイルシステムにする。/mnt/c等のWindows側のドライブは、flockが当てにならないため指定できない(終了コード2)
BRAIN_SHUTDOWN_DRAIN_SECまたはBRAIN_SHUTDOWN_UI_WAIT_SECを変えたときは、unitファイルのTimeoutStopSecを見直す。次の式で求めた値より長くする(詳細設計§9)。変えたらsystemctl --user daemon-reloadを実行する
max(BRAIN_SHUTDOWN_UI_WAIT_SEC, BRAIN_SHUTDOWN_DRAIN_SEC) + 5 + 5 + 10 秒
既定値では max(30, 30) + 20 = 50秒で、TimeoutStopSec=60に収まる
cd ~/develop/AiChatWls
uv run --extra dev pytest
pytestは追加パッケージdevに入っているため、1.1のuv sync --frozenだけでは入らない。--extra devを付けると、そのときに入る。後でuv sync --frozenを実行するとpytestは外れるが、Brainの動作には影響しない。
状態取得(GET /runtime/status)には、閲覧トークンBRAIN_VIEW_TOKEN(または管理トークンBRAIN_ADMIN_TOKEN。2.4)が要る。会話用トークンBRAIN_AUTH_TOKENでは見られない。
トークンを作る(会話用トークンと別の値にする。同じ値だと起動しない(終了コード2))
openssl rand -hex 32
Brainの.envにBRAIN_VIEW_TOKEN=<作った値>を書き、Brainを再起動する(systemctl --user restart keina-brain)
状態を見る人(ダッシュボード等)に、この値を手で渡す。ローテーションも当面は手で行う(値を変えてBrainを再起動し、見る側の値も変える)
未設定でもBrainは起動し、会話は使える。状態取得だけが常に401になり、起動時のログに「BRAIN_VIEW_TOKEN が未設定」と出る。
cd ~/develop/AiChatWls
set -a; . ./.env; set +a
curl -s -H "Authorization: Bearer $BRAIN_VIEW_TOKEN" http://127.0.0.1:8811/runtime/status | python3 -m json.tool
Windowsからは、PowerShellで次のように確かめる(<閲覧トークン>は2.1の値)。
Invoke-RestMethod -Uri http://127.0.0.1:8811/runtime/status -Headers @{Authorization = "Bearer <閲覧トークン>"} | ConvertTo-Json -Depth 6
読み方(詳細は詳細設計§8):
brain.readiness: readyなら会話に使える。not_readyならbrain.readiness_checksで失敗した依存先(Ollama等)が分かるbrain.started_at・brain.commit: Brainの起動時刻と、動いているコードのcommit(更新後の切り替わりの確認に使う)unregistered_processes: 観測しているUI。statusがrunningなら稼働中、unresponsiveなら応答なし(期限切れ。discard_atを過ぎると一覧から消える)。duplicate: trueは重複起動targets: 登録済みの監視対象(2.5)。statusがnot_runningなら未稼働、unknownはBrainの再起動直後でまだ分からないexit_requested・exit_requested_at: 終了を求めたか、いつ求めたか(2.6)。exit_request_deliverable: falseのUIには、求めても伝わらないrecovery_warnings: 登録情報を自動で戻した記録(2.8)。確認済みにするまで残るregistry.backup: staleなら、世代(バックアップ)の作成に失敗している。registry.recovery_pending: trueなら、自動復帰がまだ済んでいない(2.8)conversations: 会話のセッション数と処理中の件数だけ(会話内容は出ない)観測の出来事(観測の開始、期限切れ、終了通知、記録の破棄、停止の通知と応答)はjournalに出る。
journalctl --user -u keina-brain | grep runtime_event
tools/runtime_test_ui.pyは、heartbeatを送るだけの検証用のUIである(会話はしない)。標準ライブラリだけで動く。
# WSL2
cd ~/develop/AiChatWls
uv run python tools/runtime_test_ui.py --env-file .env --policy multi
# Windows(Windows側のPythonで、WSLのファイルを直接実行する)
py \\wsl$\<ディストリビューション名>\home\akira\develop\AiChatWls\tools\runtime_test_ui.py --env-file \\wsl$\<ディストリビューション名>\home\akira\develop\AiChatWls\.env --policy host:1
| 引数 | 用途 |
|---|---|
--policy multi/host:1/limit:N |
起動数ポリシー |
--target-id |
枠(重複起動の検出を試すときに変える) |
--interval・--expiry・--retention |
heartbeat間隔・期限切れ時間・応答なし保持時間の申告値 |
--commit <値>/--commit none |
旧commitのプロセスを模す/commitを申告しない |
--no-exit-notice |
終了時に終了通知を送らない |
--no-stop-ack |
Brainの停止の通知に応答しない |
--protocol-version 1 |
版1のUIを模す(既定は版2。版1は終了を求めても伝わらない) |
--ignore-exit-request |
終了を求められても終了しない(応じないUIを模す) |
--print-host-id |
このホストのhost_idを表示して終わる |
kill -9、Windowsではtaskkill /Fを使うhost_idがテストUIと同じ規則で計算されているかは、同じホストで--print-host-idの値と比べて確かめる常駐監視対象の登録・解除、復帰警告の確認、UIへの終了の要求(以下「管理API」)には、管理トークンBRAIN_ADMIN_TOKENが要る。管理トークンでは状態取得もできる。閲覧トークンで管理APIを呼ぶと403(forbidden)になる。
openssl rand -hex 32)。会話用トークン・閲覧トークンと別の値にする(同じ値だと起動しない(終了コード2)).envにBRAIN_ADMIN_TOKEN=<作った値>を書き、Brainを再起動する未設定でもBrainは起動する。管理APIだけが常に401になり、起動時のログに「BRAIN_ADMIN_TOKEN が未設定」と出る。値は画面に出さない(下の例のように.envから読む)。
設計: 詳細設計: 常駐監視対象の登録と、UIへの終了の要求§3
登録できるのは、今の観測一覧に現れているUIだけである。新しく導入したUIは、一度起動し、状態取得のunregistered_processesに現れてから登録する。
cd ~/develop/AiChatWls
set -a; . ./.env; set +a
H="Authorization: Bearer $BRAIN_ADMIN_TOKEN"
# 登録(host_id・ui_kind・target_idは、unregistered_processesの値をそのまま使う)
curl -s -X POST -H "$H" -H "Content-Type: application/json" \
-d '{"host_id": "<host_id>", "ui_kind": "voice", "target_id": "default"}' \
http://127.0.0.1:8811/runtime/targets
# 解除
curl -s -X DELETE -H "$H" http://127.0.0.1:8811/runtime/targets/voice/<host_id>/default
target_idで起動し直し、古いキーを解除してから新しいキーを登録するdetail.codeで理由が分かる: already_registered(登録済み)、not_observed(観測一覧に無い)、not_registered(解除するキーが未登録)、brain_stopping(Brainの停止処理中)、persist_failed(保存できなかった。journalのregistry_write_failedで原因を見る)curl -s -X POST -H "$H" http://127.0.0.1:8811/runtime/processes/<process_instance_id>/exit-request
deliverableがfalseなら、そのUIには伝わらない(版1等)。自分のホストの手段で止めるrequested_at)は変わらない。求めた後も一覧に残っていれば、そのUIは応じていない登録情報はBRAIN_REGISTRY_DIR(既定~/.local/state/keina-brain/registry)にある。
| ファイル | 内容 |
|---|---|
registrations.json |
本体 |
registrations.generations/ |
世代(登録を変えるたびに作る。30日より古いものは消すが、新しい10個は残す) |
recovery-warnings.json |
未確認の復帰警告 |
registrations.corrupt-<日時>.json・recovery-warnings.corrupt-<日時>.json |
壊れていたファイルの写し(調査用。要らなくなったら消してよい) |
別の場所への保管: 登録を変えた後に、このディレクトリをまるごと別の場所(Windows側のドライブ、別のPC等)へコピーする
cp -a ~/.local/state/keina-brain/registry /mnt/c/Users/<ユーザー名>/keina-registry-backup-$(date +%Y%m%d)
別保管から戻す/特定の世代へ戻す: Brainを止め、戻したい内容(別保管のregistrations.json、または世代のファイル)をregistrations.jsonの名前でコピーし、Brainを起動する。Brainが止まっている間に置き換えること(動いている間に置き換えても、次の登録の変更で上書きされる)
recovery_warningsに警告がある: Brainの起動時に、登録情報が壊れていた(registry_corrupted)か無かった(registry_missing)ため、世代から自動で戻した。restored_generation_at(いつの状態へ戻したか)を見て、targetsに足りない登録があれば観測一覧から登録し直し、確認済みにする
curl -s -X POST -H "$H" http://127.0.0.1:8811/runtime/recovery-warnings/<id>/ack
warnings_unreadableは、警告ファイル自体が壊れていたことを示す(未確認の警告が失われた可能性がある)。evacuated_fileが「退避未完了」(null)のうちは、まだ復帰が済んでいない
registry.backupがstale: 世代を作れていない(最新の世代が本体より古い)。journalのregistry_generation_failedで原因(容量不足・権限等)を確かめて取り除く。次に登録を変えたとき、またはBrainの起動時に作り直し、okに戻る
registry.recovery_pendingがtrue: 自動復帰の途中で書き込みに失敗している。原因を取り除くと、次の登録・解除・確認、またはBrainの起動時に続きを行う。この間の登録・解除・確認は、続きを行えなければpersist_failedで断られる
経緯と方式: Issue #27
OllamaはWSL側で、systemd --userのサービスollamaとして動かす(2026-09-26にWindows版から移した)。Windows版Ollamaは、戻し先として入れたままにしてあるが、自動起動は外してある(Startup\disabled\Ollama.lnk)。
| 項目 | 場所・値 |
|---|---|
| 本体 | ~/.local/opt/ollama/<版>。~/.local/opt/ollama/currentが使う版へのリンク |
| モデル | ~/.local/share/ollama/models |
| 待ち受け | 127.0.0.1:11434(BrainのOLLAMA_URLの既定値) |
| unitファイル | deploy/systemd/ollama.service |
| Brainが使うモデル | Brainの.envのOLLAMA_MODEL(未指定ならqwen3:8b)。2026-09-26からqwen3:14b(Issue #27)。変えたらBrainを再起動する |
systemctl --user link ~/develop/AiChatWls/deploy/systemd/ollama.service
systemctl --user daemon-reload
systemctl --user enable(ログイン後の自動起動)は、ここでは行わない(Issue #21で、起動の順序 SearXNG→Ollama→Brain と一緒に扱う)。
| 操作 | コマンド |
|---|---|
| 起動 | systemctl --user start ollama(Brainより先に起動する) |
| 停止 | systemctl --user stop ollama |
| 状態・ログ | systemctl --user status ollama、journalctl --user -u ollama |
| 読み込み中のモデル | ~/.local/opt/ollama/current/bin/ollama ps(PROCESSORが100% GPUなら、GPUで動いている) |
qwen3:14bで約73秒だった)。2回目以降の読み込みは5〜6秒ほどであるbrain.readinessがnot_readyになり、readiness_checksにOllamaの失敗が出るhttps://github.com/ollama/ollama/releases/download/v<版>/ollama-linux-amd64.tar.zst)とsha256sum.txtを取得し、チェックサムを照合する~/.local/opt/ollama/<版>に展開する(tar --zstd -xf ... -C ~/.local/opt/ollama/<版>)ln -sfn <版> ~/.local/opt/ollama/currentで切り替え、systemctl --user restart ollamamirroredモードでは、Windows版とWSL版が同じポート11434を同時に使えない。さらに、Brainが古い方のOllamaへの接続を持ち続けていると、Windows側にその接続の残り(FIN_WAIT_2、TIME_WAIT)がある間、WSL版はbind: address already in useで起動できない(2026-09-26に実際に起きた。Issue #27)。
Brainを止める(systemctl --user stop keina-brain)
今動いている方のOllamaを止める(Windows版はトレイの「Quit」、WSL版はsystemctl --user stop ollama)
Windows側に11434の接続が残っていないことを確かめる。空になるまで待つ(最大2分ほど)
/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -NoProfile -Command "Get-NetTCPConnection -LocalPort 11434 -ErrorAction SilentlyContinue"
使う方のOllamaを起動し、その後にBrainを起動する
設計: 詳細設計: ダッシュボード
ダッシュボードは、Brainとは別のsystemd --userのサービスkeina-dashboardとして動く(ポート8812)。Brainが止まっていても開け、Brainの起動・停止、常駐監視対象の登録・解除、復帰警告の確認、UIへの終了の要求、OllamaのモデルをGPUから下ろす操作ができる。§2.5〜§2.8のcurlの手順は、ダッシュボードが使えないときの手段として残す。
ダッシュボードを常駐させるには、次の2つが両方とも要る(詳細設計§2.3)。Brainの自動起動(Issue #21)も、この前提に立つ。
WSL自体を止めない: Windowsの%USERPROFILE%\.wslconfigの[general]に次を書く。既に[wsl2]等があれば、[general]の節を足す
[general]
instanceIdleTimeout=-1
この設定は、このPCのすべてのWSLに効き、使っていない間もWSLのメモリを確保したままにする。効かせるには、Windows側でwsl --shutdownを実行し、WSLを止め直す。その間、Brain・Ollama・音声UIの会話が止まるので、使っていない時間に行う
ユーザーのサービスを残す: WSLの端末で次を実行する。loginctl show-user $USER -p LingerがLinger=yesになればよい
sudo loginctl enable-linger $USER
.envにBRAIN_VIEW_TOKEN・BRAIN_ADMIN_TOKENがあることを確かめる(§2.1・§2.4)。どちらも未設定なら、ダッシュボードは起動するが、だれもログインできない。会話用・閲覧・管理のトークンが1組でも同じ値なら、ダッシュボードは起動しない(終了コード2)
サービスを登録して起動する
systemctl --user link ~/develop/AiChatWls/deploy/systemd/keina-dashboard.service
systemctl --user daemon-reload
systemctl --user start keina-dashboard
デスクトップのアイコンを作る。Windowsの(管理者でない)PowerShellで実行する。ディストリビューション名がUbuntuでなければ-Distroで、ポートを変えていれば-Portで渡す
powershell -NoProfile -ExecutionPolicy Bypass -File \\wsl.localhost\Ubuntu\home\akira\develop\AiChatWls\tools\windows\install-dashboard-shortcuts.ps1
デスクトップに「ダッシュボード」(起動して開く)と「ダッシュボードを止める」ができる。スクリプトはWSLのファイルを直接実行するので、リポジトリを更新すれば、作り直さなくても新しい内容で動く
systemctl --user enable(ログイン後の自動起動)は、ここでは行わない(Issue #21)。
| 操作 | 手段 |
|---|---|
| 起動して開く | アイコン「ダッシュボード」。既に動いていれば開くだけ。WSLが止まっていればWSLごと起動する |
| 止める | アイコン「ダッシュボードを止める」 |
| 開く(同じPC) | http://127.0.0.1:8812/ |
| 開く(LANの端末) | http://<PCのアドレス>:8812/(§4.4の規則が要る) |
| 状態・ログ | systemctl --user status keina-dashboard、journalctl --user -u keina-dashboard |
systemctl --user stop keina-dashboardで止めるWSLはnetworkingMode=mirroredで動いているため、LANからの受信は、WindowsのHyper-Vのファイアウォールで制御される。管理者のPowerShellで、ポート8812のTCPの受信だけを、プライベートのネットワークに限って許す(実機での確認は未了。詳細設計§19)。
# 作る
New-NetFirewallHyperVRule -Name KeinaDashboard8812 -DisplayName "Keina Dashboard (8812)" `
-Direction Inbound -VMCreatorId '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}' `
-Protocol TCP -LocalPorts 8812 -Profiles Private
# 確かめる
Get-NetFirewallHyperVRule -Name KeinaDashboard8812
# 消す
Remove-NetFirewallHyperVRule -Name KeinaDashboard8812
.envにDASHBOARD_HOST=127.0.0.1を書いて再起動するBrainは、最後の質問からOLLAMA_KEEP_ALIVE(今は24時間)の間、モデルをGPUに載せたままにする。すぐ空けたいときは、管理トークンでログインし、「Ollama」の区画で[GPUから下ろす]を押す。
WSL・Ollama・Brainは止まらない。回答の途中で押しても、その回答は最後まで返り、その後に下りる(それまでは「下ろし中」と出る)
次の質問では、モデルを読み込み直すため、最初の応答が遅くなる
「VRAM」の列は、Ollamaが申告する値である。gemma4:12bでは、実際の使用量(nvidia-smiで約9.6GB増える)より小さく出る(2026-10-05に確認。申告は約1.4GB)
ダッシュボードが使えないときは、次で下ろす
curl -s http://127.0.0.1:11434/api/generate -d '{"model":"<モデル名>","keep_alive":0}'
「起動失敗・異常終了」と出たら、終了コードの意味を見て、journalctl --user -u keina-brainで原因を確かめる(§1.4)。終了コード2は設定の誤り、3は二重起動(前景で動いているBrainが無いか確かめる)、4はロックファイルを開けない。
「サービスの外でBrainが動いています」と出たら、前景で動かしているBrain(§1.3)を、起動した端末で止めてから、ダッシュボードで起動する。
.envの値を変え、Brainとダッシュボードの両方を再起動する(systemctl --user restart keina-brain keina-dashboard)。ダッシュボードの再起動で、全員がログアウトされる。閲覧・管理の権限の人には、新しい値を手で渡す.envのDASHBOARD_PORT(変えたらダッシュボードを再起動する)install-dashboard-shortcuts.ps1 -Port <新しいポート>で作り直す