# Memory System

Agenvoy 的 memory 層針對 conversation memory 有三個層級，另加一個跨 session 的 error memory 層。

| 層級 | 後端 | 範圍 |
|---|---|---|
| 1. Context window（16 則訊息 + summary） | `history.json` + `summary.json` | session |
| 2. 語意搜尋（近期） | ToriiDB `DBSessionHist`（vector） | session |
| 3. 全文封存（全部歷史） | 透過 go-sqlkit 的 SQLite FTS5 | session |
| Error memory | ToriiDB `error_memory`（90 天 TTL） | 跨 session |

操作者筆記（SQLite `note` + `note_fts5`、`find_note` 工具、`/v1/note*`）已於 v1.0.23 移除，不再有全域筆記層。

## 三層 conversation memory

### 1. Context window（`limits.max_history_messages`，預設 16）

每個 session 完整保留最近 N 則訊息並餵入 LLM context window。更舊的內容仍留在 `history.json`，但不送給 LLM。

滾動式 summary（`summary.json`）將較舊的對話濃縮，讓較舊 context 得以在 N 則訊息窗口之外存續。v1.3.7 起它以 `Prior summary` JSON 區塊放進 turn context——也就是緊接近期歷史、位於使用者輸入之前的那一則 `user` 訊息；v1.3.7 以前它是夾在歷史與 turn context 之間的獨立 `user` 訊息，附帶一大段固定說明（移除於 v1.3.7）。設定了 decision model 時，每個 turn 只保留與這次請求相關的 `key_decisions`、`past_discussions` 與 `current_discussion` 條目（語言、格式或工作流程這類通用規則一律算相關）；沒有 decision model 或判斷失敗時全部保留，沒有任何相關條目則整份 summary 不放。使用方式——遵守 `key_decisions`、其餘當背景、被問到「之前談過什麼」時以文字從中回答——寫在 system prompt 的 `Prior Conversation Context` 區塊。工具歷史壓縮不再丟掉 summary。

**增量游標：** `.summary_cursor`（per-session；舊的 `summary.meta.json` 在 v1.1.1 以前於啟動時遷移，v1.1.2 起不再讀取）記錄最後一則已摘要訊息的時間（格式 `YYYY-MM-DD HH:MM:SS`，取自紀錄的 `SendAt`）。系統 cron（`*/15 * * * *`）挑出所有非 `temp-` 開頭、且 `history.json` 比 `summary.json` 新的 session（mtime gate），對其執行 `Generate`：

1. 已有 summary 但沒有游標的 session，只把游標設為最新訊息時間，不呼叫 LLM
2. v1.3.1 起若設定了 decision model，比游標新（或沒有時間戳）的訊息會先分組成一段段對話——一則使用者訊息加上其後的回覆——由 decision model 移除信心至少 0.7 判定為噪音的部分（問候、致謝、重複同一請求、沒有結果的失敗或取消輪次），再從前一份 summary 移除被這些對話推翻或取代的 `key_decisions` 與 `past_discussions` 條目。判斷失敗則全部保留；若沒有剩下任何對話，直接儲存精簡後的 summary 與游標，不呼叫 LLM
3. 比游標新（或沒有時間戳）且仍保留的訊息依 `SUMMARY_RUNES`（32,000 字元）切成 chunk；chunk 不會停在使用者訊息上
4. 每個 chunk 執行**一次** LLM call，system prompt 已含前一份 summary（`{{.Summary}}`），合併在生成時完成，沒有獨立的合併步驟
5. 每個 chunk 成功後儲存 `summary.json` 與游標；某個 chunk 失敗或無法解析就中止，不對後續 chunk 計費，下一次 cron tick 重試

v1.3.1 起 summary prompt 只把之後都適用的規則記為 `key_decisions`（最多 8 條），v1.3.7 起每條不論輸出語言一律以英文 `Do: `（同意）或 `Don't: `（拒絕）開頭——`要：` / `不要：`、`する：` / `しない：` 這類隨語言變動的標記已移除於 v1.3.7，既有條目會在下次合併時改寫——各欄位內文則以設定的 `reply_lang` 撰寫；設為 `auto` 時跟隨對話語言，中文一律為台灣繁體中文。

### 2. 語意搜尋 —— ToriiDB（近期對話）

`chat_history` 工具以 `mode=search` 搭配 `match=semantic` 透過 ToriiDB `db.VSearch` 執行 vector 相似度搜尋。每次命中觸發 context window 擴展：命中前 2 則 + 命中後 1 則。

ToriiDB 條目於 `history.json` compaction 期間清理 —— 早於 compact cutoff 的條目被移除，讓 ToriiDB 專注於近期對話。較舊資料存於 SQLite（層級 3）。

### 3. 全文封存 —— SQLite FTS5（全部歷史）

每則寫入 `history.json` 的訊息都會透過 go-sqlkit **雙寫**至 SQLite（`~/.config/agenvoy/.store/history.db`）。即使 `history.json` 已 compact，SQLite 始終持有完整對話歷史。

`chat_history` 工具以 `mode=search` 搭配 `match=keyword` 對 SQLite 封存執行 FTS5 全文搜尋 + 對近期條目執行 ToriiDB 子字串比對，並合併結果。

**Compaction：** 當 `history.json` 超過 `max_history_bytes`（自 v1.0.12 起預設 4 MiB，即 1 MiB 文件上限的四倍）時，最舊訊息在完整的 user+assistant pair 邊界上裁剪至 80%。cutoff timestamp 記錄於 SQLite `message_meta.start_at`，使 keyword 搜尋排除 `history.json` 已存在的條目（避免重複）。早於 cutoff 的 ToriiDB 條目亦被移除。

**Backfill：** 首次遇到（SQLite 對某 session 無資料但 `history.json` 有內容）時，整份既有歷史 backfill 進 SQLite。

**Timestamps：** 以 UTC unix 奈秒儲存。訊息內容中的 timestamp 透過 `time.ParseInLocation`（本地時區）解析並轉為 UTC 儲存。搜尋查詢使用 `time.Now().UnixNano()`（已為 UTC）。

## 儲存佈局

| 儲存 | 內容 | 生命週期 |
|---|---|---|
| `history.json` | 近期訊息（hot，LLM 每 turn 讀取） | 4 MiB 時自動 compact |
| ToriiDB `DBSessionHist` | 帶 embeddings 的近期訊息 | compact 時清理（移除早於 cutoff 的條目） |
| SQLite `messages` | 曾寫入的所有訊息（雙寫） | reset / remove-session 時清除 |
| SQLite `message_meta` | `start_at` —— compact cutoff timestamp | reset / remove-session 時清除 |
| `summary.json` | 滾動式 summary blob | reset 後存續 |
| ToriiDB `error_memory` | 已解決的 tool 錯誤記錄與其修正方式，由 `write_error` 寫入（`error_history(mode=write)` 移除於 v1.3.11） | 90 天 TTL（命中時續期） |

## Migration 注意

Session 與 error memory 過去存於 per-session JSON 檔，現已改置於 `~/.config/agenvoy/.store/` 底下的嵌入式 store——ToriiDB（`db_0` 工具快取、`db_1` 對話向量、`db_2` 錯誤記憶、`db_3` online marker），`history.db` 承載 session 設定、訊息封存、action 歷史、檔案歷史與用量。筆記於 v0.35.3 自 ToriiDB 移入 SQLite `note` 表，並於 v1.0.23 連同 daemon 啟動時的筆記 migration 一併移除；`state` 表於 v0.35.3 移除。`bot.json` / `bot.md` / `config.json` / `status.json` / `usage.log` / `summary.meta.json` 在 v1.1.1 以前於 daemon 啟動時遷移進來；v1.1.2 移除這些遷移，以及 v1.0 以前資料庫的 SQLite 欄位升級，這麼舊的安裝請先經過 v1.1.1。勿重新引入 JSON 路徑。
