Claude Codeに長期記憶を持たせたら、壁打ちの質が変わった
どうも、AIエンジニアの@noprogllamaです。普段はAIで日常の仕組み化をしたり、投資×テクノロジーの実践知を発信したりしています。
CLAUDE.mdという仕組みがあります。プロジェクトのルートに置いておくと、Claude Codeがセッション開始時に読み込んで、プロジェクトの方針や技術スタックを把握してくれます。開発に必要な情報はこれで十分伝わります。
ですが、CLAUDE.mdには書けないものがあります。
「前にこの方針で議論して、こういう理由で却下したよね」「あの時ハマったの、覚えてる?」——過去の会話の文脈です。
私はClaude Codeを開発だけでなく壁打ち相手としても使っています。戦略の相談、記事の構成、設計判断の議論。こういう用途では、過去に何を話したかが重要です。新しいセッションを開くたびに前提の共有からやり直すのは、率直に言ってしんどい。毎朝出社したら同僚が記憶喪失になっている、あの感じです。
この問題を解決するために、自分で記憶エンジンを作りました。結果として1,942セッション分の会話が蓄積され、壁打ちの質が明らかに変わりました。
以前、一度失敗している
実はこれが2回目の挑戦です。
1回目は、claude-memというOSSプラグインをフォークして使おうとしました。コミット1,493件(2026-03-22現在)のそれなりに成熟したプロジェクトです。セキュリティ上の懸念があったのでサニタイズして、Dropboxでマシン間同期も設計したのですが…3日後に全面無効化しました。
プロジェクトごとに記憶が断絶していたこと、バックグラウンドで毎メッセージ結構なトークンを消費していたことが主な理由でした。
この経緯は以前Zennに書いたので、興味があれば「claude-memをフォークして使ってみたけど3日で辞めた話」を読んでみてください。
今回の設計思想
2回目は一から作りました。名前はsui-memory。
前回の反省を踏まえて、設計方針を3つに絞っています。
- 外部サービスに依存しない。SQLiteの1ファイルに全データを格納する
- バックグラウンドでトークンを消費しない。記憶の保存にLLMを使わない
- セッション終了時に自動で保存される。手動操作ゼロ
依存パッケージも実質2つです(sentence-transformersとsqlite-vec)。削ぎ落として、ようやく動くものになりました。
仕組みの全体像
やっていることは素朴です。セッションが終わると、会話の全文がsui-memoryに送られます。sui-memoryはそれをQ&A形式のチャンクに分割し、日本語特化の埋め込みモデル(Ruri v3)でベクトル化して、SQLiteに保存します。
ユーザー側で必要な設定は、Claude Codeのsettings.jsonにHookを数行足すだけです。セッション終了時に自動で発火するので、普段の使い方は何も変わりません。
CLAUDE.mdとの棲み分け
この仕組みは、CLAUDE.mdと競合するものではありません。役割が違います。
CLAUDE.mdはプロジェクトのルールブックです。技術スタック、コーディング規約、ディレクトリ構成。静的な情報を伝えるのに向いています。セッションが変わっても内容は同じで、それでよいのです。
sui-memoryは過去の会話の蓄積です。この設計にした理由、あの案を却下した経緯、先週試して失敗したこと。動的な情報、つまり議論の文脈を保持します。
CLAUDE.mdが取扱説明書なら、sui-memoryは共有した経験に近い。どちらか一方では足りません。両方あって初めて、文脈を持った壁打ち相手になると思っています。
2つの検索を組み合わせる
記憶は保存するだけでは意味がありません。必要な時に、必要なものを取り出せなければ価値がありません。
sui-memoryでは、2つの検索を組み合わせています。
1つ目はキーワード検索です。SQLiteに組み込みの全文検索(FTS5)を使い、日本語を形態素解析なしに検索します。一般的にはMeCabなどの外部辞書を使いますが、ここではtrigramトークナイザという方式を採用しました。文字列を3文字ずつの断片に分割するだけの素朴な方法ですが、追加の依存パッケージが不要です。「Tailscale」「LaunchAgent」のような固有名詞にはこちらが強い。
2つ目はベクトル検索です。テキストの意味的な近さで検索します。エンベディングモデルにはRuri v3-310mという日本語特化モデルを選びました。310Mパラメータと小型ながらCPUでも十分な速度で動きます。OpenAIのEmbeddings APIを使わない選択をしたのは、コストとプライバシーの両面からです。全ての会話ログを外部に送信するのは避けたかったので。
この2つの結果をRRF(Reciprocal Rank Fusion)という方法で統合します。それぞれの検索結果の順位を使ってスコアを合算する仕組みで、検索方式ごとのスコアの単位が違っても公平に扱えます。キーワード検索だけだと意味的な類似を見逃し、ベクトル検索だけだと固有名詞に弱い。両方を組み合わせることで、お互いの弱点を補います。
さらに、時間減衰を入れています。古い記憶ほどスコアが下がる設計で、半減期は30日です。30日前の記憶はスコアが半分に、60日前は4分の1になります。人間の記憶も直近のことほど鮮明で、古いことは薄れていくので、この仕組みを導入してみました。
実際に使ってみて
現在、1,942セッション分の会話から7,059件のメモリが蓄積されています。検索のレスポンスは100ms前後です。
体感として最も変わったのは、壁打ちの精度です。
たとえば記事のテーマを相談するとき、以前なら毎回ゼロからブレストしていました。今は過去に検討したテーマや、その時の判断理由が文脈として残っているので、同じ議論を繰り返さずに済みます。前回の結論を踏まえて、その先の議論ができる。
これは開発の効率化というよりも、思考の相手としての質の変化です。壁打ちが壁打ちとして機能するようになった、という感覚があります。
もちろん万能ではありません。7,000件のメモリの中から本当に必要な数件を引き当てられるかは、クエリの質とチャンクの粒度に依存します。検索精度の調整は今も続けています。
前回の失敗との違い
前回(claude-mem)と今回(sui-memory)の違いを整理しておきます。
claude-mem sui-memory
─────────────────────────────────────────────────
コード量 大規模 1,759行
言語 TypeScript Python
記憶の保存 LLMで要約・圧縮 生のtranscriptをチャンク化
トークン消費 毎メッセージ数千 ゼロ(LLM不使用)
検索 Chroma(外部DB) SQLite内蔵(FTS5+sqlite-vec)
外部依存 ChromaDB等 sentence-transformers, sqlite-vec
セットアップ 複数プロセス起動 uv syncのみ
根本的な差は、記憶の保存にLLMを使うかどうかです。claude-memはバックグラウンドでLLMを起動して会話を要約・圧縮していました。sui-memoryは生のtranscriptをルールベースでチャンクに分割するだけです。LLMを使わないから、トークンも消費しないし、応答速度にも影響しません。
要約しないぶん情報は多少冗長になりますが、大事な文脈が要約の過程で消えてしまうリスクを避けられます。何を残して何を捨てるかの判断は、保存時ではなく検索時にやればよいのです。
大規模なシステムを捨てて、1,759行で作り直しました。やっていることの本質は同じです。会話を保存して、必要な時に取り出す。余計なものを全部削ぎ落としたら、ようやく納得のいくものになりました。
私のClaude Codeは今、過去の会話を覚えている相手になっています。CLAUDE.mdでルールを伝え、sui-memoryで経験を共有する。この2層構造が、今の私にとっての最適解です。
AIで仕組み化する実践知を発信しています → @noprogllama
Discussion
うちの構成に似ているのでとても興味深く拝見しました。
sui-memoryはSQLiteをメインにチャンク分け、ベクトル化という構成なんですね。
うちのはRAGに後付けでSQLiteを積んだ形です。
やはり記憶を持たせると会話の質が全然違ってきますよね。
ぜひ今後とも近況を教えてください。
構成が似ているとのコメント、嬉しいです。ありがとうございます。
sui-memoryはSQLiteのBM25全文検索をベースに、後からベクトル検索を重ねていった形です。
逆にRAGベースにSQLiteを後付けされたというのは、真逆のアプローチですが、求めているものはまさに同じものですね!
記憶があるとAIが本当に変わりますよね。毎回ゼロから説明し直す必要がなくなるだけで、壁打ちが積み上がる感覚になるというか。日々のAIとの会話がかなり楽になりました。
振り返ると失敗も多いですが、これに関しては正解のアプローチだったと思います。
また書いていきますので、今後ともよろしくお願いします。