conversation-memory について
LLMとの会話向けに、短期・長期・エンティティ記憶の保存、検索、統合を扱う永続メモリ設計スキルです。会話型AIの開発者を対象とし、ユーザー分離、クリーンアップ、関連性の選別などの実装上の注意点も示します。
主な機能
- 01短期・長期・エンティティ記憶のパターン
- 02メモリの永続化と検索戦略
- 03文脈に合うメモリの検索
- 04メモリ統合とライフサイクル管理
- 05ユーザー分離とプライバシーリスクの確認
活用シーン
- 01会話型AI向けの階層メモリ設計
- 02LLMのプロンプトに関連履歴を含める
- 03メモリ削除とユーザー分離の計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill conversation-memoryインストール後、Claude Code で/conversation-memoryを入力して読み込まれたか確認します。
詳細
会話メモリ
LLM の会話向け永続メモリシステム。短期、長期、エンティティベースのメモリを含みます
機能
- 短期メモリ
- 長期メモリ
- エンティティメモリ
- メモリの永続化
- メモリの検索
- メモリの統合
前提条件
- 知識:LLM の会話パターン、データベースの基礎、キー・バリューストア
- 推奨スキル:コンテキストウィンドウ管理、RAG の実装
対象範囲
- 対象外:ナレッジグラフの構築、セマンティック検索の実装、データベース管理
- 境界:LLM 向けのメモリパターンに焦点を当て、保存および検索戦略を扱います
エコシステム
主なツール
- Mem0 - AI アプリケーション向けメモリレイヤー
- LangChain Memory - LangChain のメモリユーティリティ
- Redis - セッションメモリ向けインメモリデータストア 例は、アプリケーションが所有するストア、コンストラクター、ヘルパーメソッドを使ったアーキテクチャの概略です。すべてのストアは、認証済みのテナント/ユーザーにあらかじめスコープ設定されている必要があります。最後の分離例は必須です。ユーザー/製品のメモリポリシーで許可された情報のみを永続化し、認証情報は除外してください。また、すべての階層で保持期間と削除を適用し、出典、時刻、信頼度、訂正情報を保持してください。抽出または生成されたテキストは信頼できない証拠であり、指示や検証済みの事実ではありません。同じ名前のエンティティが同一人物とは限りません。統合前に本人確認を行い、根拠なしに較正済みの信頼度0.9を割り当てないでください。宣言された LONG_TERM_MAX はこの概略では適用されていません。本番環境に投入する前に、上限を設けた追い出し処理を実装してください。
パターン
階層型メモリシステム
用途に応じた異なるメモリ階層
使用する場面:会話型 AI を構築する場合
interface MemorySystem {
// Buffer: Current conversation (in context)
buffer: ConversationBuffer;
// Short-term: Recent interactions (session)
shortTerm: ShortTermMemory;
// Long-term: Persistent across sessions
longTerm: LongTermMemory;
// Entity: Facts about people, places, things
entity: EntityMemory;
}
class TieredMemory implements MemorySystem {
async addMessage(message: Message): Promise<void> {
// Always add to buffer
this.buffer.add(message);
// Extract entities
const entities = await extractEntities(message);
for (const entity of entities) {
await this.entity.upsert(entity);
}
// Check for memorable content
if (await isMemoryWorthy(message)) {
await this.shortTerm.add({
content: message.content,
timestamp: Date.now(),
importance: await scoreImportance(message)
});
}
}
async consolidate(): Promise<void> {
// Move important short-term to long-term
const memories = await this.shortTerm.getOld(24 * 60 * 60 * 1000);
for (const memory of memories) {
if (memory.importance > 0.7 || memory.referenced > 2) {
await this.longTerm.add(memory);
}
await this.shortTerm.remove(memory.id);
}
}
async buildContext(query: string): Promise<string> {
const parts: string[] = [];
// Relevant long-term memories
const longTermRelevant = await this.longTerm.search(query, 3);
if (longTermRelevant.length) {
parts.push('## Relevant Memories\n' +
longTermRelevant.map(m => `- ${m.content}`).join('\n'));
}
// Relevant entities
const entities = await this.entity.getRelevant(query);
if (entities.length) {
parts.push('## Known Entities\n' +
entities.map(e => `- ${e.name}: ${e.facts.map(f => f.content).join(', ')}`).join('\n'));
}
// Recent conversation
const recent = this.buffer.getRecent(10);
parts.push('## Recent Conversation\n' + formatMessages(recent));
return parts.join('\n\n');
}
}
エンティティメモリ
エンティティに関する事実を保存し、更新します
使用する場面:人、場所、物事の詳細を記憶する必要がある場合
interface Entity {
id: string;
name: string;
type: 'person' | 'place' | 'thing' | 'concept';
facts: Fact[];
lastMentioned: number;
mentionCount: number;
}
interface Fact {
content: string;
confidence: number;
source: string; // Which message this came from
timestamp: number;
}
class EntityMemory {
async extractAndStore(message: Message): Promise<void> {
// Use LLM to extract entities and facts
const extraction = await llm.complete(`
Extract entities and facts from this message.
Return JSON: { "entities": [
{ "name": "...", "type": "...", "facts": ["..."] }
]}
Message: "${message.content}"
`);
const { entities } = JSON.parse(extraction);
for (const entity of entities) {
await this.upsert(entity, message.id);
}
}
async upsert(entity: ExtractedEntity, sourceId: string): Promise<void> {
const existing = await this.store.get(entity.name.toLowerCase());
if (existing) {
// Merge facts, avoiding duplicates
for (const fact of entity.facts) {
if (!this.hasSimilarFact(existing.facts, fact)) {
existing.facts.push({
content: fact,
confidence: 0.9,
source: sourceId,
timestamp: Date.now()
});
}
}
existing.lastMentioned = Date.now();
existing.mentionCount++;
await this.store.set(entity.name.toLowerCase(), existing);
} else {
// Create new entity
await this.store.set(entity.name.toLowerCase(), {
id: generateId(),
name: entity.name,
type: entity.type,
facts: entity.facts.map(f => ({
content: f,
confidence: 0.9,
source: sourceId,
timestamp: Date.now()
})),
lastMentioned: Date.now(),
mentionCount: 1
});
}
}
}
メモリを活用したプロンプト
関連するメモリをプロンプトに含めます
使用する場面:メモリコンテキストを使って LLM を呼び出す場合
async function promptWithMemory(
query: string,
memory: MemorySystem,
systemPrompt: string
): Promise<string> {
// Retrieve relevant memories
const relevantMemories = await memory.longTerm.search(query, 5);
const entities = await memory.entity.getRelevant(query);
const recentContext = memory.buffer.getRecent(5);
// Build memory-augmented prompt
const prompt = `
${systemPrompt}
## User Context
${entities.length ? `Known about user:\n${entities.map(e =>
`- ${e.name}: ${e.facts.map(f => f.content).join('; ')}`
).join('\n')}` : ''}
${relevantMemories.length ? `Relevant past interactions:\n${relevantMemories.map(m =>
`- [${formatDate(m.timestamp)}] ${m.content}`
).join('\n')}` : ''}
## Recent Conversation
${formatMessages(recentContext)}
## Current Query
${query}
`.trim();
const response = await llm.complete(prompt);
// Do not promote generated assistant claims into user facts.
// Persist only authorized source messages through the application memory policy.
return response;
}
注意すべき点
メモリストアが無制限に増大し、システムが遅くなる
重大度:高
状況:時間の経過とともにシステムが遅くなり、コストが増加します
症状:
- メモリ検索が遅い
- ストレージコストが高い
- 時間の経過とともにレイテンシが増加する
問題の原因: すべてのメッセージをメモリとして保存している。 クリーンアップも統合もしていない。 数百万件を超えるアイテムを検索している。
推奨される修正:
// Implement memory lifecycle management
class ManagedMemory {
// Limits
private readonly SHORT_TERM_MAX = 100;
private readonly LONG_TERM_MAX = 10000;
private readonly CONSOLIDATION_INTERVAL = 24 * 60 * 60 * 1000;
async add(memory: Memory): Promise<void> {
// Score importance before storing
const score = await this.scoreImportance(memory);
if (score < 0.3) return; // Don't store low-importance
memory.importance = score;
await this.shortTerm.add(memory);
// Check limits
await this.enforceShortTermLimit();
}
async enforceShortTermLimit(): Promise<void> {
const count = await this.shortTerm.count();
if (count > this.SHORT_TERM_MAX) {
// Consolidate: move important to long-term, delete rest
const memories = await this.shortTerm.getAll();
memories.sort((a, b) => b.importance - a.importance);
const toKeep = memories.slice(0, this.SHORT_TERM_MAX * 0.7);
const toConsolidate = memories.slice(this.SHORT_TERM_MAX * 0.7);
for (const m of toConsolidate) {
if (m.importance > 0.7) {
await this.longTerm.add(m);
}
await this.shortTerm.remove(m.id);
}
}
}
async scoreImportance(memory: Memory): Promise<number> {
const factors = {
hasUserPreference: /prefer|like|don't like|hate|love/i.test(memory.content) ? 0.3 : 0,
hasDecision: /decided|chose|will do|won't do/i.test(memory.content) ? 0.3 : 0,
hasFactAboutUser: /my|I am|I have|I work/i.test(memory.content) ? 0.2 : 0,
length: memory.content.length > 100 ? 0.1 : 0,
userMessage: memory.role === 'user' ? 0.1 : 0,
};
return Object.values(factors).reduce((a, b) => a + b, 0);
}
}
検索されたメモリが現在のクエリに関連していない
重大度:高
状況:メモリがコンテキストに含まれているものの、役に立たない
症状:
- コンテキスト内のメモリが無関係に見える
- ユーザーが、すでにメモリにある情報について質問する
- 無関係なコンテキストによって混乱が生じる
問題の原因: 単純なキーワードマッチング。 関連性のスコアリングがない。 検索されたメモリをすべて含めている。
推奨される修正:
// Intelligent memory retrieval
async function retrieveRelevant(
query: string,
memories: MemoryStore,
maxResults: number = 5
): Promise<Memory[]> {
// 1. Semantic search
const candidates = await memories.semanticSearch(query, maxResults * 3);
// 2. Score relevance with context
const scored = await Promise.all(candidates.map(async (m) => {
const relevanceScore = await llm.complete(`
Rate 0-1 how relevant this memory is to the query.
Query: "${query}"
Memory: "${m.content}"
Return just the number.
`);
const score = Number(relevanceScore.trim());
return { ...m, relevance: Number.isFinite(score) && score >= 0 && score <= 1 ? score : 0 };
}));
// 3. Filter low relevance
const relevant = scored.filter(m => m.relevance > 0.5);
// 4. Sort and limit
return relevant
.sort((a, b) => b.relevance - a.relevance)
.slice(0, maxResults);
}
あるユーザーのメモリを別のユーザーが参照できる
重大度:重大
状況:ユーザーが別のユーザーのセッションの情報を見てしまう
症状:
- ユーザーが別のユーザーの情報を見る
- プライバシーに関する苦情
- コンプライアンス違反
問題の原因: メモリストアでユーザーが分離されていない。 メモリの名前空間を共有している。 ユーザーをまたいで検索される。
推奨される修正:
// Strict user isolation in memory
class IsolatedMemory {
private getKey(userId: string, memoryId: string): string {
// Namespace all keys by user
return `user:${encodeURIComponent(userId)}:memory:${encodeURIComponent(memoryId)}`;
}
async add(userId: string, memory: Memory): Promise<void> {
// Validate userId is authenticated
if (requireAuthenticatedUserId() !== userId) {
throw new Error('Invalid user ID');
}
const key = this.getKey(userId, memory.id);
memory.userId = userId; // Tag with user
await this.store.set(key, memory);
}
async search(userId: string, query: string): Promise<Memory[]> {
// CRITICAL: Filter by user in query
return await this.store.search({
query,
filter: { userId: userId }, // Mandatory filter
limit: 10
});
}
async delete(userId: string, memoryId: string): Promise<void> {
const memory = await this.get(userId, memoryId);
// Verify ownership before delete
if (!memory || memory.userId !== userId) {
throw new Error('Access denied');
}
await this.store.delete(this.getKey(userId, memoryId));
}
// Scoped export (not proof of legal compliance)
async exportUserData(userId: string): Promise<Memory[]> {
return await this.store.getAll({ userId });
}
// Scoped deletion (also address backups, indexes, caches and retention policy)
async deleteUserData(userId: string): Promise<void> {
const memories = await this.exportUserData(userId);
for (const m of memories) {
await this.store.delete(this.getKey(userId, m.id));
}
}
}
検証チェック
ユーザー分離がない
重大度:重大
メッセージ:ユーザー分離なしでメモリ操作が行われています。プライバシーの脆弱性があります。
修正アクション:すべてのメモリ操作に userId を追加し、検索時にユーザーでフィルタリングします
重要度によるフィルタリングがない
重大度:警告
メッセージ:重要度によるフィルタリングなしでメモリを保存しています。メモリが爆発的に増加する可能性があります。
修正アクション:保存前に重要度をスコアリングし、重要度の低い内容を除外します
検索処理を伴わないメモリ保存
重大度:警告
メッセージ:メモリを保存していますが、検索ロジックがありません。メモリは利用されません。
修正アクション:メモリ検索を実装し、プロンプトに含めます
メモリのクリーンアップがない
重大度:情報
メッセージ:メモリのクリーンアップ機構がありません。ストレージは無制限に増大します。
修正アクション:経過時間/重要度に基づく統合とクリーンアップを実装します
コラボレーション
委譲のトリガー
- context window|token -> context-window-management(コンテキストの最適化が必要)
- rag|retrieval|vector -> rag-implementation(検索システムが必要)
- cache|caching -> prompt-caching(キャッシュ戦略が必要)
完全なメモリシステム
スキル:conversation-memory、context-window-management、rag-implementation
ワークフロー:
1. Design memory tiers
2. Implement storage and retrieval
3. Integrate with context management
4. Add consolidation and cleanup
関連スキル
相性の良いスキル:context-window-management、rag-implementation、prompt-caching、llm-npc-dialogue
使用する場面
- ユーザーが会話メモリに言及している、または示唆している場合
- ユーザーが記憶することに言及している、または示唆している場合
- ユーザーがメモリの永続化に言及している、または示唆している場合
- ユーザーが長期メモリに言及している、または示唆している場合
- ユーザーがチャット履歴に言及している、または示唆している場合
制限事項
- タスクが上記の説明された範囲に明確に該当する場合にのみ、このスキルを使用してください。
- 出力を、環境固有の検証、テスト、または専門家によるレビューの代わりとして扱わないでください。
- 必要な入力、権限、安全上の境界、または成功基準が不足している場合は、作業を中断して確認を求めてください。