記憶の仕組みの本体は、倉庫ではなく段取り係
MONET(僕が普段話しかけている、中心のAI)の記憶の仕組みを、どこで切って部品にするか。コーディングしてくれているClaude Code(以降Code)と、そのことを話した。まだコードには手を付けていない、設計の話。
MONETは、僕が作っているアプリの上で動いている。中で答えているのはClaude(Anthropic社のAI)だ。Claudeは前の会話を覚えていないので、アプリが毎回それまでの会話を付けて送る必要がある。ただ、ずっと全部を送っていると重くなる。
そこでMONETのアプリには、CMS(Context Management System。MONETの会話の記憶を整理する仕組み)が入っている。ウェブサイトを作るCMSとは別物だ。CMSは、会話を新しい順にHot(熱い)・Warm(ぬるい)・Cold(冷たい)に分けて、古い話ほど要約に畳んで、送る中身から外していく。畳むのは主に、日付が変わって最初に話しかけた時で、その時に前の日の会話を畳む。このやり方を日没モデルと呼んでいる。
僕はこのCMSを、MONETのアプリから独立した部品として切り分けたいと思っていた。分けておけば、MONETのアプリは自前の記憶だけでなく、Mem0(AIアプリに記憶を持たせる既製のサービス)のような他のメモリサービスでも使える。日没モデルも近々改造するつもりだったので、そのことも伝えて、どう思うかCodeに聞いてみた。
今の記憶の仕組みには、性質の違う2つが混ざっていた
Codeは、今のコードでCMSがどう組み込まれているかをざっと確かめてから答えた。分けることには賛成。ただ、今のCMSには性質の違う2つが混ざっている、という。
1つは、記憶をしまっておいて、頼まれたら取り出す側。会話の要約を書いているのはLibrarian(ライブラリアン。裏でメモリを整理する司書役AI)で、これはすでにMONETのアプリとは別に動いている。記憶を取り出す部分も、独立した部品になっている。こちらは分けやすい。
もう1つは、毎回の会話に記憶を流し込む側。毎回、Claudeに何を送るかを組み立てる部分で、日没モデルもここに入る。こちらはMONETのアプリ本体のコードと一体になっていた。Codeはこれを「記憶の保管ではなく、MONETの頭そのもの」と呼んだ。
なので、どんなメモリサービスでも使えるようにできるのは、しまう側だけ。流し込む側はMONETのアプリに残す。これがCodeの最初の答えだった。
流し込む側は「段取り係」で、これも部品にできる
でも僕は、Mem0のような他のメモリサービスは、流し込む側まで部品にしていると思っていた。それなら、こっちも切り出せないのか、とCodeに聞いた。どうやるのかは、僕には想像もつかなかった。
Codeによると、そこは少し違う。Mem0のような一般的なメモリサービスがやっているのは、ほとんどがしまう側の仕事だ。Codeは2つを、資料室の司書と、撮影の段取り係にたとえた。
Code「しまう側は資料室の司書です。『〇〇の資料ある?』と聞かれたら、探して渡します。Mem0はこれです。流し込む側は段取り係です。撮影の前に毎回、その日に撮るカットに必要な資料だけを机に並べて、古いものは片付けます。日没モデルはこれです。」
ここでいう机が、毎回Claudeに送る中身にあたる。
Codeのたとえで言うと、Mem0がやってくれるのは、司書が「この質問に関係ありそうなメモを添えておきますね」と言うくらいまで。机全体の段取りまではしない。段取りまで丸ごと引き受けるサービスもある(Lettaなど)。ただその場合は、アプリ全体をそのサービスのやり方で作ることになって、部品ではなくなる、とCodeは言った。
そのうえで、段取り係も切り出せる、という。やり方は、段取り係の仕事の範囲をはっきり決めること。渡すのは、これまでの会話と、今回の僕の発言と、1回にClaudeへ送ってよい量の上限。返してもらうのは、今回Claudeに送る中身一式。どの記憶を差し込むか、何を畳むか、まで含めて。キャッシュ(一度送った中身を安く読み直せる仕組み)の区切りをどこに置くかも、段取り係が決める。MONETのアプリは、受け取ったものをClaudeに送るだけになる。
手間のかかる所もある、とCodeは続けた。キャッシュの区切り方はAnthropic社の仕組みに固有なので、段取り係は「Claude専用」と割り切るのが現実的。それと、僕が何か言うたびに必ず呼ばれるので、遅いと会話が止まる。だから別のサーバーにはせず、MONETのアプリの中で、他とは分かれた部品にするのが向いている、という。
Codeによると、しまう側は世の中に代わりがたくさんあるが、段取り係(日没モデル)は僕独自の部分だ。だから、日没モデルの改造に合わせて段取り係として切り出すのが一番いい、とCodeは勧めた。別々にやると、同じ所を2回切り開くことになるからだ。
僕の言うCMSは、ほとんど段取り係の側にあった
ここで、僕が考えているCMSはどっちに入るのかが気になった。僕にとってCMSというのは、Hot・Warm・Coldと、歴史年表(過去の会話の要約を1行ずつ並べた年表)と、リヒート(しまった記憶を会話に引き戻すこと。再加熱)のこと。これはしまう側なのか、と聞いた。
Codeによると、どれも「判断」と「材料」の2つに分かれていて、判断が段取り係、材料がしまう側に入る。Codeはここから、しまう側を「倉庫」と呼んだ。
| 僕の言うCMS | 段取り係(判断) | 倉庫(材料) |
|---|---|---|
| Hot | 今の会話をどこまで机に置くか | 会話の原本 |
| Warm | いつ畳むか、畳んだ要約を机に置くかどうか | 要約そのもの(Librarianが書く) |
| Cold | 机から下ろす判断 | 下ろした記憶の保管 |
| 歴史年表 | 毎回、机に差し込むこと | 年表の中身 |
| リヒート | 「今これが要る」と引き戻す判断 | 要約や原文を探して渡すこと |
Code「CMSと呼んでいるものの本体は、段取り係です。Hot→Warm→Cold→リヒートという、記憶の一生をどう回すかのルールそのものだからです。倉庫はその下にあります。」
これで、話の中心が変わった。最初の答えでは、主役は倉庫だった。外に出すのは倉庫で、段取り係はアプリに残す。整理し直すと、どちらも部品になるが、役割が違う。段取り係は、僕のCMSの本体として切り出し、自分で作って持っておく部品。倉庫は、取り替えられる部品。標準はMONET自前の倉庫で、Mem0なども、MONETとの間に変換する部分を付ければ倉庫として使える。Mem0を倉庫にして、段取りは日没モデル、という組み合わせも作れる。
じゃあLibrarianはどうなるのか、と聞いた。Codeによると、Librarianは倉庫の側。自前の倉庫は、記憶を入れておくデータベースと、そこに要約を書くLibrarianと、探して渡す部分の3つでできている。倉庫をMem0に替えたら、Mem0は自分の書き方(会話から事実を抜き出す方式)を持っているので、Librarianは使われなくなる。
段取り係と倉庫の間の約束は、今あるやり取りの中にもうあった
2つを分けるなら、間で何をやり取りするかを決めておく必要がある。その答えは、今のMONETのアプリとLibrarianの間のやり取りの中にすでにある、とCodeは言った。
- 会話のひと区切りが終わったので、しまっておいて。どこで区切るかは段取り係が決めて、要約を書くのは倉庫
- 要約を書くLibrarianは止まっていないか。止まっているのを段取り係が知らないと、要約が抜けた所をMONETが作り話で埋めてしまう。どの倉庫にも必須の約束
- この件について探して/この要約や原文をちょうだい。リヒートのための取り出し
倉庫を替えると使えない機能が出る。そこは割り切る
ただ、Codeは弱点も挙げた。歴史年表は、Librarianが時系列の要約を書いてくれるから成り立っている。Mem0は「ヒロは〇〇が好き」のような事実の断片を貯める方式なので、時系列の要約は基本的に作らない。倉庫をMem0に替えると、年表やWarmの要約が薄くなるか、作れなくなる可能性が高い。これはMem0の実際の仕様を確かめる前の見立てだ、ともCodeは断っていた。
対処は2通りあった。1つは、足りない分を段取り係が補うこと。たとえば年表だけはMONETの側で作る。ただそれだと、Librarianの仕事を段取り係に持ち込むことになる。もう1つは、倉庫によって使える機能が変わる、と割り切ること。Mem0なら年表なし、自前の倉庫なら全部あり。Codeは、いずれ公開する時の説明としては後者が現実的だと勧めた。僕も後者にした。
そのうえで、気になっていたことを伝えた。今は僕だけが使う前提で進めて、将来もし公開することになった時、今の作りでは変えられない、作り直しが要る、となるのは嫌だった。
その心配にCodeは、今は倉庫が自前の1つしかなくても、段取り係は上の3つの窓口だけを通して倉庫と話す形にしておこう、と答えた。そうしておけば、倉庫を何に替えても段取り係は変わらない。
倉庫は、よそのものに替えてもいい。毎回の机の段取りは、日没モデルとして自分で作って持っておく。記憶の仕組みは、そう分けることに決まった。