# 记忆模块实施计划（面试优先版 -> 产品可用版）

## 1. 文档目标

1. 在 3 天内交付一个“可演示、可讲清楚、可继续演进”的记忆系统 MVP。
2. 兼容当前单体工程，不引入高风险拆分，不破坏现有聊天主链路。
3. 复用现有 Outbox 异步基础设施，避免重复造轮子。
4. 形成可直接用于面试讲述的架构故事线、指标体系与演示脚本。
5. 在不增加过度复杂度的前提下，吸收 Mem0 中已被验证的关键机制（抽取、决策、检索、降级、防幻觉）。

## 2. 背景与约束

1. 当前系统是单体 Go 项目，已有稳定的 `Outbox + Kafka + 消费事务` 通路。
2. 当前项目定位先是日程助手，长期演进为陪伴型助手。
3. 短期目标是快速做出“真的可用”的记忆能力，不追求一次做成完整通用平台。
4. 风险约束：
   - 不能让重型 LLM 处理阻塞聊天实时响应。
   - 不能在 Outbox 消费主循环里堆重计算，避免拖垮其他事件消费。
   - 不能牺牲数据一致性与可审计性。

## 3. 总体方案

### 3.1 核心思路

采用“同步快路径 + 异步慢路径”：

1. 同步快路径：回复前快速读取可用记忆（以 MySQL 结构化事实为主），保证“下一轮能用”。
2. 异步慢路径：通过 Outbox 触发记忆抽取任务，执行去重、冲突消解、打分、向量化等重操作。
3. 读写解耦：写路径确保可靠入队，读路径优先稳定可控，再做语义增强。

### 3.2 存储职责分层

1. MySQL：事实主库（偏好、约束、任务上下文、TTL、置信度、敏感级别、来源）。
2. Milvus：语义召回（同义表达匹配、模糊语义联想）。
3. Redis（可选）：热数据缓存（后续优化，不作为 MVP 必选项）。

### 3.3 编排层职责

`Memory Orchestrator` 负责两条链路：

1. 写入链路：候选抽取 -> 去重/冲突 -> 打分 -> 分流落库（MySQL/Milvus）。
2. 读取链路：硬约束优先 -> 语义召回补充 -> 重排 -> 门控 -> 注入上下文。

### 3.4 借鉴 Mem0 的关键机制（已裁剪版）

1. 双阶段去重决策：先向量召回候选旧记忆，再由 LLM 决策 `ADD/UPDATE/DELETE/NONE`，而不是只靠相似度阈值硬判。
2. UUID 映射防幻觉：把真实 `memory_id` 映射成临时整数给 LLM，回收结果时再反查，防止模型编造不存在 ID。
3. 结构化输出刚性约束：抽取与决策都用 JSON 结构，失败时走 `extract_json -> normalize_facts` 容错链，不让解析失败直接污染主流程。
4. 动作分型嵌入：嵌入接口显式传入 `memory_action`（`add/search/update`），为后续差异化 embedding 策略预留接口。
5. 检索后处理标准化：`threshold 过滤 -> 可选 reranker -> 统一降级`，当重排器异常时保留向量原始排序并打告警日志。
6. 多维隔离语义：统一采用 `user_id + agent_id + run_id` 三维过滤；在本项目映射为 `user_id + assistant_id + conversation_id`。

### 3.5 本项目明确不做（本轮）

1. 不做图记忆（Graph Memory）落地实现，仅预留扩展点，避免 3 天范围失控。
2. 不做多 Provider 工厂体系，只保留单 Provider 可替换接口，后续再扩展。
3. 不做独立 server 化记忆服务，先在单体内完成闭环与指标验证。

## 4. 3 天执行计划（可直接照着做）

## Day 1：把“可写入”打通（可靠入队 + 可追踪）

### 目标

1. 记忆任务能稳定从聊天主链路发出。
2. 能看到任务从 `pending` 到 `success/failed` 的状态流转。
3. 保证失败可重试、可追踪、可补偿。

### 任务清单

1. 新增文档与目录占位：
   - `backend/memory/README.md`（模块说明）
   - `backend/memory/service/`（门面）
   - `backend/memory/model/`（DTO 与状态）
   - `backend/memory/repo/`（数据访问）
   - `backend/memory/orchestrator/`（编排）
   - `backend/memory/worker/`（异步执行）
2. 新增 MySQL 表（建议先手写 SQL + DAO）：
   - `memory_items`
   - `memory_jobs`
   - `memory_audit_logs`
   - `memory_user_settings`
3. 新增配置对象（`memory config`）：
   - 抽取 prompt、更新决策 prompt、阈值、是否启用 reranker、LLM 温度参数。
   - 默认采用低随机参数（`temperature/top_p` 低值）提高可复现性。
4. 新增 Outbox 事件：
   - `memory.extract.requested`（v1）
5. 在聊天后置持久化环节发布事件：
   - 仅传轻量字段，避免超大 payload。
6. 新增消费处理器：
   - 只做任务入库，不做重型 LLM 调用。
7. 新增解析与标准化工具：
   - `extract_json()`：从模型输出中抽取 JSON（兼容代码块包裹）。
   - `normalize_facts()`：去重、去空、长度校验、非法项过滤。
8. 新增决策状态机定义：
   - `ADD/UPDATE/DELETE/NONE` 的合法状态与动作映射。
9. 启动期接线：
   - 在 `backend/cmd/start.go` 注册记忆事件处理器。

### Day 1 验收标准

1. 一次聊天后，Outbox 中能看到 `memory.extract.requested` 事件。
2. 事件消费后，`memory_jobs` 生成记录。
3. 人工触发 worker 可完成一次任务状态推进（哪怕先是 mock 抽取）。

## Day 2：把“可读取可注入”打通（先 MySQL 后向量）

### 目标

1. 记忆可在回复前被检索并注入上下文。
2. 能避免明显的“尬提”与无关提及。
3. 提供最小用户可控能力（查看/删除/关闭）。

### 任务清单

1. 实现 `MemoryReadService`：
   - 按用户与会话上下文读取记忆。
   - 优先结构化硬约束（时间偏好、排程禁忌、显式偏好）。
2. 实现 `MemoryInjector`：
   - Top-K 记忆选择。
   - token 预算截断。
   - 注入模板统一化。
3. 实现门控逻辑：
   - 相关性阈值。
   - 置信度阈值。
   - 时间衰减权重。
   - 敏感级别检查。
4. 增加“阈值 + 可选重排 + 降级”链路：
   - 阈值过滤作为第一道过滤。
   - `reranker` 失败时自动降级为原排序并记录原因码。
5. 新增最小管理接口：
   - `GET /api/v1/memory/items`
   - `DELETE /api/v1/memory/items/:id`
   - `POST /api/v1/memory/settings`（开关）
6. 完成首版日志埋点：
   - 检索命中数、注入条数、门控丢弃原因。
   - 决策分布（ADD/UPDATE/DELETE/NONE 占比）。

### Day 2 验收标准

1. 给出偏好后，下一轮排程请求能利用该偏好。
2. 无关话题不会频繁硬提旧记忆。
3. 用户可删除指定记忆，删除后不再注入。

## Day 3：把“可讲清楚”与“可评估”补齐（面试可答）

### 目标

1. 输出完整可讲架构，说明设计取舍。
2. 增加可量化指标，证明记忆“有用”而不是“看起来有”。
3. 可选接入 Milvus（若环境未就绪，先保留接口 + mock）。

### 任务清单

1. 实现/预留向量接口：
   - `VectorStore.Upsert()`
   - `VectorStore.Search()`
   - `VectorStore.Delete()`
   - `VectorStore.Get()`（为 UPDATE/DELETE 决策回查旧值）
2. 对接 Milvus（可选）：
   - collection 初始化。
   - 向量 + 元数据过滤检索。
3. 指标体系落地：
   - 记忆命中率（retrieved/useful）
   - 错误提及率（wrong mention）
   - 用户纠正率（user correction）
   - 回复延迟影响（P50/P95）
4. 准备演示脚本与面试问答稿：
   - 5 分钟架构说明。
   - 3 个典型失败案例及兜底策略。
   - 未来迭代路线。
5. 输出“借鉴 Mem0 但本地化裁剪”的对比说明：
   - 借鉴了什么。
   - 为什么暂时不做图记忆与多 Provider 工厂。

### Day 3 验收标准

1. 能现场演示“记住偏好 -> 下轮生效 -> 删除后失效”。
2. 能答清楚“为什么不是纯同步/纯异步”。
3. 能答清楚“为什么 MySQL + Milvus 双存储”。

## 5. 数据模型设计（首版）

## 5.1 `memory_items`（长期事实记忆）

用途：保存对业务有约束价值的可注入记忆。

关键字段建议：

1. `id` bigint PK
2. `user_id` bigint（必填）
3. `conversation_id` varchar(64)（可空，表示全局用户记忆）
4. `assistant_id` varchar(64)（可空，区分不同助手人格/技能域）
5. `run_id` varchar(64)（可空，会话级隔离）
6. `memory_type` varchar(32)
   - `preference`（偏好）
   - `constraint`（硬约束）
   - `fact`（事实）
   - `todo_hint`（近期提醒线索）
7. `title` varchar(128)
8. `content` text
9. `normalized_content` text（去噪后）
10. `content_hash` varchar(64)（幂等去重）
11. `confidence` decimal(5,4)（0~1）
12. `importance` decimal(5,4)（0~1）
13. `sensitivity_level` tinyint
    - 0 普通
    - 1 中敏
    - 2 高敏
14. `source_message_id` bigint
15. `source_event_id` varchar(64)
16. `is_explicit` tinyint(1)（是否用户明确要求记住）
17. `status` varchar(16)
    - `active`
    - `archived`
    - `deleted`
18. `ttl_at` datetime（到期时间）
19. `last_access_at` datetime
20. `created_at` datetime
21. `updated_at` datetime
22. `vector_status` varchar(16)（`pending/synced/failed`）
23. `vector_id` varchar(128)（向量库主键映射）

索引建议：

1. `(user_id, status, memory_type, updated_at desc)`
2. `(user_id, conversation_id, status, updated_at desc)`
3. `(source_message_id)`（排查链路）
4. `(ttl_at)`（过期清理）
5. `(user_id, assistant_id, run_id, status, updated_at desc)`
6. `(user_id, memory_type, content_hash)`（幂等去重）

## 5.2 `memory_jobs`（异步任务队列表）

用途：承接 Outbox 消费后的待处理任务，解耦重计算。

关键字段建议：

1. `id` bigint PK
2. `user_id` bigint
3. `conversation_id` varchar(64)
4. `source_message_id` bigint
5. `source_event_id` varchar(64)
6. `job_type` varchar(32)
   - `extract`
   - `embed`
   - `reconcile`
7. `idempotency_key` varchar(128)
8. `payload_json` longtext
9. `status` varchar(16)
   - `pending`
   - `processing`
   - `success`
   - `failed`
   - `dead`
10. `retry_count` int
11. `max_retry` int
12. `next_retry_at` datetime
13. `last_error` varchar(2000)
14. `created_at` datetime
15. `updated_at` datetime

索引建议：

1. `(status, next_retry_at, id)`
2. `(user_id, created_at desc)`
3. `(source_event_id)`（幂等与追踪）
4. `(idempotency_key)`（消费防重）

## 5.3 `memory_audit_logs`（审计日志）

用途：回答“这条记忆是谁在什么条件下写的/改的/删的”。

关键字段建议：

1. `id` bigint PK
2. `memory_id` bigint
3. `user_id` bigint
4. `operation` varchar(32)
   - `create`
   - `update`
   - `archive`
   - `delete`
   - `restore`
5. `operator_type` varchar(16)
   - `system`
   - `user`
6. `reason` varchar(255)
7. `before_json` longtext
8. `after_json` longtext
9. `created_at` datetime

## 5.4 `memory_user_settings`（用户记忆开关）

用途：实现用户可控能力。

关键字段建议：

1. `user_id` bigint PK
2. `memory_enabled` tinyint(1)
3. `implicit_memory_enabled` tinyint(1)
4. `sensitive_memory_enabled` tinyint(1)
5. `updated_at` datetime

## 6. 事件与协议设计

## 6.1 事件类型

1. `memory.extract.requested`（v1）
2. 预留：
   - `memory.embed.requested`
   - `memory.cleanup.requested`

## 6.2 载荷字段（v1）

1. `user_id`
2. `conversation_id`
3. `assistant_id`
4. `run_id`
5. `source_message_id`
6. `source_role`
7. `source_text`
8. `occurred_at`
9. `trace_id`
10. `idempotency_key`

设计约束：

1. Payload 只放执行需要的最小字段。
2. 大文本允许截断并保留摘要，防止消息膨胀。
3. 必须包含幂等标识（如 `source_message_id + user_id`）。
4. 过滤维度必须完整（`user_id + assistant_id + run_id`），避免跨会话串记忆。

## 7. 写入流程详细设计

## 7.1 主流程

1. 聊天主链路完成并落历史消息。
2. 发布 `memory.extract.requested` 到 Outbox。
3. Outbox 消费处理器验证 payload。
4. 处理器创建或幂等更新 `memory_jobs`（仅任务入库）。
5. `memory/worker` 扫描 `pending` 任务并抢占为 `processing`。
6. Worker 调用 LLM 执行“候选事实抽取”（JSON 输出）。
7. 执行 `extract_json -> normalize_facts` 容错标准化链路。
8. 对每条候选事实做向量检索，召回 Top-K 旧记忆候选。
9. 对召回结果执行“临时整数 ID 映射”，再交给 LLM 决策 `ADD/UPDATE/DELETE/NONE`。
10. 根据决策执行写入动作：
    - `ADD`：新增 `memory_items` + 审计日志。
    - `UPDATE`：更新记录并保留历史旧值。
    - `DELETE`：软删除并记录删除原因。
    - `NONE`：不写入，仅记调试日志。
11. 按决策动作触发向量同步（支持 `vector_pending`）。
12. 成功后任务标记 `success`，失败按重试策略推进。

## 7.2 失败处理策略

1. Payload 非法：直接标记 dead，不重试。
2. LLM 短时失败：指数退避重试。
3. DB 写失败：重试，超过上限 dead。
4. 向量写失败：
   - MVP 策略：不阻塞事实写入，记录 `vector_pending` 状态。
   - 后续策略：补偿任务重建向量索引。

## 7.3 幂等策略

1. 幂等键：`user_id + source_message_id + memory_type + normalized_content_hash`
2. 同幂等键重复写入：更新 `updated_at`、提升访问热度，不新增重复条目。
3. 由 Outbox 重试导致的重复消费必须无副作用。
4. 对 UPDATE/DELETE 必须先校验目标 `memory_id` 是否存在且属于当前过滤域。

## 8. 读取流程详细设计

## 8.1 主流程

1. 接收用户新问题，先做意图分类（排程/闲聊/混合）。
2. 从 `memory_items` 拉取硬约束记忆（高优先级）。
3. 若 Milvus 可用，执行语义召回补充记忆候选。
4. 对候选执行重排：
   - 相关性分
   - 置信度分
   - 时间衰减分
   - 显式记忆加权
5. 执行门控：
   - 低相关丢弃
   - 高敏过滤
   - 过期过滤
6. 执行阈值过滤后可选 reranker；若 reranker 异常则自动降级使用原排序。
7. 按 token budget 选择最终注入条目。
8. 组装统一注入上下文，传给主模型生成回复。

## 8.2 重排评分（建议公式）

`final_score = 0.45 * relevance + 0.25 * confidence + 0.20 * recency + 0.10 * explicit_bonus`

说明：

1. 排程类场景可增加硬约束权重。
2. 闲聊类场景可提高语义相关权重。
3. 该公式为 MVP 默认值，后续可通过线上数据调参。

## 8.3 门控规则（MVP）

1. `final_score < 0.55` 不注入。
2. `sensitivity_level >= 2` 且用户未开启敏感记忆时不注入。
3. `ttl_at < now` 不注入。
4. 同主题最多注入 1~2 条，防止重复轰炸。

## 9. 对外接口（MVP）

## 9.1 用户接口

1. `GET /api/v1/memory/items`
   - 支持按类型、时间、状态过滤。
2. `DELETE /api/v1/memory/items/:id`
   - 软删除并写审计日志。
3. `POST /api/v1/memory/settings`
   - 修改记忆总开关、隐式记忆开关。

## 9.2 内部接口

1. `MemoryService.EnqueueExtractJob(ctx, payload)`
2. `MemoryService.RetrieveForPrompt(ctx, req)`
3. `MemoryService.UpsertMemoryItems(ctx, items)`
4. `MemoryService.DeleteMemory(ctx, userID, memoryID)`

## 10. 可观测性与指标

## 10.1 指标定义

1. `memory_job_success_rate`
2. `memory_job_retry_rate`
3. `memory_retrieval_hit_rate`
4. `memory_injection_count_avg`
5. `memory_wrong_mention_rate`
6. `memory_user_correction_rate`
7. `chat_p95_latency_delta_with_memory`
8. `memory_json_parse_fail_rate`
9. `memory_decision_distribution`（ADD/UPDATE/DELETE/NONE）
10. `reranker_fallback_rate`

## 10.2 日志与追踪

1. 每个任务写 `trace_id`，贯穿聊天请求 -> outbox -> memory_job -> memory_item。
2. 对门控丢弃记录原因码：
   - `LOW_SCORE`
   - `EXPIRED`
   - `SENSITIVE_BLOCKED`
   - `DUP_TOPIC`
3. 保证可以反查“为什么这次没有提某条记忆”。

## 11. 安全与隐私约束

1. 敏感信息默认不做隐式记忆（如健康、财务、证件等）。
2. 用户必须可删除历史记忆，删除后不再用于注入。
3. 记忆开关关闭后，仅保留必要系统数据，不再新增记忆条目。
4. 审计日志保留系统写入行为，便于风控与合规排查。

## 12. 测试策略

## 12.1 单元测试范围（实现阶段）

1. 候选抽取结果解析函数。
2. 冲突消解函数。
3. 重排评分函数。
4. 门控函数。
5. 幂等去重函数。
6. `extract_json` 容错解析函数。
7. `normalize_facts` 标准化函数。
8. UUID 映射与反查函数。
9. `ADD/UPDATE/DELETE/NONE` 决策结果校验函数。

## 12.2 集成测试范围（实现阶段）

1. 聊天后事件成功入 outbox。
2. Outbox 消费后任务成功入 `memory_jobs`。
3. Worker 成功写 `memory_items`。
4. 读取链路能在回复中注入预期记忆。

## 12.3 注意事项（遵循项目约束）

1. 若编写 Go 测试文件（`*_test.go`）做验证，任务完成后按项目约定移除测试文件。
2. 每次执行本地 `go test` 后清理项目根目录 `.gocache`。

## 13. 风险与回滚

## 13.1 主要风险

1. 记忆误提影响体验。
2. LLM 抽取不稳定导致脏记忆。
3. 向量检索误召回导致不相关注入。
4. 任务积压影响时效。

## 13.2 应对策略

1. 先严门控，宁可少提，不要乱提。
2. 保留“用户纠正”入口，纠正后提高冲突更新优先级。
3. 对召回做 metadata 过滤（近 30 天、类型限定）。
4. 监控任务积压长度，超阈值降级（停向量，仅结构化记忆）。

## 13.3 回滚方案

1. 配置开关 `memory.enabled=false` 可一键关闭记忆注入。
2. 保留写入链路但停读取链路，避免历史数据丢失。
3. 极端情况下停 worker，仅保留主链路聊天功能。

## 14. 面试表达模板（可直接复述）

1. “我们做的是同步快路径 + 异步慢路径。同步保证下轮可用，异步负责治理和质量。”
2. “结构化事实放 MySQL 保证可控可审计，语义联想放 Milvus 提高召回覆盖。”
3. “Outbox 保证事件可靠入队，Worker 解耦重计算，避免阻塞主链路。”
4. “借鉴 Mem0 的双阶段策略：先向量召回旧记忆，再让 LLM 决策 ADD/UPDATE/DELETE/NONE，兼顾召回率与准确率。”
5. “我们用 UUID 映射防止模型伪造 ID，并且用 JSON 容错链保证抽取稳定性。”
6. “我们用命中率、误提率、纠正率和 reranker 降级率验证记忆是否真的有价值。”

## 15. DoD（完成定义）

1. 代码层：
   - 记忆事件可发布、可消费、可重试。
   - 记忆可检索、可注入、可删除、可关闭。
2. 质量层：
   - 有基础指标与日志，支持问题排查。
   - 有失败兜底与降级路径。
3. 叙事层：
   - 3 分钟能讲清架构。
   - 5 分钟能演示端到端效果。
   - 能回答核心取舍与后续演进。

## 16. 本轮执行顺序建议

1. 先做 Day 1 的表结构与事件接线，不进入复杂抽取细节。
2. 再做 Day 2 的读取注入，优先 MySQL 结构化记忆。
3. 最后补 Day 3 的 Milvus 与指标，确保面试讲述闭环。

## 17. Mem0 借鉴清单与取舍结论（本轮新增）

### 17.1 直接借鉴

1. `ADD/UPDATE/DELETE/NONE` 统一决策状态机。
2. `threshold -> reranker(可选) -> fallback` 的检索后处理套路。
3. 三维过滤隔离（`user_id/agent_id/run_id`）的语义边界设计。
4. 历史追踪思路（本项目落在 `memory_audit_logs`）。
5. 低随机参数 + JSON 输出约束，提升可复现性。

### 17.2 延后借鉴

1. 图记忆（关系三元组与软删除）延后到 V2/V3。
2. 多 Provider 工厂体系延后到“需要跨云/跨模型”时再上。
3. 托管 API 平台化能力延后到单体稳定后再拆。

### 17.3 不照搬的原因

1. 当前目标是 3 天可演示 MVP，优先“稳定可讲”而非“能力最全”。
2. 项目已有 Outbox 可靠链路，先最大化复用，避免架构重复。
3. 日程助手是强约束场景，结构化事实主库优先级高于图谱表达能力。

---

本文件定位为“落地执行蓝图”。后续每完成一块能力，建议在本文件追加“已落地清单 + 待办差距”，持续收敛为真实实施记录。
