本文へスキップ
メニュー

プロダクト

ソリューション

連携

開発者

言語

プロダクト

単一バイナリ、markdownファイル、派生インデックス

フックがエージェントの動作を記録します。git 内の Markdown がページを、SQLite が検索インデックスとセッション、引き継ぎ、アカウントなどの固有データを保持します。両方をバックアップしてください。以下では記録から次のブリーフィングまでを説明します。

単一バイナリ、データディレクトリも1つ。

SQLiteは同梱、libgit2はベンダリング、埋め込み処理はピュアRustです。サイドカーも、データベースサーバーも、運用すべきキューもありません。知っていることはすべて1つのフォルダに入っています。

<data_dir>/

wiki/YAMLフロントマター付きのmarkdownページ。gitリポジトリで管理します。信頼できる唯一の情報源です。
db/memory.sqlite検索インデックスと、データベース固有のセッション、観測、引き継ぎ、アカウント、監査記録。WAL モード。
raw/ai-memory runのマネージドセッションから得た、サニタイズ済みで変更不可のJSONL断片。
models/ローカルの埋め込みモデルall-MiniLM-L6-v2。約87 MBで、SHA-256を固定しています。
logs/日次でローテーションされるログ出力。
config.toml起動時に一度だけ読み込みます。すべての値はAI_MEMORY_*で上書きできます。

サーバーはデフォルトで127.0.0.1:49374にバインドします。バックアップはai-memory backupで取るか、wikiをgit pushしたうえでフォルダをrsyncします。

フックから次のブリーフィングまで。

8つのステップのうち7つは、モデルもAPIキーもなしで動きます。LLMのステップは、プロバイダーを設定するまでオフです。

フック

サニタイズ

単一ライター

オブザベーション

オブザベーション

セッションページ

LLM(任意)

Git wiki

ブリーフィング

Git wiki

フック

LLMが必要なのは破線のステージだけです。戻りの矢印は、次のSessionStartで注入されるブリーフィングです。
  1. フックLLM不要

    エージェントのCLIがライフサイクルフックを発火します。送りっぱなしで、持ち時間は200 msです。ネイティブフックはイベントをローカルにスプールし、切り離されたヘルパーが配送します。サーバーは202を返し、飽和時は429を返します。

  2. サニタイズLLM不要

    /hookルーターがシークレットを取り除き、サイズに上限をかけます。信頼できないテキストがストアに入る経路はここだけです。

  3. 単一ライターLLM不要

    サニタイズ済みのイベントは1本のキューに入ります。キューを処理するのは、唯一の書き込み接続を持つ1つのスレッドです。

  4. オブザベーションLLM不要

    イベントは、運用上の監査証跡としてSQLiteに記録されます。これはセッションをサイズ制限つきで投影したもので、完全なトランスクリプトになることはありません。

  5. セッション終了LLM不要

    モデルを使わずルールだけで、オブザベーションをsessions/<id>.mdにまとめ、次のエージェント向けのHandoff行を開きます。すべて1つのトランザクションで行います。

  6. 統合LLMは任意

    プロバイダーを設定している場合、LLMが要約を書き直すか、concepts/、decisions/、gotchas/、procedures/へ展開します。リトライ可能なキューから実行され、フックのレイテンシには影響しません。

  7. コミットとインデックスLLM不要

    ページの書き込みはどれもアトミック(tmp、rename、fsync)で、gitにコミットされ、その行と同じSQLiteトランザクションの中でインデックスされます。

  8. クエリとブリーフィングLLM不要

    memory_queryがインデックスを検索します。次のSessionStartで、フックはそのディレクトリ向けの未受理の引き継ぎと、構造化されたブリーフィングを取得します。

2つの層、正は1つ。

ファイルとインデックスが食い違ったら、ファイルが正です。

ライター

git上のMarkdown

正本

SQLiteインデックス

派生

git上のMarkdown

ファイル監視

再インデックス

SQLiteインデックス

  • ファイルが正

    ページは、wiki///の下にあるYAMLフロントマター付きのmarkdownです。Obsidianで開くことも、grepすることも、リモートにpushすることもできます。

  • ページのインデックスは再構築可能

    Reindex は Markdown からページ、リンク、全文検索を再構築します。データベースにしかないセッション、観測、引き継ぎ、ユーザー、キー、監査記録は復元できません。

  • 書き込みはサーバーが担う

    通常の書き込みはwikiレイヤーを通り、ファイル、git履歴、インデックスがまとめて更新されます。

  • 残りはウォッチャーが拾う

    vimやObsidianでの編集は、ファイルウォッチャーが検知します。見逃したイベントは、30秒ごとの全体diffで拾います。

検索: 5つの系統、1つのランキング。

クエリ

FTS5

エンティティ

グラフ

観測

ベクトル

RRF k=60

権威度

結果

ベクトルのレーンが破線なのは、なくても検索が動くからです。
  • 全文検索

    ページのタイトルと本文を対象にしたSQLite FTS5です。単純なクエリからはストップワードを除外します。

  • エンティティ一致

    フロントマターのentitiesとtagsにある名前の字句インデックスです。ページ頻度の逆数で重み付けします。

  • グラフの隣接ページ

    linksテーブルを1ホップたどります。対象はwikilink、markdownリンク、型付きエッジ、プロジェクト間リンクです。素のSQLで、グラフデータベースは使いません。

  • オブザベーション

    プロジェクトの生のオブザベーションに対する全文検索です。一致した各セッションは、最も良いオブザベーション1件を通じて、自分のセッションページに1票を投じます。そのため、ページに残らなかった細部からでもページが見つかります。3.0からデフォルトで有効です。

  • ベクトル(任意)

    プロセス内のローカルモデルによる埋め込みのコサイン類似度です。2.0以降はデフォルトで有効ですが、必須ではありません。意図的に総当たりで計算しています。

融合のあと

  • k=60のReciprocal Rank Fusionが、各系統を順位ベースで融合します。そのため、どの系統もスコアの較正が要りません。
  • 次に、上限つきの権威度の乗数が、僅差の結果を、メンテナンスされているルール、決定事項、手順、落とし穴のページ寄りに調整します。エピソード的なページや過去のページも検索対象のままで、完全に除外されるものはありません。
  • 任意のLLMリランク: クエリごとに1回、最大30件のタイトルとスニペットに対して呼び出します。失敗した場合はローカルの順位をそのまま使います。ローカルのリランカーはまだありません。
  • コンパイル済みのページが1件もヒットしない場合は、生のオブザベーションを範囲を限って検索し、raw_hitsを返します。
  • 絞り込みはランキングの前に入ります。kind、tag、tier、パスの接頭辞がすべてのストリームを絞るので、希少なページがありふれたページに押し出されません。
  • 任意の信号が2つあり、どちらも初期値は0です。ひとつは「古い」「誤り」のフィードバックが未解決のページの優先度を下げ、もうひとつは同じセッションが似た問い合わせで読んだ、または役に立つと記録したページを押し上げます。ページを書き直すと両方とも消えます。
  • explain=trueを渡すと、ヒットごとに系統別の順位、RRFの寄与、乗数を確認できます。

日本語、韓国語、中国語

  • 単語の区切りがない漢字・仮名・ハングルの連続には、文字単位の照合が必要です。1 文字と 2 文字の連続を扱う補助インデックスを、ページと同じトランザクションで書き込みます。
  • モデルではなくSQLiteのインデックスです。日本語や韓国語での検索でも、API呼び出しも埋め込みも発生しません。
  • ラテン文字のテキストは従来どおりの経路を通り、変わりません。古いバージョンから更新したストアは、通信を受け付ける前に新しいインデックスを埋めます。

時間: as_of

  • ISO形式の日付を渡すと、その時点でwikiに何と書かれていたかを調べられます。
  • 記録するのは取り込み時刻だけです。ai-memoryがある事実をいつ知り、いつ置き換えたかであって、それが現実の世界でいつ正しかったかは記録しません。
  • その日付に検索が返したはずのランキングを再現するものではありません。
ドキュメントの時間的妥当性の項

型付きエッジ

  • フロントマターのrelations:が受け付けるのは、causes、fixes、contradictsという閉じた集合です。タイプミスで新しい種類が生まれることはありません。
  • contradictsはLLMなしでlintの入力になり、誰かが矛盾を解消するまで報告され続けます。
  • 検索ではexplain専用です。通常のリンクとしてグラフに加わりますが、ランキングには影響しません。ベンチマークから重みの根拠が得られなかったためです。memory_read_pageはこのエッジをたどって関連ページを一覧できます。
ドキュメントの型付きエッジの項

受け取るのは1人、書くのも1人。

正しさの大部分は2つのルールが支えています。引き継ぎを受け取れるのは一度だけ。SQLiteに書き込むスレッドは1つだけ。

引き継ぎはプロトコル

  • 引き継ぎは型付きのレコードです。引き継ぎ元と引き継ぎ先のエージェント、プロジェクト、cwd、要約、未解決の質問、触れたファイル、次のステップを持ちます。
  • 受理はアトミックなcompare-and-setです。2番目に問い合わせたエージェントには何も返りません。
  • cwdはパスの境界で照合します。/repoは/repo/apiを含みますが、/repo-otherは含みません。
  • 手動の引き継ぎは自動のものより優先されます。受理すると、古い自動の候補が同じトランザクションの中で失効します。
  • 共有サーバーでは、shared=trueを付けて送らない限り、引き継ぎは所有者のものです。

単一ライターのルールを実測

すべての書き込みは、上限1024の1本のキューを通って1つのOSスレッドに届きます。読み取りには別の読み取り専用プールを使います。書き込みが集中すると書き込む側が減速しますが、書き込みが捨てられることはありません。

  1. ライター142/秒23.9 ms
  2. ライター8295/秒3.4 ms
  3. ライター32698/秒1.43 ms
  4. ライター128700/秒1.43 ms
  • 上限は毎秒約700書き込みで、ライター32以上では横ばいです。ライター1つの場合を律速するのはfsyncで、CPUではありません。
  • 高速なローカルディスクでの計測です。ネットワークボリュームや遅いボリュームでは大幅に下がります。
  • テストはストアを直接駆動し、HTTPの入口を通りません。
  • cargo test -p ai-memory-store --test writer_throughput -- --ignored --nocaptureで再現できます。

古いセッションページにはスコアが付き、コールドなものは追い出し、コンパクション、マージのいずれかの対象になります。記憶はどう古くなるか。

クレートごとに見るコード。

製品を構成する10個のクレート。それぞれが1つの役割と型付きAPIを持ち、循環依存はありません。

クレート責務
ai-memory-coreドメインの型、エラー、ID。IOなし。
ai-memory-storeSQLite、ライターアクター、リーダープール、減衰の計算。
ai-memory-wikimarkdownのアトミックな書き込み、ファイルウォッチャー、git。
ai-memory-mcpMCPのトランスポートとツールルーター。
ai-memory-webブラウザー画面と読み取り専用 JSON API。アカウント操作と承認は認証付きルートを呼び出します。
ai-memory-hooksペイロードのスキーマ、サニタイザー、/hookの受け口。
ai-memory-llmプロバイダー認証の境界、LLMと埋め込み処理のトレイト。
ai-memory-consolidate取り込み、lint、スイープ、自動改善のパイプライン。
ai-memory-workstream読み取り専用のネイティブトランスクリプトアダプターと起動アダプター。
ai-memory-cliai-memoryバイナリと、薄いHTTPサブコマンド。

MCPツールは25個。意図して絞っています

日常のキャプチャはフックが行うので、エージェントがこれらを手動で呼ぶ必要はほとんどありません。

想起 (7)

  • memory_query
  • memory_recent
  • memory_read_page
  • memory_read_session_observations
  • memory_briefing
  • memory_explore
  • memory_status

引き継ぎ (4)

  • memory_handoff_begin
  • memory_handoff_list
  • memory_handoff_accept
  • memory_handoff_cancel

プロジェクト間メッセージ (4)

  • memory_message_send
  • memory_message_list
  • memory_message_pop
  • memory_message_cancel

書き込みとメンテナンス (10)

  • memory_write_page
  • memory_patch_page
  • memory_delete_page
  • memory_restore_page
  • memory_consolidate
  • memory_auto_improve
  • memory_feedback
  • memory_lint
  • memory_forget_sweep
  • memory_install_self_routing

ARCHITECTURE.md(16個の不変条件をすべて掲載)設計上の決定と、採用しなかった選択肢

よくある質問

ai-memoryはデータをどこに保存しますか?

1つのデータディレクトリの中です。wiki/にmarkdownページのgitリポジトリ、db/に派生のSQLiteインデックス、raw/にサニタイズ済みのworkstream断片、models/にローカルの埋め込みモデルがあり、ほかにログがあります。

ai-memoryにLLMは必要ですか?

いいえ。キャプチャ、セッションの要約、引き継ぎ、インデックス作成、検索、ブリーフィングは、プロバイダーを設定しなくても動きます。LLMによる統合、自動改善、リランクはオプトインです。

SQLiteインデックスが失われたり壊れたりしたらどうなりますか?

Reindex は Markdown からページ、リンク、全文検索を再構築します。データベースにしかないセッション、観測、引き継ぎ、ユーザー、キー、監査記録は復元できません。

検索結果はどのようにランク付けされますか?

FTS5の全文検索、エンティティ一致、グラフの隣接ページ、観測、任意のベクトルという5系統の候補を融合します。Reciprocal Rank Fusionのk=60でまとめ、範囲を制限した情報源の権威度による調整を加えます。LLMリランクは任意です。

毎秒何件の書き込みに耐えられますか?

ストアの実測値は、ライター1つで毎秒42書き込み、8つで295、32以上では約700で頭打ちです。数値は高速なローカルディスクで計測したもので、テストはHTTPの入口を通さずストアを直接駆動しています。

書き出されたファイルを読んでみる。

インストールしてセッションを1つ走らせたら、wikiフォルダをエディタで開いてみてください。