コンテンツにスキップ

運用 Runbook

対象: CPersona 2.6.x。 このページは運用に関する正式リファレンスです。バックアップ、劣化検知、recall のチューニング、CJK の指針、コーパス索引のパターンを扱います。ここが依拠している挙動の事実は契約です。挙動契約 を参照してください。

翻訳について: 正本は英語版です。記述が食い違う場合は英語版が優先です (右上の言語切替から参照できます)。


バックアップとリストア

データベースは WAL モードで動作する単一の SQLite ファイル (CPERSONA_DB_PATH) です。稼働中の WAL データベースを素の cp でコピーすると、チェックポイントをまたいで壊れたコピーができ得ます。 スクリプト化しないでください。

推奨順:

  1. オンライン物理バックアップ (第一選択 — サーバー稼働中でも安全):
sqlite3 "$CPERSONA_DB_PATH" ".backup 'cpersona-backup.db'"
# または
sqlite3 "$CPERSONA_DB_PATH" "VACUUM INTO 'cpersona-backup.db'"

どちらも並行書き込み下で一貫したスナップショットを作ります。

  1. オフラインコピー: サーバーを停止し、.db を -wal と -shm の兄弟ファイルごとコピーします。

  2. 論理バックアップ (低頻度の補完として推奨): export_memories はスキーマバージョンに依存しない JSONL を書き出し、import_memories は冪等です (すでにある記憶とエピソードはスキップ)。プロファイルはインポートがスキップせずに置き換える唯一のレコードです: ファイルの内容が保存済みのプロファイルを置き換えますが、保存済みのほうが新しい場合は保存済みのものを残し、その行を errors で報告します。したがって古いエクスポートでの訓練がプロファイルを過去に戻すことはありません。月次程度が妥当な頻度です。ファイルは記録に加えて宣言された連想 (entity・別名・言及・関係) も運び、リストアはそれを復元した記録へ向け直します。以前の版が書き出したファイルは連想を含まず、これまでどおり取り込めます。2.4.37 以前が書き出したファイルは project と channel の軸も含まないため、その行はすべてのプロジェクトから読まれるグローバルプールに復元され、インポートはその件数を errors で報告します。インポートは CPERSONA_MAX_IMPORT_BYTES (既定は 100 MiB) より大きいファイルを拒否し、エクスポートにはこの上限がありません: バックアップファイルごとにサイズをこの値と比べ、リストアで必要になる前に引き上げてください (設定)。

.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 ファイル。これ抜きで復元すると、誰が接続できるかが変わります。 - 別名台帳 (alias ledger) — どれかの ACL 行が per_subject を設定している場合。データベースと並ぶ alias_ledger.json、または CPERSONA_ALIAS_LEDGER_FILE が指すファイルです。サーバーは subject が初めてサインインした時にこれを書き込み、どの u- エージェントが誰のものかを記録しているのはこのファイルだけです。これ抜きでデータベースを復元すると、サーバーはそれらのエージェントを名指しして起動を拒否します。台帳をそのパスに戻してから、サーバーを再び起動してください。代わりにエージェントを改名したり per_subject を外したりしないでください: どちらを行っても、サインイン済みの subject はすべて新しい別名を受け取り、保存した記憶に届かなくなります。 - 外部ベクトル索引 — CPERSONA_VECTOR_SEARCH_MODE=remote かつ CPERSONA_STORE_BLOB=false の構成の場合。この構成では埋め込みがリモート索引にしか存在しないので、データベースと上記のファイルを全て戻しても、ベクトル腕がゼロの状態で復元されます。check_health は no_local_vector_fallback として報告します。索引も同じ頻度でバックアップするか、STORE_BLOB=true のままにして .db を自己完結に保ってください。 - <CPERSONA_DB_PATH>.memories.vecindex と .episodes.vecindex (連続ベクトル索引) は含めません。 データベースから導出される派生物で、リストアするものではなく作り直すものです。バックアップに含めても容量の無駄で、古い索引が戻ってきても次の build で正されます。連続ベクトル索引 を参照してください。

稼働中のデータベースはクラウド同期フォルダの外に置いてください (Dropbox、Drive など)。同期クライアントは SQLite の WAL ファイルと相性が悪いためです。同期するのはバックアップの方にしてください。

埋め込みサーバー停止の検知

ベクトル検索は最も強い検索層ですが、誰も見張っていなければ、ネットワーク境界で何も告げずに劣化します。検知面は 3 つあり、呼び出し側のエージェントには 1 番目を見るよう指示してください。

  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 を報告します。その実行自身の probe がバックエンドに届いてベクトルが返った場合、その応答は以前に latch された fault より優先されます。再起動したバックエンドは接続済みとして読まれ、その NULL 行は同じ実行で再埋め込みされます。 4. not_probed はそのままの意味で読んでください。 check_health(fix=false) は生存を判定しません。バックエンドへの接続は起こりえます — 次元チェックは probe 用のテキストを埋め込み、モデルチェックはバックエンドの identity を問い、ノード走査はトークン数を問います — が、そのどの呼び出しも生存の証拠としては読みません。沈黙から健全性を推測させる代わりに、embedding_backend が not_probed を返し、判定していないことを明示します。

    fix=true の実行でも、probe が埋め込みクライアントの 5 分間キャッシュから応答された場合には、同じものを返します。次元はエンドポイントに要求が届かないまま返るので、そこからは何も分かりません。2 つのどちらであったかは、finding の reason に書かれます。recall が既に latch した fault は、そこでも報告されます。

    バックエンド未構成の場合、このチェックは意図的に沈黙します。それはサポートされた構成であり、提起するのは advisory の面だからです。

recall のチューニング

つまみを、手を伸ばすべき順に:

  1. set_recall_precision(agent_id, precision) — 主たるポリシーつまみで、既定の融合モードでは事実上唯一のものです。融合品質ゲートの動作点 β を動かします。strict=2.0 (混入が減り、取りこぼしが増える)、balanced=1.0 (既定)、 lenient=0.5 (取りこぼしが減り、混入が増える)。即時反映で、再起動は不要です。
  2. calibrate_threshold(agent_id) — コーパスからゲート位置を導出し直します (ラベル不要)。agent_id 付きで (かつ融合ゲート有効で) 呼ぶと、ベクトル閾値と融合ゲートの両方を較正します。付けずに呼ぶと、ベクトル閾値のみを較正します。表示される値はベクトル側の閾値ですが、融合モードで実際に結果を切っているのは融合ゲートです。コーパスが大きく変わった後、一括再構築の後、埋め込みモデルを変えた後に再実行してください。
  3. CPERSONA_AUTOCUT_MIN_RESULTS — autocut は類似度スケールのシグナルに対して発火します。confidence スコアリング下、あるいは生の cosine だけで構成された均質なリスト (cascade が作るのがそれで、confidence の on/off は問いません) に対してです。rsf / rrf では意図的に不活性なので (契約 §6)、既定構成ではこのつまみは何もしません。それを決めているのは融合モードであって、 confidence フラグではありません。
  4. 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 — 索引を使い捨ての射影として作り直す (まずはこちらを推奨)。

  1. 索引に専用の agent_id を与えます (例: md-index)。索引チャンクを会話エージェントの記憶に混ぜないでください。分けておけば、エピソード境界の機構も絡んできません。
  2. 元ドキュメントが変わったら delete_agent_data(agent_id="md-index") を実行し、全チャンクを再 store します。
  3. 再構築のたびに calibrate_threshold を再実行してください。 set_recall_precision を使っているなら、それも再適用します。 delete_agent_data は、その agent の行と一緒に較正状態も破棄するためです。再構築が夜間バッチなら、再較正もバッチの一部にします。
  4. コスト: 数千チャンクの再埋め込みは CPU で数分〜数十分です (環境依存)。得られるのは、差分ロジックが不要になることと、再構築が終わった時点で索引がソースと一致することです。

    再構築の最中は一致しません。delete_agent_data は再 store が始まる前にコミットするので、その窓の中で発行された recall は、部分的な索引か、何も無い索引を見ます。誰も問い合わせていない時間に再構築するか、第 2 の agent_id の下で構築して、完成後に読み手を切り替えてください。

パターン B — 外部の内容ハッシュ台帳による差分更新。

  1. 各チャンクを、安定した msg_id (例: path#heading) と、source.id に入れた元ドキュメントとともに store します。こうすると recall の source_id 引数が、ドキュメント単位のフィルタとしても使えます。英字か数字で終わる値 (docs/a.md) はその 1 ドキュメントにだけ完全一致し、区切り文字で終わる値 (docs/) はその下のすべてのドキュメントに前方一致します。
  2. 呼び出し側で キー → 内容ハッシュ の台帳を保持し、ハッシュが変わったチャンクだけを処理します。
  3. 変更されたチャンクは必ず update_memory (または delete_memory + store) を通してください。 同じ msg_id で変更後の内容を再保存しても、何も告げられずスキップされます。変更のないチャンクは何も考えずに再投入して構いません。完全一致の重複排除が、それを無害だと保証します。

再構築時間が実際に痛くなるほどコーパスが大きくなるまでは、A を選んでください。壊れる台帳がなく、drift するモードもありません。

スケール

成長にアーカイブや間引きの定期処理は不要です。ベクトル層は新しい順のウィンドウ (CPERSONA_MAX_MEMORIES、既定 10,000) を走査します。FTS と keyword のチャネルは全履歴に届き、古い行は削除されるのではなく、ウィンドウと減衰によって沈んでいきます。

ベクトルの腕をウィンドウより先の行に届かせるには、CPERSONA_VECTOR_REACH をウィンドウより大きく設定するか、窓の外の席 (CPERSONA_FAR_SEATS_ENABLED) を有効にしてください。ウィンドウ自体を広げると、それが与えている新しさの事前分布も失われます: 実測では、新しい答えで NDCG@10 を 20 ポイント失い、古い答えで 5 ポイント得ました (契約 §4)。

連続ベクトル索引

ローカルのベクトル走査は、窓の中の埋め込みを SQLite から 1 行ずつ読み出します。走査の時間の大半は、計算ではなくこの読み出しです。連続ベクトル索引は、同じ埋め込みを計算が望む並びで持つデータベース脇のファイルで、走査はそれを一度に読みます。行も、スコアも、順序も同じで、変わるのはレイテンシだけです。基準機では 100,000 行で、ベクトル側が 604 ms から 77 ms になりました。

索引はテーブルごとに 1 つです。memories と episodes は別々に走査されるので、ファイルも別で、build も検査もそれぞれ行います。索引の無い episodes テーブルは、その 5 倍の行数を持つ索引済み memories テーブルよりクエリあたりの費用が高いので、片方を build する運用なら両方 build してください。

派生物です。 正本はデータベースだけで、索引はその射影です。キャッシュと同じ扱いをします。バックアップしない、修復しない、いつ消しても安全。何かおかしく見えたら、ファイルを消して build し直してください。

変更ログを通して読みます。 ファイルが持つ行は build の後に変わることがあります。 update_memory が本文とベクトルを置き換える、埋め込み欠落の修復がベクトルを埋める、再タグ付けで移る、修復が時刻を書き換える、削除される。トリガーがそうした行の id をすべて記録し、クエリはその行をファイルでなくデータベースから読むので、答えは走査と同じです。ログが動くのは索引ファイルがある間だけです。build がログを入れ、ファイルが 1 つも残っていなければサーバーが起動時に止めます。ログができる前に書かれた索引ファイルは使えないものとして拒否されるので、アップグレード後に build し直してください。

delete_agent_data は持っていた行と一緒にこのファイルも消しますが、それは呼び出し元の権限がすべての agent に及ぶ時だけです (ACL が無ければ常に)。ファイルはすべての agent の行を持つので、1 つの agent への権限はそこまで届きません。1 つの agent への権限での削除の後、ファイルは次の 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 し直します)、まだどのサーバーも移行していないデータベースか (サーバーを 1 回起動します) のいずれかです。

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_log_refused を警告として、 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) は、窓の外に記録が 1 件でもあればファイルを作り直します。ファイルは全エージェントの記録を持つので、この修復は全エージェントへの書き込み権限を求めます。所見は他のチェックと同じく get_session_findings に届くので、監視側は遅さとして誰かが気づく前に費用を知ることができます。所見を受けて修復するかは監視側が決めることで、サーバは決めません。

build と build の間に起きること。 索引は build 時点の最大行 id を覚えています。それ以降に書かれた行は索引に無く、失われもしません。毎クエリがそれらをデータベースから厳密に読み (走査が常にそうしてきたとおり)、索引側の行と合流させます。索引が持つ行のうち build 以降に変わった行も、変更ログを通して同じように読みます。

だから build が遅れても、答えは変わりません。変わるのは、各クエリのうち行単位で読まれる部分が育ち、レイテンシが索引なしの数字へ戻っていくことだけです。再 build の頻度は性能の設定であって、正しさの設定ではありません。既定は毎晩、成長の速いコーパスなら毎時が目安で、遅れ具合は status の rows_read_exactly でいつでも見られます。

索引が使われない時。 索引が置き換える走査はそのまま残っていて、fallback です。ファイルが無い、ファイル自身の整合性検査に落ちる、クエリベクトルの幅が索引と合わない、変更ログがファイルを保証できない (build 以降にログが止まった・再開した、後の build がこのファイルに要る変更を捨てた、スキーマが変わった) — これらの時、recall は走査に戻ります (遅いが、正しい)。

いずれも運用者が health を読む場所に報告されます。check_health は、索引が無ければ vector_index_absent (カバーできる行数つき)、ファイルが信頼できなければ vector_index_unusable、変更ログがファイルを保証できなければ vector_index_log_refused (reason つき) を出します (テーブルごとに 1 行、それぞれ table を名乗ります)。サーバーは、走査に戻したクエリごとに warning をログへ出します。

こうしないと、1 週間使えないままだった索引が「なぜか速くならない」としか見えません。報告は、その失敗を防ぐためにあります。

索引の設定項目はありません。パスは CPERSONA_DB_PATH から導出され、索引が担う走査窓は、走査と同じ CPERSONA_MAX_MEMORIES です。

Block の索引ファイル

CPERSONA_BLOCK_INDEX=false にしない限り、Block の腕は想起のたびに SQLite から最大 25 万行を読む代わりに、 3 つ目の派生ファイル <database>.blocks.blockindex から行を読みます。答えはどちらでも同じで (約束 §3)、違うのは時間だけです。ほかの 2 つのファイルと同じく、バックアップせず、修復せず、消してかまわず、削除 (purge) の時も同じ規則で消されます。

ファイルの大きさは、Block の行ごとに 28 バイトと、モデルの次元ごとに 1 ビットです。768 次元のモデルで 124 バイト、1,024 次元で 156 バイトになります。Linux と macOS ではマップするので、想起が読む部分だけを OS が読み込みます。Windows ではファイル全体をメモリに読みます。使わない場合は CPERSONA_BLOCK_INDEX=false にして再起動します。サーバーは起動時に変更記録を止め、想起は SQLite を読み、ファイルは消してかまいません。

手で作る必要はありません。設定が on の間、サーバーは Block が変わった記録の記録 (変更記録) を付け、ファイルが無い・使えない・その記録から遅れすぎている時に、起動時と Block を書いた後にキューがファイルを作ります。作り直しは同時に 1 つだけ積まれます。作り終わるまで、想起は SQLite を読みます。タスクキューを無効にしている時や、今すぐ作りたい時は次を使います:

python -m cpersona.block_index --db /path/to/cpersona.db build
python -m cpersona.block_index --db /path/to/cpersona.db status

status は、今の想起がファイルを読めるか (current) と、作った後に変更記録がどれだけ進んだか (changes_since_build) を報告します。ファイルが無ければ 1、あっても使えなければ 2 で終了します。ある想起がどちらから読んだかを見るには、その trace を求めます (trace=true)。block_source が file、またはファイルを読まなかった理由つきの sqlite を示します (想起の trace)。

設定が on の間、check_health は block_index_absent、block_index_unusable、block_index_behind を観察 (info) として報告します。どの想起も SQLite から答えを得ているためです。設定が off の時に出るのは block_log_without_index だけで、(たとえばコマンドラインから作ったために) 変更記録が動いたままになり、読む者のいない行を Block の書き込みごとに足している状態です。check_health(checks=["block_index"], fix=true) で止まり、設定を off にしてサーバーを起動しても止まります。

メンテナンスの頻度

  • 月次: 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 以降)。