mcpField
M
memory-persistence

memory-persistence

github.com/itallstartedwithaideaエージェントワークフロー4492 回インストール28 週間前に更新
リポジトリ

memory-persistence について

memory-persistence は、AI エージェントがセッションをまたいで文脈を保持するための Agent Skills™ スキルです。事実の抽出とセッション要約を保存し、後の作業に関連する記憶を取得します。

主な機能

  1. 01セッション終了時のエンティティ抽出
  2. 02会話の要約
  3. 03関連度に基づく記憶検索
  4. 04エンティティ・セッション・パターンの記憶
  5. 05Cloudflare KV による永続保存

活用シーン

  1. 01複数セッションでユーザーの好みを保持
  2. 02過去のプロジェクトやアカウントの文脈を再利用
  3. 03継続的な判断の一貫性を維持
  4. 04監査用のセッション要約を作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/itallstartedwithaidea/agent-skills --skill memory-persistence

インストール後、Claude Code で/memory-persistenceを入力して読み込まれたか確認します。

詳細

メモリ永続化

Agent Skills™ の一部です。googleadsagent.ai™ 提供

説明

メモリ永続化により、AI エージェントはセッションをまたいで知識を維持できるようになり、ステートレスな推論呼び出しを、継続的に改善されるステートフルなシステムへと変えます。永続化がなければ、新しいセッションは毎回ゼロから始まり、エージェントはユーザーの好みを学び直し、コードベースのパターンを再発見し、すでに修正した間違いを繰り返さなければなりません。メモリ永続化では、セッション終了時に重要なコンテキストを保存し、セッション開始時に再読み込みするフックを実装することで、あたかも記憶が継続しているかのように見せます。

このスキルは、googleadsagent.ai™ の Buddy™ を支える本番環境のメモリシステムを基盤としており、永続的なメモリストアとして Cloudflare KV を使用します。会話のたびに Buddy™ はエンティティ(キャンペーン、指標、ユーザーの好み、決定事項など)を抽出し、セッションを要約して、ユーザーとセッションをキーとして結果を保存します。次の会話では、最も関連性の高いメモリが取得されてコンテキストに注入されるため、Buddy™ は過去の分析を参照し、明示された好みを尊重し、以前の決定を踏まえて対応できます。

メモリシステムは、エンティティレベルのメモリ(「ユーザーは保守的な入札を好む」といった個別の事実)、セッションレベルのメモリ(要約された会話)、パターンレベルのメモリ(「このアカウントは常にブランド関連語句に過剰な予算を費やす」といった繰り返しの行動)の3つの粒度で動作します。それぞれの粒度は異なる取得パターンに対応し、保存要件や鮮度要件も異なります。

使用する場面

  • ユーザーが複数のセッションにわたってエージェントとやり取りし、継続性を期待する場合
  • エージェントが、時間が経っても一貫性を保つべき判断(好み、規約など)を行う場合
  • ドメイン知識がセッションを重ねるごとに蓄積され、失われてはならない場合
  • コードベースの構造やプロジェクトのコンテキストを繰り返し説明するのを避けたい場合
  • アカウント、キャンペーン、指標など、変化するエンティティをエージェントが時間を追って追跡する必要がある場合
  • 監査証跡やコンプライアンスのために、セッションの要約が必要な場合

仕組み

graph TD
    A[Session Start] --> B[Memory Retrieval]
    B --> C[Context Assembly]
    C --> D[Agent Execution]
    D --> E[Session End Trigger]
    E --> F[Entity Extraction]
    F --> G[Session Summarization]
    G --> H[Memory Indexing]
    H --> I[KV Store Write]
    
    B --> J[KV Store Read]
    J --> K{Match Found?}
    K -->|Yes| L[Relevance Ranking]
    L --> M[Top-K Selection]
    M --> C
    K -->|No| C

セッション開始時に、メモリ取得システムは KV ストアに問い合わせ、現在のユーザーとタスクに関連するメモリを検索します。取得したメモリは関連性と新しさに基づいてランク付けされ、上位 K 件がエージェントのコンテキストに注入されます。セッション中、エージェントは通常どおり動作します。フックまたは明示的な保存によってセッションが終了すると、エンティティ抽出器が会話から重要な事実、決定事項、好みを特定します。セッション要約器が簡潔な要約を作成します。エンティティと要約の両方にインデックスが付けられ、今後の取得に備えて KV ストアに書き込まれます。

実装

メモリストアインターフェース(Cloudflare KV):

interface Memory {
  id: string;
  userId: string;
  type: "entity" | "session_summary" | "pattern";
  content: string;
  metadata: {
    sessionId: string;
    timestamp: number;
    importance: number;
    tags: string[];
  };
}

class MemoryStore {
  constructor(private kv: KVNamespace) {}

  async save(memory: Memory): Promise<void> {
    const key = `memory:${memory.userId}:${memory.type}:${memory.id}`;
    await this.kv.put(key, JSON.stringify(memory), {
      metadata: { importance: memory.metadata.importance, timestamp: memory.metadata.timestamp },
      expirationTtl: 60 * 60 * 24 * 90,
    });

    const indexKey = `index:${memory.userId}:${memory.type}`;
    const existing = await this.kv.get<string[]>(indexKey, "json") || [];
    existing.push(memory.id);
    await this.kv.put(indexKey, JSON.stringify(existing));
  }

  async retrieve(userId: string, type: string, limit = 10): Promise<Memory[]> {
    const indexKey = `index:${userId}:${type}`;
    const ids = await this.kv.get<string[]>(indexKey, "json") || [];

    const memories: Memory[] = [];
    for (const id of ids.slice(-limit * 2)) {
      const key = `memory:${userId}:${type}:${id}`;
      const mem = await this.kv.get<Memory>(key, "json");
      if (mem) memories.push(mem);
    }

    return memories
      .sort((a, b) => b.metadata.importance * 0.6 + b.metadata.timestamp * 0.4
        - (a.metadata.importance * 0.6 + a.metadata.timestamp * 0.4))
      .slice(0, limit);
  }
}

セッション終了フック(エンティティ抽出):

ENTITY_EXTRACTION_PROMPT = """Analyze the following conversation and extract key entities.

Categories:
- DECISIONS: Choices made, strategies adopted, preferences stated
- METRICS: Specific numbers, KPIs, performance data mentioned
- PREFERENCES: User preferences for communication style, analysis depth, risk tolerance
- FACTS: Key facts about the account, project, or domain

Conversation:
{conversation}

Respond with JSON:
{{"entities": [{{"category": "...", "content": "...", "importance": 0.0-1.0}}]}}"""

async def extract_and_persist(conversation: list[dict], user_id: str, session_id: str):
    prompt = ENTITY_EXTRACTION_PROMPT.format(
        conversation=format_conversation(conversation[-20:])
    )
    result = await model.generate(prompt, temperature=0.0)
    entities = json.loads(result)["entities"]

    store = MemoryStore(kv_namespace)
    for entity in entities:
        memory = Memory(
            id=f"{session_id}_{hash(entity['content'])[:8]}",
            user_id=user_id,
            type="entity",
            content=entity["content"],
            metadata={
                "session_id": session_id,
                "timestamp": int(time.time()),
                "importance": entity["importance"],
                "tags": [entity["category"]],
            },
        )
        await store.save(memory)

    summary = await summarize_session(conversation)
    await store.save(Memory(
        id=session_id,
        user_id=user_id,
        type="session_summary",
        content=summary,
        metadata={"session_id": session_id, "timestamp": int(time.time()), "importance": 0.7, "tags": ["summary"]},
    ))

メモリ拡張セッション開始:

async def build_memory_context(user_id: str, current_task: str) -> str:
    store = MemoryStore(kv_namespace)

    entities = await store.retrieve(user_id, "entity", limit=15)
    summaries = await store.retrieve(user_id, "session_summary", limit=5)

    context_parts = []
    if entities:
        context_parts.append("<prior_knowledge>")
        for entity in entities:
            tag = entity.metadata["tags"][0] if entity.metadata["tags"] else "fact"
            context_parts.append(f"[{tag.upper()}] {entity.content}")
        context_parts.append("</prior_knowledge>")

    if summaries:
        context_parts.append("<recent_sessions>")
        for s in summaries:
            context_parts.append(f"- {s.content}")
        context_parts.append("</recent_sessions>")

    return "\n".join(context_parts)

ベストプラクティス

  1. エンティティの抽出はセッション中ではなく終了時に行う — リアルタイムでの抽出はエージェントの作業の流れを妨げます。セッション終了時にまとめて抽出するほうが、コストを抑えられ、会話全体のコンテキストを利用できるため精度も高くなります。
  2. 重要度 × 新しさで順位付けする — すべての記憶が同じ価値を持つわけではありません。エンティティの記憶では新しさよりも重要度(ユーザーにとって重要な決定)を重視し、セッションの要約では重要度よりも新しさを重視してください。
  3. TTLを積極的に設定する — 90日を過ぎた記憶が関連性を持つことはほとんどありません。ストアを管理しやすく保ち、検索を高速化するため、自動的に期限切れにしてください。
  4. 記憶の注入トークン数に上限を設ける — コンテキストウィンドウの10%を超えて、取得した記憶に割り当てないでください。それを超えると、現在のタスクへの注意が薄れてしまいます。
  5. 抽出にはモデルを使う — 正規表現ベースのエンティティ抽出は壊れやすいため、抽出には高速なモデル(Haikuクラス)を使い、主要モデルは実際のタスク用に確保してください。
  6. 記憶スキーマをバージョン管理する — 抽出プロンプトの進化に伴い、古いスキーマで作られた記憶は互換性がなくなる場合があります。移行に備えてバージョンフィールドを含めてください。
  7. 記憶の関連性をテストする — 取得した記憶が実際にタスクの完了に役立っているか、定期的に監査してください。関連性のない記憶はコンテキストの予算を無駄にします。

プラットフォームとの互換性

機能Claude CodeCursorCodexGemini CLI
セッションフック✅ stop hooks✅ 拡張機能⚠️ カスタム⚠️ カスタム
KVストレージ✅ 任意のKV/ファイル✅ 任意のKV/ファイル✅ 任意のKV/ファイル✅ 任意のKV/ファイル
コンテキスト注入✅ CLAUDE.md✅ ルール/スキル✅ 指示✅ システムプロンプト
エンティティ抽出✅ 完全対応✅ 完全対応✅ 完全対応✅ 完全対応
自動保存トリガー✅ フック✅ イベント⚠️ 手動⚠️ 手動

関連スキル

  • エンティティメモリ管理 - 永続化レイヤーを基盤とする、きめ細かなエンティティレベルの記憶
  • 継続的学習 - 永続化されたセッションデータにより、セッションをまたいだパターンのマイニングとスキルの導出が可能になります
  • トークン最適化 - エージェントの注意が薄れないよう、記憶の注入ではコンテキストウィンドウの予算を守る必要があります
  • 品質スコア最適化 - セッションをまたいだQSの継続的な追跡により、過去の傾向を分析できます

キーワード

memory-persistence, session-memory, entity-extraction, cloudflare-kv, session-summarization, memory-retrieval, cross-session-context, memory-indexing, knowledge-persistence, agent-skills


© 2026 googleadsagent.ai™ | Agent Skills™ | MIT License

コメント