AI Navigate

AIアシスタントに永久メモリを与えた — 具体的な方法を紹介します

Dev.to / 2026/3/12

📰 ニュースDeveloper Stack & InfrastructureTools & Practical Usage

要点

  • 著者はAIアシスタントがセッション間で記憶を失い、毎回繰り返しコンテキストを設定しなければならないことに不満を感じていた。
  • すべてのデータをコンテキストウィンドウに詰め込むのは非効率であり、単なる保存ではなく効果的なメモリの検索が真の課題であると認識した。
  • そこで、SQLiteをバックエンドに持ち、TypeScriptで実装した永続的で検索可能なメモリシステム「@cartisien/engram」を構築した。
  • このAPIにより、関連データを簡単に保存・呼び出しが可能となり、AIアシスタントがセッションを超えて重要なコンテキストを記憶できるようになるが、モデルには不要な情報で負荷をかけない。
  • このソリューションはどのチャットハンドラやLLMループにも統合でき、AIアシスタントに永久メモリ機能を強化できる。

私のAIアシスタントは毎朝、私が誰か全くわからない状態で目を覚ましていました。

数か月間同じアシスタントを使っていました。アシスタントは私の開発環境やプロジェクト、好みを知っていましたが、それらはすべて一つのセッション内だけの話でした。翌日になると?白紙の状態です。すべての会話はコンテキストの読み込みから始まりました。「これが私たちの作っているものです。ここまで進みました。重要なのはこれです。」と。

それにうんざりしたので、この問題を解決するものを作りました。

コンテキストウィンドウの問題点

多くの人はAIのメモリ問題をすべてをシステムプロンプトに詰め込むことで解決しようとします。プロジェクトのドキュメント、以前の意思決定、好みなど全てを、毎回のセッションで。

これはうまくいく場合もありますが、限界があります。コンテキストウィンドウには制限があるからです。もっと重要なのは、すべてのメモリが同じ価値ではないということです。すべてを知る必要はなく、必要なタイミングで必要な情報だけを知ればいいのです。

これは保存の問題ではなく、検索の問題です。

私が作ったもの

@cartisien/engram — AIアシスタントのための永続的かつ検索可能なメモリ。SQLiteをバックエンドに、TypeScriptをメインに使い、設定不要の設計です。

コアAPIは意図的にシンプルにしています。

import { Engram } from '@cartisien/engram';

const memory = new Engram({ dbPath: './assistant.db' });

// 保存する
await memory.remember(sessionId, 'User is building a federal contracting app in React 19', 'user');

// 関連情報を取り出す
const context = await memory.recall(sessionId, 'what are we building?', 5);

これだけです。どのエージェントループやチャットハンドラー、LLM統合にも簡単に組み込めます。

v0.1: キーワード検索

最初のバージョンはシンプルでした。SQLiteテーブルを作り、セッションとタイムスタンプにインデックスを張り、リコール時にLIKEベースのキーワードマッチングを行いました。

うまく動きましたが、キーワード検索の問題は明白です — 文字通り質問したものしか見つけられません。「何を作っているの?」という質問では「GovScoutという連邦契約アプリに取り組んでいる」という記憶は出てきません。

v0.2: ローカル埋め込みによるセマンティック検索

今週、セマンティック検索を搭載したv0.2をリリースしました。重要な決断は外部APIや管理されたベクトルデータベースを使わないことでした。

RTX 5090とOllamaをローカルで動かしています。nomic-embed-textもすでにダウンロード済み。なので埋め込み呼び出しはローカルのHTTPリクエストです。

const response = await fetch('http://localhost:11434/api/embeddings', {
  method: 'POST',
  body: JSON.stringify({ model: 'nomic-embed-text', prompt: text })
});
const { embedding } = await response.json(); // 768次元の浮動小数点配列

remember()呼び出し時にコンテンツを埋め込みし、そのベクトルをJSONとしてメモリに保存します。recall()ではクエリを埋め込み、保存されたすべてのベクトルとコサイン類似度を計算し、スコア上位k件を返します。

private cosineSimilarity(a: number[], b: number[]): number {
  let dot = 0, magA = 0, magB = 0;
  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
    magA += a[i] * a[i];
    magB += b[i] * b[i];
  }
  return dot / (Math.sqrt(magA) * Math.sqrt(magB));
}

sqlite-vss拡張なし。pgvectorなし。Pineconeなし。JSON配列の数値計算だけです。

Engramが想定する規模(一人のアシスタント、数千の記憶。数百万ではない)には十分高速です。

もしOllamaに接続できなければ自動的にキーワード検索にフォールバックします。クラッシュも設定も不要です。

本当の検証: 本当に機能するのか?

現在、私はEngramを自分のアシスタントのメモリストアとして運用中です。重要な記憶はすべてローカルAPIサーバ(PM2、ポート3470)に投稿されており、既に使っていたマークダウンファイルと並行運用しています。

最初に行ったセマンティッククエリは以下です:

curl "http://localhost:3470/memory/charli?query=what+projects+is+jeff+working+on&limit=5"

結果:

[
  { "content": "Jeff is building GovScout, a federal contracting app...", "similarity": 0.525 },
  { "content": "Engram v0.2 ships semantic search via nomic-embed-text...", "similarity": 0.396 }
]

「Jeffはどんなプロジェクトに取り組んでいるの?」という質問で、キーワードは合致していないにも関わらず、「GovScout」という記憶(類似度0.53)が最も高く、「Engramの話(0.40)」より上位にリコールされました。正しい結果です。

その裏にあるアーキテクチャ

Engramは私が「Cartisien Memory Suite」と呼ぶより大きなフレームワークの一部です:

  • @cartisien/engram — 永続メモリ(本記事のもの)
  • @cartisien/extensa — ベクトルインフラ層
  • @cartisien/cogito — エージェントのアイデンティティと起床・休眠ライフサイクル管理

このフレーミングはデカルトに由来します。Res cogitans(思考する実体)とres extensa(拡張された実体)— 心(マインド)と身体です。Cogitoはエージェントの自己認識。Extensaは思考のためのベクトル層。Engramは経験が蓄積される場所です。

この仮説は、エージェントは単なるコンテキストウィンドウ以上のものを必要とし、自己という基盤を持つべきだ、というものです。

インストール方法

npm install @cartisien/engram

v0.2.0が公開中です。GitHub: github.com/Cartisien/engram

セマンティック検索の本番適用はまだテスト中です。エッジケースの監視やOllamaのタイムアウト処理、スケール時のコサイン計算の妥当性を確認しています。

もしあなたがエージェント開発中でメモリ問題に悩んでいるなら、どんな解決策を取っているのか教えてほしいです。この分野はまだまだ未開拓です。