運用 Runbook¶
対象: CPersona 2.6.x。 このページは運用に関する正式リファレンスです。 バックアップ、劣化検知、recall のチューニング、CJK の指針、コーパス索引の パターンを扱います。ここが依拠している挙動の事実は契約です。 挙動契約 を参照してください。
翻訳について: 正本は英語版です。記述が食い違う場合は英語版が優先です (右上の言語切替から参照できます)。
バックアップとリストア¶
データベースは WAL モードで動作する単一の SQLite ファイル (CPERSONA_DB_PATH)
です。稼働中の WAL データベースを素の cp でコピーすると、チェックポイントを
またいで壊れたコピーができ得ます。 スクリプト化しないでください。
推奨順:
- オンライン物理バックアップ (第一選択 — サーバー稼働中でも安全):
sqlite3 "$CPERSONA_DB_PATH" ".backup 'cpersona-backup.db'"
# または
sqlite3 "$CPERSONA_DB_PATH" "VACUUM INTO 'cpersona-backup.db'"
どちらも並行書き込み下で一貫したスナップショットを作ります。
-
オフラインコピー: サーバーを停止し、
.dbを-walと-shmの 兄弟ファイルごとコピーします。 -
論理バックアップ (低頻度の補完として推奨):
export_memoriesは スキーマバージョンに依存しない JSONL を書き出し、import_memoriesは 冪等です (重複はスキップ)。したがってリストア訓練を安全に反復できます。 月次程度が妥当な頻度です。
.db はインスタンスの全体ではありません。 データベースの外に状態があり、
上のどのバックアップ手段もそれらに触れません:
-
<CPERSONA_DB_PATH>.calibration.json— エージェント別のベクトル閾値・ ゲート状態・スコアリング版、そしてエージェント別のset_recall_precisionの beta。これ抜きで復元しても、測定値のほうは自力で復旧します。既定の
CPERSONA_CALIBRATE_ON_MODEL_CHANGE=trueでは、sidecar が無い起動は フォールバックではなく再較正を行い、それが起きなかった場合はdeep_checkがnever_calibratedを報告します。beta は復旧しません。 beta は測定値ではなくポリシーの入力であり、 述べられた選好を再導出する仕組みは存在しません。precision を調整していた 運用者は、何も告げられないままその調整を失い、再較正は全ゲートを既定の beta で測ります。sidecar をデータベースと並べてコピーするか、復元後に
set_recall_precisionを再適用してください。 -~/.cpersona/operating-context.toml(またはCPERSONA_OPERATING_CONTEXT_PATH) — 全クライアントに配られる運用者の指示。 -CPERSONA_ACL_FILEを設定しているなら、その ACL ファイル。これ抜きで復元 すると、誰が接続できるかが変わります。 - 外部ベクトル索引 —CPERSONA_VECTOR_SEARCH_MODE=remoteかつCPERSONA_STORE_BLOB=falseの構成の場合。この構成では埋め込みがリモート索引 にしか存在しないので、データベースと上記 3 ファイルを全て戻しても、ベクトル腕 がゼロの状態で復元されます。check_healthはno_local_vector_fallbackと して報告します。索引も同じ頻度でバックアップするか、STORE_BLOB=trueのまま にして.dbを自己完結に保ってください。 -<CPERSONA_DB_PATH>.memories.vecindexと.episodes.vecindex(連続ベクトル 索引) は含めません。 データベースから導出される派生物で、リストアするもの ではなく作り直すものです。バックアップに含めても容量の無駄で、古い索引が 戻ってきても次の build で正されます。 連続ベクトル索引 を参照してください。
稼働中のデータベースはクラウド同期フォルダの外に置いてください (Dropbox、Drive など)。同期クライアントは SQLite の WAL ファイルと相性が 悪いためです。同期するのはバックアップの方にしてください。
埋め込みサーバー停止の検知¶
ベクトル検索は最も強い検索層ですが、誰も見張っていなければ、ネットワーク境界で 何も告げずに劣化します。検知面は 3 つあり、呼び出し側のエージェントには 1 番目を見るよう指示してください。
-
recall 応答の
advisory(v2.4.33 以降、主たる検知面)。状態機械が実際の 埋め込み失敗を観測し、劣化した recall にはadvisory = {degraded, severity, reason, evidence, runbook, advisory_scope}が付きます。「ベクトル層が落ちている。FTS + keyword のみで応答中」という 意味です。このフィールドをユーザーに提示し、その
runbookに従うようエージェントに 指示してください (通常は「埋め込みサーバーを起動または向け直してから再度 recall する」)。runbook が短縮されている場合は「もう伝えた」という意味です。 その「誰に」を述べるのがadvisory_scopeで、プロセスが自分専用ならsession、HTTP トランスポートでは状態がサーバー全体のものなのでprocessになります。共有サーバーでは、fault は自分のセッションが見たと仮定せず 完全版 runbook を繰り返します (bug-251)。CPERSONA_DEGRADED_ADVISORY=falseは advisory を黙らせます。埋め込み バックエンド無しでの運用を運用者が受け入れたことを記録する用途で設定して ください。これはサポートされる fallback であって、推奨構成ではありません。 設計: DEGRADED_ADVISORY_DESIGN。 2. store 応答のembedded。 全ての書き込みが、自分の埋め込みが永続化された かどうかを報告します。サーバー停止中に行われた書き込みはembedded: falseで返り、それらの行は修復可能です (次項)。 3.check_health(agent_id, fix=true)は、埋め込みが NULL のまま保存された 行を検出し、サーバー復帰後に再埋め込みします。エンドポイントに到達できない 場合、次元チェックは今も失敗ではなくスキップされますが、エンドポイント 自体は見張られるようになりました。構成済みのバックエンドが応答しない時、embedding_backendが、失敗した呼び出し自身の証跡を添えてwarnを報告 します。 4.not_probedはそのままの意味で読んでください。check_health(fix=false)はネットワーク呼び出しを行わないので、生存を検査できません。沈黙から健全性を 推測させる代わりに、embedding_backendがnot_probedを返し、検査していない ことを明示します。fix=trueの実行でも、probe が埋め込みクライアントの 5 分間キャッシュから 応答された場合には、同じものを返します。次元はエンドポイントに要求が届かない まま返るので、そこからは何も分かりません。2 つのどちらであったかは、finding のreasonに書かれます。recall が既に latch した fault は、そこでも報告されます。バックエンド未構成の場合、このチェックは意図的に沈黙します。それはサポート された構成であり、提起するのは
advisoryの面だからです。
recall のチューニング¶
つまみを、手を伸ばすべき順に:
set_recall_precision(agent_id, precision)— 主たるポリシーつまみで、 既定の融合モードでは事実上唯一のものです。融合品質ゲートの動作点 β を動かし ます。strict=2.0(混入が減り、取りこぼしが増える)、balanced=1.0(既定)、lenient=0.5(取りこぼしが減り、混入が増える)。即時反映で、再起動は不要です。calibrate_threshold(agent_id)— コーパスからゲート位置を導出し直します (ラベル不要)。agent_id付きで (かつ融合ゲート有効で) 呼ぶと、ベクトル 閾値と融合ゲートの両方を較正します。付けずに呼ぶと、ベクトル閾値のみを 較正します。表示される値はベクトル側の閾値ですが、融合モードで実際に結果を 切っているのは融合ゲートです。コーパスが大きく変わった後、一括再構築の後、 埋め込みモデルを変えた後に再実行してください。CPERSONA_AUTOCUT_MIN_RESULTS— autocut は類似度スケールのシグナルに 対して発火します。confidence スコアリング下、あるいは生の cosine だけで 構成された均質なリスト (cascadeが作るのがそれで、confidence の on/off は 問いません) に対してです。rsf/rrfでは意図的に不活性なので (契約 §6)、 既定構成ではこのつまみは何もしません。それを決めているのは融合モードであって、 confidence フラグではありません。CPERSONA_FUSED_GATE_ENABLED=false— 最後の手段。ゲートが無いとフィルタ はプール規模のヒューリスティック (_adaptive_min_score) にフォールバック しますが、それでも弱い一致は弾かれます。開けっ放しになるのではなく、粗い ゲートになるということです。失うのはこのコーパスに対して測定された動作点 です。
precision の選び方。 トレードオフは用途ごとに非対称です。索引的なコーパス
(recall が、無関係な行を捨てられる AI に供給される) では取りこぼしの方が
混入より悪いので、数日 lenient で運用し、混入が実コストになった場合にのみ
balanced に戻してください。混入に敏感な文脈 (狭い窓に recall を注入する)
では balanced / strict を選んでください。
recall に頼らないという選択¶
確率的検索は負けることがあります。文脈に無いこと自体が害になる事実 (現在の
最優先の決定、常時有効な安全ルール) は、記憶ではなく決定的に注入される面
(CLAUDE.md、システムプロンプト、エージェントが常に読む索引ファイル) に置いて
ください。
有用な切り分けは、問われずに発火すべきものは決定的注入へ、問われたときに 見つかればよいものは記憶へです。ここから系が 2 つ出てきます。
- 決定の更新は追記でなく上書きで行う。 決定が変わったら古い行を
update_memoryしてください (自動で再埋め込みされます)。古くなった決定が recall に勝つのを止める最も確実な方法は、それが検索空間に存在しないことです。 lock_memoryは消失からは守りますが、順位で負けることからは守りません (契約 §9)。
Claude Code のフックから recall を促す¶
エージェントは答える前に必ず記憶を引くとは限りません。常時読み込まれるファイルに書いた指示は
一度読まれたきりで、他のすべてと注意を奪い合います。UserPromptSubmit フックは発言のたびに
発火します。
examples/claude-code-hooks/prompt_hook.py
は標準ライブラリだけの最小の例です: その依頼について reconstruct を呼ぶこと (発言を提案クエリ
として添えます)、そして「その記録はありません」はスコアでなく完全一致の検索で確かめることを、
文脈に書き足します
(契約 §12)。
プロジェクトの .claude/hooks/ に置き、.claude/settings.json に登録します:
{
"hooks": {
"UserPromptSubmit": [
{"hooks": [{"type": "command",
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/prompt_hook.py\"",
"timeout": 5}]}
]
}
}
Windows では 2 つの誤りが黙って失敗します。ある実運用の利用者は両方を踏み、フックから 5 日間 recall が一度も働きませんでした:
- UTF-8 の読み書きは自分で行う。 Windows では Python の標準入出力がコンソールのコードページ
(日本語版 Windows では cp932) を使い、Claude Code は UTF-8 で送受信します。
sys.stdinを文字列 として読むフックは日本語の発言で失敗し、何も足しません。この例はバイトで読み書きし、UTF-8 の 変換を自分で行います。 - スクリプトは絶対パスで指定する。 相対パスは、セッションの作業ディレクトリがサブフォルダの時に
見つからなくなります。
$CLAUDE_PROJECT_DIRはプロジェクトのルートに展開されます。
日本語 / CJK コーパス¶
CPERSONA_RECALL_MODE=rsfを設定してください。FTS5 は CJK のトークン化が 苦手ですが、rsf はマージの際に keyword チャネルのスコアの大きさを保持し、 それが補償するシグナルになります。これ以外に CJK 固有の設定はありません。jina-v5-nano— 本プロジェクトが動かしているモデルであり、参照実装の埋め込み サーバーが既定でダウンロードするモデル — の日本語における既知の特性 (長期の 本番運用で確認済み): クエリと記憶の間に固有名詞や識別子のアンカーが 1 つ 以上重なるときは強い一方、共有語彙のない純粋な概念一致には弱いです。 具体的なアンカー語を含めてクエリを書くこと (「キーワードアンカリング」) は、 現行モデルに対する正しい適応であって、後ろめたく思うべき回避策ではありま せん。- モデルスロットは差し替え可能です (
CEmbeddingプロバイダ)。埋め込みモデルを 入れ替える場合は、コーパス全体を再埋め込みしてからcalibrate_thresholdを 実行してください。サーバーは次元が変わったときは自動で再較正しますが、 同一次元のモデル入れ替えでは手動の再較正が必要です。
コーパス索引と同期のパターン¶
CPersona の第一の設計中心は、会話から積み上がる記憶です。正本の Markdown ファイルに対する検索索引として使うことも可能ですが、正しいパターンは 2 つの 事実に規定されます。CPersona が受動的なサーバーであること (ファイル監視は なく、取り込みは常に呼び出し側が駆動する) と、重複排除が upsert ではなく skip であること (契約 §5) です。
パターン A — 索引を使い捨ての射影として作り直す (まずはこちらを推奨)。
- 索引に専用の
agent_idを与えます (例:md-index)。索引チャンクを会話 エージェントの記憶に混ぜないでください。分けておけば、エピソード境界の機構も 絡んできません。 - 元ドキュメントが変わったら
delete_agent_data(agent_id="md-index")を実行し、 全チャンクを再storeします。 - 再構築のたびに
calibrate_thresholdを再実行してください。set_recall_precisionを使っているなら、それも再適用します。delete_agent_dataは、その agent の行と一緒に較正状態も破棄するためです。 再構築が夜間バッチなら、再較正もバッチの一部にします。 -
コスト: 数千チャンクの再埋め込みは CPU で数分〜数十分です (環境依存)。 得られるのは、差分ロジックが不要になることと、再構築が終わった時点で索引が ソースと一致することです。
再構築の最中は一致しません。
delete_agent_dataは再storeが始まる 前にコミットするので、その窓の中で発行された recall は、部分的な索引か、 何も無い索引を見ます。誰も問い合わせていない時間に再構築するか、第 2 のagent_idの下で構築して、完成後に読み手を切り替えてください。
パターン B — 外部の内容ハッシュ台帳による差分更新。
- 各チャンクを、安定した
msg_id(例:path#heading) と、source.idに入れた 元ドキュメントとともにstoreします。こうすると recall のsource_id引数 (前方一致) が、ドキュメント単位のフィルタとしても使えます。 - 呼び出し側で
キー → 内容ハッシュの台帳を保持し、ハッシュが変わった チャンクだけを処理します。 - 変更されたチャンクは必ず
update_memory(またはdelete_memory+store) を通してください。 同じmsg_idで変更後の内容を再保存しても、 何も告げられずスキップされます。変更のないチャンクは何も考えずに再投入して 構いません。完全一致の重複排除が、それを無害だと保証します。
再構築時間が実際に痛くなるほどコーパスが大きくなるまでは、A を選んでください。 壊れる台帳がなく、drift するモードもありません。
スケール¶
成長にアーカイブや間引きの定期処理は不要です。ベクトル層は新しい順のウィンドウ
(CPERSONA_MAX_MEMORIES、既定 10,000。大きなコーパスでは環境変数で上げて
ください。契約 §4)
を走査します。FTS と keyword のチャネルは全履歴に届き、古い行は削除されるのでは
なく、ウィンドウと減衰によって沈んでいきます。
連続ベクトル索引¶
ローカルのベクトル走査は、窓の中の埋め込みを SQLite から 1 行ずつ読み出します。 走査の時間の大半は、計算ではなくこの読み出しです。連続ベクトル索引は、同じ 埋め込みを計算が望む並びで持つデータベース脇のファイルで、走査はそれを一度に 読みます。行も、スコアも、順序も同じで、変わるのはレイテンシだけです。基準機 では 100,000 行で、ベクトル側が 604 ms から 77 ms になりました。
索引はテーブルごとに 1 つです。memories と episodes は別々に走査されるので、 ファイルも別で、build も検査もそれぞれ行います。索引の無い episodes テーブルは、 その 5 倍の行数を持つ索引済み memories テーブルよりクエリあたりの費用が高いので、 片方を build する運用なら両方 build してください。
派生物です。 正本はデータベースだけで、索引はその射影です。キャッシュと 同じ扱いをします。バックアップしない、修復しない、いつ消しても安全。何かおかしく 見えたら、ファイルを消して build し直してください。
build は運用者の操作です。 稼働中のサーバーは、索引を作りも更新もしません。 入口は cron や systemd timer から呼ぶためのコマンドです。
python -m cpersona.vector_index --db "$CPERSONA_DB_PATH" build
python -m cpersona.vector_index --db "$CPERSONA_DB_PATH" --table episodes build
python -m cpersona.vector_index --db "$CPERSONA_DB_PATH" status
python -m cpersona.vector_index --db "$CPERSONA_DB_PATH" --table episodes status
--table でファイルを選びます (既定は memories)。
build はデータベースを読むだけで (書き込みはしません)、索引をアトミックに
置き換えます。サーバーは次のクエリで新しいファイルを拾うので、再起動は不要です。
build できれば exit 0、辞退したら exit 1 で理由を印字します。理由は、埋め込み
済みの行が無いか、2 種類の埋め込み幅が同時に存在するか (モデル変更の途中の
一時的な状態。再埋め込みが終わってから build し直します) のいずれかです。
status は、索引の有無と使用可否、保持行数、build 以降に書かれた行数を報告
します。加えて、毎クエリが索引でなくデータベースから読む行数 (rows_read_exactly)
も報告します。この数には、build 以降に書かれた行と、build が索引に入れられずに
名前を挙げた行が入ります。後者は、created_at が標準の形でない行 (excluded) と、
まだ埋め込みの無い行 (unembedded) です。名前を挙げられた行は、埋め込みを得た
時点から読む費用がかかります。欠けた埋め込みを埋めるのは check_health の
fix=true がすることなので、その修復の後は rows_since_build が 0 のままでも
rows_read_exactly が増えることがあります。索引が無ければ exit 1、ファイルは
あるが使えなければ exit 2 です。
どちらも --json を付けると機械可読な 1 行になります。
memories では、build は粗探索の索引も書きます。 1 つ目の隣に置く 2 つ目の派生
ファイル (<データベース>.memories.coarseindex) です。同じ行を、それぞれ 1 次元 1 ビットに
縮めて持ち、1 ビット粗探索で述べる到達だけが読みます。
読むのは、on にした時の窓の外の席と、時間の手がかりが期間のうち走査窓より後ろを探す時です。
後者は 2.6.4 から、ファイルがあれば既定で使います。扱いは連続配置の索引と同じで、バックアップせず、修復せず、消しても安全で、
削除 (purge) は持っていた行と一緒にこれも消します。両方のファイルを作る場合、build が
exit 0 を返すのは両方を作れた時だけです。status は粗探索の索引を coarse の下で報告しますが、
自分の exit code は変えません。設定が求めない限り、粗探索の索引を読むものは無いからです。
設定が読む時は、健全性チェックが報告します。 CPERSONA_FAR_SEATS_ENABLED=true か
CPERSONA_CUE_COARSE_ENABLED=true で、あるエージェントが走査窓の外に記録を持つ時、粗探索の
索引を使えない想起は、その記録すべての保存ベクトルをデータベースから読みます。答えは同じで、
費用が窓ではなくストアの大きさに比例して増えます。check_health は coarse_index_absent
(窓の外の記録数と 1 回の想起が読むバイト数つき)・coarse_index_unusable・
coarse_index_dimension_drift・coarse_index_rows_missing を警告として、
coarse_index_tail_grown を観察として出し、両方の設定が off の間やストアが窓に収まる間は
何も言いません。
CPERSONA_CUE_COARSE_ENABLED が未設定 (既定) の時、時間の手がかりはファイルにしか尋ねません。
使えるファイルが無ければ、期間のうち窓より後ろは探さず、想起は time_cue.remainder でそう
伝えます。窓の外の席が off の間は、ファイルが無くても費用を払う想起が無いので、同じ 4 つの
状態は警告でなく観察 (info) として報告します。窓の外に 1,000 件以上の記録を持つ範囲で、
ファイルが無いまま想起すると、エージェントが利用者に伝えるための suggestion もセッション
ごとに 1 回付きます。ファイルの作成が自動で行われることはありません。
check_health(checks=["coarse_index"], fix=true) はファイルを作り直します。
ファイルは全エージェントの記録を持つので、この修復は全エージェントへの書き込み権限を求めます。
所見は他のチェックと同じく get_session_findings に届くので、監視側は遅さとして誰かが
気づく前に費用を知ることができます。所見を受けて修復するかは監視側が決めることで、
サーバは決めません。
build と build の間に起きること。 索引は build 時点の最大行 id を覚えて います。それ以降に書かれた行は索引に無く、失われもしません。毎クエリがそれらを データベースから厳密に読み (走査が常にそうしてきたとおり)、索引側の行と合流 させます。
だから build が遅れても、答えは変わりません。変わるのは、各クエリのうち行単位で
読まれる部分が育ち、レイテンシが索引なしの数字へ戻っていくことだけです。再 build
の頻度は性能の設定であって、正しさの設定ではありません。既定は毎晩、成長の速い
コーパスなら毎時が目安で、遅れ具合は status の rows_read_exactly で
いつでも見られます。
索引が使われない時。 索引が置き換える走査はそのまま残っていて、fallback です。ファイルが無い、ファイル自身の整合性検査に落ちる、クエリベクトルの幅が 索引と合わない、索引が持つ行がその後のメンテナンス修復で埋め込みを失った — これらの時、recall は走査に戻ります (遅いが、正しい)。
いずれも運用者が health を読む場所に報告されます。check_health は、索引が
無ければ vector_index_absent (カバーできる行数つき)、ファイルが信頼できなければ
vector_index_unusable を出します (テーブルごとに 1 行、それぞれ table を
名乗ります)。サーバーは、走査に戻したクエリごとに warning をログへ出します。
こうしないと、1 週間使えないままだった索引が「なぜか速くならない」としか 見えません。報告は、その失敗を防ぐためにあります。
索引の設定項目はありません。パスは CPERSONA_DB_PATH から導出され、索引が担う
走査窓は、走査と同じ CPERSONA_MAX_MEMORIES です。
メンテナンスの頻度¶
- 月次:
check_health(agent_id, fix=true)で決定的な整合性チェックと 自動修復、deep_check(agent_id, fix=true)で意味的なパス、加えてexport_memoriesによる論理バックアップ。 - コーパスの大変動の後 (一括インポート、再構築、モデル変更):
calibrate_threshold(agent_id)。 - ベクトル索引を使っているなら timer で:
python -m cpersona.vector_index build(既定は毎晩)。build が遅れた時の代償 (レイテンシであって、正しさではない) は 連続ベクトル索引 を参照してください。 - バージョンアップ: スキーマは自身で前方マイグレーションします。較正は スコアリング関数と埋め込み次元に対して fingerprint されており、どちらかが 変わると初回起動時にサーバーが再較正します (v2.5.2 以降)。