目次
2026 年 3 月、AI エンジニアの noprogllama 氏が、Claude Code に長期記憶を持たせるための自作ツール「sui-memory」を公開した。現在1,942 セッション分の会話から7,059 件のメモリが蓄積され、壁打ちの質が明らかに変わったという。
本稿はこの仕組みの概要、CLAUDE.md との棲み分け、そして技術的な実装詳細を解説する。
CLAUDE.md には書けないもの
CLAUDE.md という仕組みがある。プロジェクトのルートに置いておくと、Claude Code がセッション開始時に読み込んで、プロジェクトの方針や技術スタックを把握してくれる。開発に必要な情報はこれで十分伝わる。
だが、CLAUDE.md には書けないものがある。
- 「前にこの方針で議論して、こういう理由で却下したよね」
- 「あの時ハマったの、覚えてる?」
過去の会話の文脈だ。
noprogllama 氏は Claude Code を開発だけでなく壁打ち相手としても使っている。戦略の相談、記事の構成、設計判断の議論。こういう用途では、過去に何を話したかが重要だ。
新しいセッションを開くたびに前提の共有からやり直すのは、率直に言ってしんどい。毎朝出社したら同僚が記憶喪失になっている、あの感覚だ。
この問題を解決するために、自作の記憶エンジンを作った。結果として 1,942 セッション分の会話が蓄積され、壁打ちの質が明らかに変わった。
以前、一度失敗している
実はこれが 2 回目の挑戦だ。
1 回目は、claude-memという OSS プラグインをフォークして使おうとした。コミット 1,493 件(2026-03-22 現在)のそれなりに成熟したプロジェクトだ。セキュリティ上の懸念があったのでサニタイズして、Dropbox でマシン間同期も設計したのだが…3 日後に全面無効化した。
プロジェクトごとに記憶が断絶していたこと、バックグラウンドで毎メッセージ結構なトークンを消費していたことが主な理由だった。
今回の設計思想:sui-memory
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 を使わない選択をしたのは、コストとプライバシーの両面からだ。全ての会話ログを外部に送信するのは避けたかった。
RRF(Reciprocal Rank Fusion)で統合
この 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 行で作り直した。やっていることの本質は同じだ。会話を保存して、必要な時に取り出す。余計なものを全部削ぎ落としたら、ようやく納得のいくものになった。
結論:2 層構造が最適解
noprogllama 氏の Claude Code は今、過去の会話を覚えている相手になっている。
CLAUDE.md でルールを伝え、sui-memory で経験を共有する。
この 2 層構造が、今の彼にとっての最適解だ。
参考:
引用元・参考リンク
免責事項 — 掲載情報は執筆時点のものです。料金・機能は変更される場合があります。最新情報は各公式サイトをご確認ください。