<p align="center">
  <img src="assets/logo.png" alt="ERINYS" width="600">
</p>

# ERINYS — AIエージェントのための反射記憶

**LongMemEval-S (`_s` split) Recall@5 100% · LoCoMo 94% · ConvoMem 98% — 検索パイプラインのLLM呼び出しゼロ。**

[🇬🇧 English](README.md)

> **あった記憶から、なかった記憶すら生み出す。**

AIエージェントの記憶システムは、人間の記憶を模倣してきた。短期記憶、長期記憶、エピソード記憶、意味記憶。教科書通りの分類をそのまま実装に持ち込む。

使ってみて、ずっと違和感があった。

人間は忘れる。でも既存のメモリは忘れない。際限なく溜まり、古い情報が新しい情報と同じ重みで検索に出る。人間は「あれ、前と言ってること違くない？」と気づく。でもメモリは黙って上書きする。人間は2つの無関係な経験を結びつけて「あ、これ使える」と閃く。でもメモリはただ保存して返すだけ。

模倣すべきは記憶の分類じゃなかった。記憶の振る舞いだった。

その違和感がERINYSを呼んだ。

ERINYSは番犬だ。覚え、忘れ、疑い、噛む。

> **出自:** ERINYSは[HyperAION](https://github.com/GhostyAI-HA)（AIエージェント自己改善フレームワーク）の検索レイヤーとして開発された。スタンドアロンのMCPサーバーとして公開しており、どのエージェントスタックからでも独立して利用できる。

## ベンチマーク

全結果、同一モード（`enhanced_v2_boost`）で検索パイプラインに**LLM呼び出しゼロ**。注: 上位機能（Dream Cycle、蒸留）はLLMを使用する — 下記参照。

| ベンチマーク | N | R@5 | R@10 | 平均レイテンシ |
|:--|:--|:--|:--|:--|
| **LongMemEval-S** | 500 | **100.0%** | **100.0%** | 10.3 ms |
| **LoCoMo** | 1,982 | **94.0%** | **98.1%** | 6.9 ms |
| **ConvoMem** | 250 | **97.6%** | — | — |

> **なぜ重要か：** APIキー不要。ネットワーク不要。検索にトークン消費ゼロ。ERINYSはFTS5 + sqlite-vec + アルゴリズムブーストだけでこれらの精度を実現。エージェントの記憶検索はSQLiteの速度で動く。
>
> LongMemEvalは `longmemeval_s` split（各約20セッション/問）で評価。**より難しい `_m` splitはまだ評価していない。** 詳細な手法・カテゴリ別内訳・再現手順 → [benchmarks/BENCHMARKS.md](benchmarks/BENCHMARKS.md)

100%到達までの物語 → [🇯🇵 日本語](docs/benchmark_story_ja.md) / [🇺🇸 English](docs/benchmark_story_en.md)

## 他のメモリシステムと何が違うか

**忘れる能力。** 多くのメモリは追記するだけで増え続ける。ERINYSはエビングハウスの忘却曲線で古い情報を沈め、アクセス頻度で重要な記憶を浮かせる。手動で整理しなくても検索結果が常に鮮度と関連性で最適化される。減衰は自動実行 — LLM不要。

**蒸留。** 具体的なバグ修正（「JWTのhttpOnlyフラグ漏れ」）から3層を自動生成する。事実→パターン（「新規エンドポイントにはセキュリティチェックリストが必要」）→原則（「セキュリティのデフォルトは安全側に倒すべき」）。プロジェクトを超えて使える教訓を記憶から抽出する。他のメモリシステムに蒸留機能はない。⚠️ *蒸留の抽象/メタ層生成にはLLM呼び出しが必要。*

**Dream Cycle。** 過去のメモ2つをLLMに渡して「関連はある？」と聞く。候補ペアは意味的類似度で選定。近すぎず（重複）遠すぎず（無関係）のスイートスポット（コサイン0.65–0.90）だけ。現在は`erinys_dream`の手動呼び出しのみ。⚠️ *Dream CycleはLLM呼び出しを使用する — ゼロLLM検索パイプラインの一部ではない。*

## 設計思想

### 記憶には層がある

すべての記憶は同じではない。ERINYSは知識を抽象度で3層に分ける。

- **Concrete（具体）** — 起きた事実そのもの。「`/api/auth`エンドポイントでJWTのhttpOnlyフラグが抜けていた」
- **Abstract（抽象）** — 具体的事実から抽出されたパターン。「新しいAPIエンドポイントにはセキュリティヘッダーのチェックリストが必要」
- **Meta（原則）** — パターンから抽出された原則。「セキュリティのデフォルト値は、手動で有効化しなくても安全な状態にすべき」

1件のバグ修正から3層すべてが自動生成される。時間が経つと、Meta層にはプロジェクトも技術スタックも問わない普遍的な原則が蓄積される。

### 忘れることは機能である

すべての記憶には強度スコアがある。エビングハウスの忘却曲線に従い、時間とともに減衰する。半年前のメモは昨日のメモより低くランクされる。ただし頻繁にアクセスされた記憶は減衰に抵抗する。復習と同じ仕組み。

強度が閾値を下回ると、その記憶は刈り取り候補になる。DBが肥大化せず、検索結果のノイズが減る。

### 事実は変わる。履歴は消すな

情報が更新されたとき（「AWSからGCPに移行した」）、ERINYSは上書きしない。Supersede Chain（上書き連鎖）を作る。古い事実は「置き換え済み」とマークされるが消えない。「3月時点では何を信じていた？」と聞けば、その時点で有効だった答えが返る。

### 矛盾は検出すべき

エージェントの記憶に「PostgreSQLを使う」と「SQLiteを使う」が両方あったら矛盾。ERINYSはこれを検出して表面化させる。黙ってDBを切り替えるのではなく、「前にPostgreSQLを選んだけど、要件が変わった？」と聞く。

### 検索はキーワードだけでなく、意味も見つける

2つの検索を同時に実行し、結果を融合する。

- **キーワード検索**（FTS5）— 完全一致。高速で正確。
- **ベクトル検索**（sqlite-vec）— 意味の類似性。「認証」で検索すると「ログイン」「JWT」「セッショントークン」もヒットする。

2つの結果リストはRRF（Reciprocal Rank Fusion）で統合。両方で上位 = 最高スコア。

### すべてローカルで完結

SQLiteファイル1つ。クラウドAPI不要。APIキー不要。サブスク不要。オフライン動作。エージェントの記憶がマシン外に出ることはない。

## ベンチマーク性能 (LongMemEval)

ERINYSの最新アーキテクチャ（`enhanced_v4`）は、LLMエージェント向けの長期記憶ベンチマークである[LongMemEval](https://github.com/WeizeChen/LongMemEval)において、**LLMのリライト（自己クエリ拡張）を用いずに物理的限界である最高スコアに到達**しました。

| カテゴリ | 質問数 | R@5 | R@10 |
| :--- | :--- | :--- | :--- |
| multi-session | 133 | 100.0% | 100.0% |
| temporal-reasoning | 133 | 100.0% | 100.0% |
| knowledge-update | 78 | 100.0% | 100.0% |
| single-session-user | 70 | 100.0% | 100.0% |
| single-session-assistant | 56 | 100.0% | 100.0% |
| single-session-preference | 30 | 100.0% | 100.0% |
| **Overall (総合)** | **500** | **100.0%** | **100.0%** |

*   **NDCG@5**: 0.943
*   **遅延 (Latency)**: 平均 **11.2ms / query**（ローカルマシン上でのSQLite検索 + RRF合成処理総計）
*   **特徴**: クエリのスペルミス耐性と厳密な名詞抽出、および時間的近接性に基づく減衰ブーストグラフを組み合わせることで、高コストな「LLMによるクエリ書き換え」をゼロに抑えたまま、トップランナーであるMemPalace（Hybrid v4 + Haiku搭載）と同等以上の検索精度と圧倒的な低遅延を実現しています。
*   **注意**: 上記は `longmemeval_s` split での結果です。`_m` split はまだ評価していません。R@5 は検索リコール（正しいセッションがTop-Kに含まれるか）であり、エンドツーエンドのQA精度とは異なります。

## ユースケース

### 1. セッションをまたぐコーディングエージェントの記憶

```python
# バグ修正後にエージェントが学びを保存
erinys_save(
  title="JWT httpOnlyフラグ漏れを修正",
  content="CookieがJSからアクセス可能だった。httpOnly: true, secure: true, sameSite: strictを追加。",
  type="bugfix",
  project="my-app"
)

# 翌週、似たタスクが来たらエージェントが記憶を検索
erinys_search(query="認証 Cookie セキュリティ", project="my-app")
# → JWTの修正がスコア付きで返る。同じミスを繰り返さない。
```

### 2. 矛盾検出

```python
erinys_save(title="DB選定", content="シンプルさ重視でSQLiteを使用", project="my-app")
erinys_conflict_check(observation_id=42)
# → "⚠️ Observation #18 と矛盾: 'PostgreSQLを本番信頼性のために使用'"
```

### 3. Dream Cycle — 夜間の知識合成

```python
erinys_dream(max_collisions=10)
# ERINYSが「ちょうどいい距離」のペアを選ぶ（コサイン類似度 0.65–0.90）
# メモA: 「RTKでトークン消費を60-90%削減」
# メモB: 「Bootstrap Gateは5つのスクリプトを順次実行で3秒かかる」
# → 気づき: 「Bootstrap Gateの各スクリプトにRTKを適用すれば起動が速くなる」
```

### 4. 時間旅行クエリ

```python
erinys_timeline(query="デプロイ先", as_of="2026-03-01")
# → "AWS EC2 (2026-02-15に決定)"

erinys_timeline(query="デプロイ先", as_of="2026-04-01")
# → "GCP Cloud Run (2026-03-20にAWSから切り替え)"
```

### 5. 知識蒸留

```python
erinys_save(title="新エンドポイントにCORSヘッダー漏れ", type="bugfix", ...)
erinys_distill(observation_id=50, level="meta")
# → concrete: "/api/v2/usersエンドポイントにCORSヘッダーが未設定"
# → abstract: "新規APIエンドポイントにはCORSレビューチェックリストが必要"
# → meta:     "セキュリティ設定はデフォルトで安全にし、解除を明示的にすべき"
```

### 6. Obsidianエクスポート

```python
erinys_export(format="markdown")
# → [[wikilink]]付きの.mdファイルを生成
# Obsidianに入れればナレッジグラフが即座に可視化
```

## クイックスタート

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
ollama pull gemma4:e4b

# MCPサーバーとして起動（stdio）
python -m erinys_memory.server

# テスト実行
PYTHONPATH=src pytest tests/ -v
```

## MCP設定

### Claude Desktop / Claude Code

```json
{
  "mcpServers": {
    "erinys": {
      "command": "/path/to/ERINYS-mem/.venv/bin/python3",
      "args": ["-m", "erinys_memory.server"],
      "env": {
        "ERINYS_DB_PATH": "~/.erinys/memory.db"
      }
    }
  }
}
```

### Antigravity

`~/.gemini/antigravity/settings.json` の `mcpServers` に追加:

```json
{
  "erinys": {
    "command": "/path/to/ERINYS-mem/.venv/bin/python3",
    "args": ["-m", "erinys_memory.server"],
    "env": {
      "ERINYS_DB_PATH": "~/.erinys/memory.db"
    }
  }
}
```

## 環境変数

| 変数 | デフォルト | 説明 |
|:--|:--|:--|
| `ERINYS_DB_PATH` | `~/.erinys/memory.db` | SQLiteデータベースのパス |
| `ERINYS_EMBEDDING_MODEL` | `BAAI/bge-small-en-v1.5` | fastembed埋め込みモデル |
| `ERINYS_DISTILL_MODEL` | `gemma4:e4b` | 自動蒸留に使うローカルOllamaモデル |
| `ERINYS_DISTILL_ENDPOINT` | `http://localhost:11434/api/generate` | ローカルOllama生成エンドポイント |

## Tools (25)

### Core
- `erinys_save` — Save observation (with topic_key upsert)
- `erinys_get` — Get by ID (full content, untruncated)
- `erinys_update` — Partial update
- `erinys_delete` — Delete with FK cascade
- `erinys_search` — RRF hybrid search (FTS5 + vector)
- `erinys_save_prompt` — Save user prompt
- `erinys_recall` — Recent observations
- `erinys_context` — Session context recall
- `erinys_export` — Obsidian-compatible markdown export
- `erinys_backup` — SQLite backup
- `erinys_stats` — Database statistics

### Graph
- `erinys_link` — Create typed edge
- `erinys_traverse` — BFS graph traversal
- `erinys_prune` — Prune weak/decayed edges

### Temporal
- `erinys_reinforce` — Boost observation strength
- `erinys_supersede` — Version an observation
- `erinys_timeline` — Query as-of timestamp
- `erinys_conflict_check` — Detect contradictions

### Dream Cycle
- `erinys_collide` — Collide two observations via LLM
- `erinys_dream` — Batch collision cycle

### Distillation
- `erinys_distill` — 3-granularity abstraction (concrete → abstract → meta)

### Batch & Eval
- `erinys_batch_save` — Bulk save with auto-linking
- `erinys_eval` — LOCOMO-inspired quality metrics

### Session
- `erinys_session_start` — Start session
- `erinys_session_end` — End session with summary
- `erinys_session_summary` — Save structured summary


## アーキテクチャ

```
┌──────────────────────────┐
│     FastMCP Server       │  25ツール、統一レスポンス形式
├──────────────────────────┤
│  search.py  │ graph.py   │  RRFハイブリッド │ 型付きエッジ
│  decay.py   │ session.py │  エビングハウス  │ ライフサイクル
│  temporal.py│collider.py │  バージョニング  │ 記憶の交差授粉
│  distill.py │ db.py      │  3段階蒸留      │ SQLite + vec
├──────────────────────────┤
│  embedding.py            │  fastembed (BAAI/bge-small-en-v1.5)
├──────────────────────────┤
│  SQLite + FTS5 + vec0    │  ローカル完結、ランタイムにネットワーク不要
└──────────────────────────┘
```

## ロードマップ

- [x] Save時の自動蒸留 — `erinys_save`のたびに3段階蒸留を自動実行
- [ ] Dream Daemon — Dream Cycleをバックグラウンドで自動実行（現在は手動トリガーのみ）
- [ ] 自動Prune — DB容量閾値超過時に減衰メモを自動刈り取り
- [ ] cron対応CLI — `erinys dream --max 10` で夜間バッチ実行
- [ ] PyPIパッケージ — `pip install erinys-memory`
- [ ] マルチエージェント — エージェントごとのスコープ分離
- [ ] LongMemEval `_m` split 評価
- [ ] GitHub Releaseタグ + CIバッジ

## ライセンス

MIT
