Skip to content

feat: メモリシステムの導入(最小構成) #141

@endo-ly

Description

@endo-ly

背景

EgoPulse のプロジェクト目標は「自分自身の文脈を理解するAIエージェント」だが、現在はセッションを跨いだ記憶がない。毎回ゼロから会話を始める状態。

Microclaw のメモリシステムを参考に、5テーブル全て を導入する。Reflector/Injection のログテーブルは「ちゃんと動いているか」の確認に不可欠であり、最初の実装・デバッグフェーズで最も必要になる。

仕組みの概要

会話する → Reflector(LLM呼び出しで記憶を抽出) → memories に保存
                                                    ↓
次の会話開始時 ← Injection(記憶をシステムプロンプトに埋め込み)

Reflector(抽出)

  • 会話が一定量溜まったら、別LLM呼び出しで「この会話から覚えておくべきことを抽出」
  • 構造化パースして memories テーブルに保存
  • memory_reflector_state で「どこまで抽出したか」を進捗管理(差分抽出)
  • memory_reflector_runs で実行結果(成功/失敗/抽出数/エラー詳細)を記録

Injection(注入)

  • セッション開始時に最新の memories を上位N件取得
  • トークン上限内に調整して system prompt に append
  • memory_injection_logs で注入結果(候補数/採用数/除外数/推定トークン)を記録

Supersede(置換)

  • 新しい記憶が古い記憶を更新・訂正した場合、古い記憶をアーカイブし置換関係を記録
  • memory_supersede_edges で置換グラフを管理

スキーマ(5テーブル)

1. memories — 構造化知識ストレージ

CREATE TABLE IF NOT EXISTS memories (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    chat_id INTEGER,                          -- NULL = グローバル記憶
    content TEXT NOT NULL,
    category TEXT NOT NULL,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL,
    confidence REAL NOT NULL DEFAULT 0.70,
    source TEXT NOT NULL DEFAULT 'reflector',
    last_seen_at TEXT NOT NULL,
    is_archived INTEGER NOT NULL DEFAULT 0,
    archived_at TEXT
);

CREATE INDEX IF NOT EXISTS idx_memories_chat ON memories(chat_id);
CREATE INDEX IF NOT EXISTS idx_memories_active_updated
    ON memories(is_archived, updated_at);
CREATE INDEX IF NOT EXISTS idx_memories_confidence ON memories(confidence);
カラム 説明
chat_id チャット固有の記憶。NULL = グローバル記憶
content 記憶の本文
category カテゴリ分類(preference, fact, instruction 等)
confidence 信頼度スコア(0.0〜1.0)。高いほど優先的に注入
source 生成元(reflector, legacy 等)
last_seen_at 最後に参照された日時。鮮度管理に使用
is_archived ソフトデリートフラグ。置換や陳腐化でアーカイブ

2. memory_reflector_state — 抽出進捗管理

CREATE TABLE IF NOT EXISTS memory_reflector_state (
    chat_id INTEGER PRIMARY KEY,
    last_reflected_ts TEXT NOT NULL,
    updated_at TEXT NOT NULL
);
カラム 説明
last_reflected_ts 前回抽出完了時点のメッセージタイムスタンプ。この後の新規メッセージのみ差分抽出

3. memory_reflector_runs — Reflector 実行ログ

CREATE TABLE IF NOT EXISTS memory_reflector_runs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    chat_id INTEGER NOT NULL,
    started_at TEXT NOT NULL,
    finished_at TEXT NOT NULL,
    extracted_count INTEGER NOT NULL DEFAULT 0,
    inserted_count INTEGER NOT NULL DEFAULT 0,
    updated_count INTEGER NOT NULL DEFAULT 0,
    skipped_count INTEGER NOT NULL DEFAULT 0,
    dedup_method TEXT NOT NULL,
    parse_ok INTEGER NOT NULL DEFAULT 1,
    error_text TEXT
);

CREATE INDEX IF NOT EXISTS idx_memory_reflector_runs_chat_started
    ON memory_reflector_runs(chat_id, started_at);
カラム 説明
extracted_count LLMが抽出した記憶候補数
inserted_count 新規挿入数
updated_count 既存記憶の更新数
skipped_count スキップ数(重複等)
dedup_method 重複排除手法(semantic, jaccard, none 等)
parse_ok LLM出力のパース成功フラグ。失敗時は error_text に詳細

なぜ最初から必要か: Reflector はバックグラウンドで動く非同期ジョブ。「抽出が動いてるか」「何件取れてるか」「パースエラーが出てないか」を確認する唯一の手段。

4. memory_injection_logs — Injection 実行ログ

CREATE TABLE IF NOT EXISTS memory_injection_logs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    chat_id INTEGER NOT NULL,
    created_at TEXT NOT NULL,
    retrieval_method TEXT NOT NULL,
    candidate_count INTEGER NOT NULL DEFAULT 0,
    selected_count INTEGER NOT NULL DEFAULT 0,
    omitted_count INTEGER NOT NULL DEFAULT 0,
    tokens_est INTEGER NOT NULL DEFAULT 0
);

CREATE INDEX IF NOT EXISTS idx_memory_injection_logs_chat_created
    ON memory_injection_logs(chat_id, created_at);
カラム 説明
retrieval_method 検索手法(keyword, semantic, provider 等)
candidate_count 候補として抽出された記憶数
selected_count 実際にコンテキストに注入された数
omitted_count トークン制限等で除外された数
tokens_est 推定消費トークン数

なぜ最初から必要か: 「記憶は注入されてるか」「トークンを食いすぎてないか」「除外されすぎてないか」の確認に必須。

5. memory_supersede_edges — 記憶の置換関係グラフ

CREATE TABLE IF NOT EXISTS memory_supersede_edges (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    from_memory_id INTEGER NOT NULL,
    to_memory_id INTEGER NOT NULL,
    reason TEXT,
    created_at TEXT NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_memory_supersede_from
    ON memory_supersede_edges(from_memory_id, created_at);
CREATE INDEX IF NOT EXISTS idx_memory_supersede_to
    ON memory_supersede_edges(to_memory_id, created_at);
カラム 説明
from_memory_id 置換元(新しい記憶)の memories.id
to_memory_id 置換先(古い記憶、アーカイブ対象)の memories.id
reason 置換理由(LLMが出力)

なぜ最初から必要か: 記憶が上書きされた際に「なぜ古い記憶が消えたか」を追跡する唯一の手段。デバッグで「この記憶どうして消えた?」を調査する時に必要。

実装ステップ

  1. スキーマ追加: マイグレーションで5テーブルを CREATE
  2. storage.rs に CRUD 追加: 全テーブルの操作メソッド
  3. Reflector 実装: 会話履歴 → LLM抽出 → 構造化パース → DB保存 → 実行ログ記録
  4. Injection 実装: セッション開始時に記憶を検索 → system prompt に組み込み → 注入ログ記録
  5. Supersede 実装: 重複検出時の古い記憶アーカイブ + 置換エッジ記録
  6. トリガー設計: Reflector をいつ走らせるか(会話終了時?定期実行?)
  7. テスト: AAA パターンで単体テスト(スキーマ操作 + Reflector/Injection/Supersede 各ロジック)

参考

  • Microclaw メモリ設計: docs/30.egopulse/microclaw-db-memory.md
  • Microclaw 実装: /root/workspace/microclaw/crates/microclaw-storage/src/db.rs
  • Microclaw Reflector ロジック: /root/workspace/microclaw/src/scheduler.rs (lines 516-673)
  • Microclaw Injection ロジック: /root/workspace/microclaw/src/memory_service.rs (line 401)
  • EgoPulse 現状スキーマ: docs/30.egopulse/egopulse-db-schema.md

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions