# 生产级 AI Agent 长期记忆 — 行业主流方案

## 行业共识：四层记忆架构

2025 年以来，行业在长期记忆上已经形成了**四层分类共识**（模仿认知科学）：

| 层 | 类型 | 内容 | 生命周期 |
|---|---|---|---|
| L1 | **工作记忆** | 当前对话的上下文窗口 | 会话内 |
| L2 | **短期/情景记忆** | 近期对话摘要、事件日志 | 数小时 ~ 数天 |
| L3 | **语义记忆** | 提炼出的事实、偏好、知识 | 持久（可衰减） |
| L4 | **程序记忆** | 学到的行为规则、约束、SOP | 持久 |

> **sundynix-agentix 当前只覆盖了 L1（context window）+ L2（history）+ L3 的最简形态（KV profile）。**

---

## 四大主流方案详解

### 1️⃣ OpenAI ChatGPT — "Dreaming" 机制

**思路**：不靠向量检索，靠**后台异步合成摘要**。

```
用户对话 → 结束后异步触发 "Dreaming"
    ↓
分析历史 → 合成/更新 Memory Summary（结构化事实列表）
    ↓
下次对话 → 整个 Memory Summary 注入 context window
```

**架构特点**：
- **存储**：结构化事实列表（非向量），存为 user-scoped 文档
- **写入**：后台异步 "Dreaming" 进程，周期性回顾历史、合成偏好
- **读取**：**全量注入** context window（不做检索，依赖结构化摘要足够紧凑）
- **治理**：自动失效旧记忆（"traveling to Singapore" → "went to Singapore"），用户可查看/编辑/删除
- **不用 RAG**：刻意避免向量检索的延迟和复杂度，用轻量摘要替代

> [!NOTE]
> sundynix-agentix 的方案与 ChatGPT 早期版本（2024.4 Saved Memories）非常接近——都是**逐轮 LLM 抽取 + 全量注入**。
> ChatGPT 后来演进到 Dreaming 是因为发现逐轮抽取会导致"记忆腐烂"（noisy/stale），需要**周期性整理合并**。

---

### 2️⃣ Mem0 — 行业最主流的记忆中间件

**思路**：三阶段流水线 + 混合存储。被认为是当前**事实上的行业标准**。

```
对话消息 → Extract → Consolidate → Store
                                        ↓
                            ┌───────────┴───────────┐
                        Vector Store          Graph Store (Mem0g)
                        (语义检索)             (关系推理)
                                        ↓
查询时 → Retrieve (语义相似 + 图谱关联) → 注入 prompt
```

**三阶段流水线**：

| 阶段 | 做什么 | 关键区别 |
|---|---|---|
| **Extract** | LLM 从对话中抽取结构化事实和关系 | 类似 sundynix 的 `extractMemory` |
| **Consolidate** | LLM 将新事实与已有记忆**对账**，输出 ADD / UPDATE / DELETE / NOOP | ⚠️ sundynix 缺失这一层 |
| **Store** | 分别写入 Vector Store + Graph Store + KV Store | sundynix 只有 KV |

**Consolidate 是核心差异**：

```
已有记忆: "用户喜欢 Vue"
新抽取:   "用户说最近在学 React，打算迁移"

Mem0 Consolidate → UPDATE: "用户从 Vue 迁移到 React"
sundynix        → 如果 key 不同 → 两条并存（"前端框架:Vue" + "技术学习:React"）
                  如果 key 恰好相同 → 覆盖（但依赖 LLM 用同一个 key，不可控）
```

**检索方式**：
- **不是全量注入**，而是用当前 query embedding 做 top-K 语义检索
- 同时走 Graph Store 做关系关联检索（"用户在 X 公司" → 关联出 "X 公司用 Go 技术栈"）
- 大幅减少 token 浪费

---

### 3️⃣ Letta（原 MemGPT）— 操作系统式自管理记忆

**思路**：Agent 像操作系统一样**自己管理**自己的内存层级。

```
┌─────────────────────────────────────────┐
│  Context Window ("RAM")                  │
│  ┌─────────┐  ┌──────────┐  ┌─────┐    │
│  │ System   │  │ Recall   │  │User │    │
│  │ Prompt   │  │ Memory   │  │Msg  │    │
│  │(可编辑)  │  │(检索结果) │  │     │    │
│  └─────────┘  └──────────┘  └─────┘    │
└─────────────┬───────────────────────────┘
              │ Agent 主动调用内存管理工具
              ↓
┌─────────────────────────────────────────┐
│  Archival Memory ("Disk")  — 向量存储    │
│  Agent 可以 search / insert / delete     │
└─────────────────────────────────────────┘
```

**关键设计**：
- Agent 自己决定什么时候存记忆（不是外部异步抽取）
- Agent 自己决定什么时候查记忆（通过 tool call）
- System prompt 是**可编辑的**，Agent 可以修改自己的"人格"和"核心记忆"
- Archival Memory 是无限的向量存储，Agent 按需检索

**与 sundynix 的对比**：sundynix 是外部驱动（orchestrator 异步抽取），Letta 是 Agent 自驱动。

---

### 4️⃣ Zep / Graphiti — 双时态知识图谱

**思路**：面向**时间敏感**场景，用图谱追踪事实的时间演变。

```
对话/文档 → Graphiti 引擎 → 三层子图：
    ├─ Episode 子图：原始事件（无损保留，带时间戳）
    ├─ Semantic Entity 子图：提取的实体和关系（语义检索）
    └─ Community 子图：实体聚类（高层概览）
```

**双时态模型（Bi-temporal）**：

每条事实记录两个时间维度：

| 维度 | 含义 | 示例 |
|---|---|---|
| **Valid Time** | 事实在现实中为真的时间 | "2024-01 ~ 2025-03 用户在 A 公司" |
| **Ingestion Time** | 系统何时发现这个事实 | "2025-04 从对话中得知" |

```
2024-01: 用户说 "我在 A 公司做后端"
2025-03: 用户说 "我上个月从 A 离职了"

Zep 存储：
  (用户) -[就职于]-> (A公司)  valid: 2024-01 ~ 2025-02, invalidated_at: 2025-03
  (用户) -[离职于]-> (A公司)  valid: 2025-03 ~

普通 KV 存储：
  公司: A公司  →  被覆盖为空，或两条矛盾并存
```

- 可以回答"用户去年这个时候在做什么？"
- 旧事实**失效但不删除**，保留完整时间线
- 企业级场景（合规、审计、保单变更）的首选

---

## 对比总览

| 维度 | sundynix 现状 | ChatGPT | Mem0 | Letta | Zep |
|---|---|---|---|---|---|
| **存储** | PG 扁平 KV | 结构化文档 | Vector + Graph + KV | Vector (Archival) | 双时态知识图谱 |
| **抽取** | 逐轮 LLM 抽取 | 后台周期 Dreaming | Extract 阶段 | Agent 自主存储 | Graphiti 引擎 |
| **去重/合并** | exact key match | Dreaming 合成 | **Consolidate (ADD/UPDATE/DELETE)** | Agent 自管理 | 双时态失效 |
| **检索** | **全量注入** | 全量注入(紧凑摘要) | **语义 top-K + 图谱关联** | Agent 主动搜索 | 语义 + 时间 + 图谱 |
| **时间维度** | ❌ | 有(自动过时) | ❌ | ❌ | ✅ 双时态 |
| **遗忘** | ❌ | ✅ 自动 | ✅ DELETE 操作 | ✅ Agent 决定 | ✅ 失效不删 |
| **用户控制** | ❌ | ✅ 查看/编辑/删除 | 有 API | 有 API | 有 API |
| **复杂度** | ★☆☆☆☆ | ★★☆☆☆ | ★★★☆☆ | ★★★★☆ | ★★★★★ |

---

## sundynix-agentix 的差距与改造路线

### 与行业标准（Mem0 模式）的 3 个关键差距

```mermaid
flowchart LR
    A["1. 缺 Consolidate 层<br/>无法 ADD/UPDATE/DELETE<br/>只有盲目 upsert"] --> D["记忆腐烂<br/>冗余/过时/矛盾"]
    B["2. 全量注入<br/>不做相关性检索"] --> E["token 浪费<br/>噪声干扰"]
    C["3. 无时间/置信度<br/>维度"] --> F["无法遗忘<br/>无法区分强弱偏好"]
```

### 建议改造路线（利用已有基础设施）

项目已有 **Milvus + Neo4j + Postgres**，完全可以不引入新依赖，渐进实现 Mem0 级别的能力：

#### Phase 1 — 加 Consolidate 层（改 `memory_extract.go`，约 50 行）

把现在的 "抽取 → 盲目 upsert" 改为 "抽取 → LLM 对账 → 决策执行"：

```go
// 现在的流程：
fresh := filterNewPrefs(parsePrefs(txt), existing)  // exact match 去重
for _, p := range fresh {
    o.upsertMemory(ctx, uid, p.Key, p.Value)  // 盲 upsert
}

// 改为 Mem0 式 Consolidate：
// 1. 把新抽取 + 已有画像一起交给 LLM
// 2. LLM 输出操作列表：[{op:"ADD/UPDATE/DELETE", key, value, reason}]
// 3. 按操作执行
```

#### Phase 2 — 按需检索代替全量注入（复用 Milvus，改 `compile.go`）

```go
// 现在 (compile.go:27-31)：全量注入
if rc.Profile != "" {
    sys.WriteString("\n\n关于当前用户的已知信息：\n")
    sys.WriteString(rc.Profile)  // 全部偏好
}

// 改为：memory_get 传入 query，Milvus 语义检索 top-5 相关偏好
// memory store 存偏好时同时 embedding → Milvus memory collection
// 读取时用 query embedding → top-K → 只注入相关的
```

#### Phase 3 — 图谱记忆（复用 Neo4j）

```
memory_upsert 时同时写入 Neo4j：
  (User:uid) -[偏好 {key, since, confidence}]-> (Value:value)

检索时可以做关系推理：
  "用户的公司" → "公司的技术栈" → 关联出相关偏好
```

> [!TIP]
> **最务实的改造优先级**：Phase 1（Consolidate）> Phase 2（语义检索）> Phase 3（图谱）。
> Phase 1 改动最小（只改 `memory_extract.go` 的抽取 prompt 和处理逻辑），
> 但效果最显著——直接解决记忆腐烂这个最大的 Day-2 问题。

---

# 落地方案（修正版 · 实施计划）

> 在上面分析基础上做了三处修正：**① Consolidate 改异步攒批（不逐轮，省成本）；② DELETE 用 soft-delete（失效不物删）；③ Phase 2 语义检索暂缓（当前画像小，全量注入够用）**。并把读路径目标定为 Generative Agents 检索打分公式。

## 设计四原则

1. **对账而非盲写** —— 引入 Consolidate（ADD / UPDATE / DELETE / NOOP），解决 exact-key 去重不可控导致的记忆腐烂。
2. **异步攒批而非逐轮** —— 每 N 轮跑一次"抽取+对账"，一次 LLM 调用，省成本，对齐 ChatGPT 周期整理。
3. **失效不物删** —— DELETE = GORM 软删（`BaseModel.DeletedAt` 已具备，零成本），白拿审计/可恢复。
4. **按当下规模右尺寸** —— 小画像继续全量注入；语义检索/图谱留接口、暂不做。

## 数据模型（扩 mcp-go 的 `Profile`）

```go
type Profile struct {
    BaseModel              // 已含 id/created/updated/deleted_at(软删)
    UserID     string
    Key        string      // (user_id,key) 唯一
    Value      string
    Importance float64     // 1~10，consolidate 时 LLM 打分（poignancy）→ 读路径权重
    Confidence float64     // 0~1 信念强度（可选，强弱偏好）
    Source     string      // "user"(显式) / "extracted"(推断)
    LastSeenAt time.Time   // 最近被印证时间 → Recency 衰减依据
}
```

## 写路径：异步攒批 Consolidate（核心）

```
每轮结束 → 只 append history（已有，便宜）
            ↓ 每 N 轮（用 history 长度判断，无需新基建）
        Consolidate（1 次 LLM 调用，extract + reconcile 合一，Mem0 式）：
          输入 = 近 K 轮对话 + 当前 active 画像(带 key/importance)
          输出 = [{op: ADD|UPDATE|DELETE|NOOP, key, value, importance, reason}]
            ↓ 按 op 执行
          ADD/UPDATE → memory_upsert(带 importance, last_seen=now)
          DELETE     → memory_delete（软删）
          NOOP       → bump last_seen（印证即强化）
```

- 决策（LLM 对账）在 dispatcher；落库在 mcp-go 工具（新增 `memory_delete` 软删 + `memory_upsert` 收 importance）。
- 触发：`memorize` 里 append 后看 `history_get` 长度，跨 N 轮才跑。

## 读路径目标 ★ Generative Agents 检索打分公式

最终读路径按此公式排序、取 top-K（论文 Park et al. 2023）：

```
Score = w1·Relevance + w2·Recency + w3·Importance
```

| 项 | 取值 | 本项目映射 | 何时上 |
|---|---|---|---|
| **Importance** | 写入时 LLM 打 1~10（存下，不在检索时算）| `Profile.Importance`，consolidate 顺手打分 | **P1 就存** |
| **Recency** | `decay^(距 last_seen)` 指数衰减 | `LastSeenAt` + 衰减 | **P2** |
| **Relevance** | query×memory 余弦相似 | 需 embedding → Milvus | **P3（画像大才上）** |

- 三项各 min-max 归一到 [0,1]，加权求和；**w1/w2/w3 设为可配**。
- 现在画像小、全量注入：先用 **Recency + Importance** 两项排序/截断/遗忘（无向量、便宜）；画像变大后补 Relevance 凑齐三项。
- `memory_get(user_id, query?)` 签名预留 `query`，将来接 Milvus 不破坏调用方。

## 分阶段

| 阶段 | 内容 | 解决 |
|---|---|---|
| **P1** | 异步攒批 Consolidate（ADD/UPDATE/DELETE/NOOP）+ soft-delete + Profile 加 Importance/LastSeenAt（consolidate 时 LLM 打 importance 分）| 记忆腐烂 + 成本 |
| **P2** | 读路径：按 Score(Recency+Importance) 排序 + 衰减遗忘 + 截断 top-N；桌面端记忆面板可看/改/删 | 噪声 + 遗忘 + 用户控制 |
| **P3（暂不做）** | 补 Relevance：memory 存时 embedding→Milvus，读时 query top-K，凑齐三项公式；Neo4j 图谱记忆 | 大规模 / 关系推理 |

**故意不做**：双时态完整模型（soft-delete + last_seen 已够）、Letta 式 Agent 自管理（与现有外部编排架构不合）。

## P1 改动点（下午动手）

- `mcp-go/internal/memory/store.go`：`Profile` 加 `Importance/LastSeenAt`；`Upsert` 收 importance + 置 last_seen；新增 `Delete`（软删）。
- `mcp-go/internal/mcp/gateway.go`：新增 `memory_delete` 工具；`memory_upsert` 透传 importance。
- `dispatcher/internal/eino/memory_extract.go`：`extractMemory` → `consolidateMemory`：合并 extract+reconcile 为一次 LLM 调用，输出 op 列表（含 importance 打分），按 op 调 upsert/delete；触发改为每 N 轮（看 history 长度）。
- 纯逻辑（op 解析 / 应用决策）可单测。
