# RFC 0005: 适时触发多类型记忆与智能去重

## 背景

当前记忆系统在每个对话轮次结束后统一处理——调用 `build_memory_turn_result()` 构建单个 `TurnResult`，再通过 `run_micro_rumination()` 提取单个 Episode。存在以下问题：

1. **单轮只产出一个记忆**：长对话中多种能力/知识组合只浓缩为一个 Episode，丢失细节
2. **Entity/Decision 延迟到 Meso 反刍**：会话结束后才从 Episode 中提取实体和决策，不及时
3. **无去重**：相似或重复的操作会创建多个 Episode
4. **关联仅基于关键词重叠**：无跨类型关系（Episode→Entity、Decision→Episode 等）
5. **回忆参与度不足**：当前主要依赖主模型主动调用 `recall_memory`，Memory 没有在任务运行过程中根据操作需要持续判断“现有上下文是否足够”
6. **召回层级不清晰**：缺少“先低成本粗回忆，再按需深度混合检索”的运行时策略，容易在简单任务上浪费成本，复杂任务上又召回不充分

## 目标

- **混合触发**：工具执行后做轻量评估标记候选，轮次结束前统一提炼并去重
- **多类型记忆**：一轮对话能产生 Episode、Entity、Decision、Evidence 等多种类型
- **双重联动**：写入前检索相似记忆去重 + 写入后自动建立关系
- **避免重复**：相似则更新而非新建
- **渐进回忆**：程序运行时先用纯搜索引擎做低成本粗回忆，只有必要时再升级为混合检索和深度回忆
- **执行中重新回忆**：当当前上下文对即将执行的操作没有明显辅助效果时，允许 Memory 基于下一步意图重新召回，避免执行过程与历史经验脱节
- **Memory 深入参与交互**：Memory 不只在用户显式历史指代时被动召回，还要在任务规划、工具执行前后和上下文不足时参与判断

## 核心设计

### 1. MemoryCandidate — 工具执行后的轻量提示

工具执行完成后，纯规则评估是否值得记忆，生成 `MemoryCandidate`（无 LLM 调用）：

```rust
pub struct MemoryCandidate {
    pub tool_name: String,
    pub step_index: usize,
    pub hint: String,                              // 一句话描述为什么值得记忆
    pub suggested_kinds: Vec<MemoryCandidateKind>, // 建议的记忆类型
    pub file_path: Option<String>,
    pub url: Option<String>,
    pub result_summary: Option<String>,            // 工具结果摘要（max 240 字符）
    pub success: bool,
}

pub enum MemoryCandidateKind {
    Episode, Entity, Decision, Evidence, UserPreference,
}
```

候选评估规则：

| 工具 | 建议类型 | 触发条件 |
|------|---------|---------|
| `write_file` / `replace_in_file` | Episode + Evidence | 执行成功 |
| `search_code` | Entity | 结果非空 |
| `run_command` | Episode + Decision | 涉及构建/测试 |
| `generate_image` / `generate_video` | Evidence | 执行成功 |
| 其他 | Episode | summary 超过 20 字符 |

### 2. EnhancedTurnResult — 扩展的轮次结果

在原有 `TurnResult` 基础上增加候选列表和对话消息：

```rust
pub struct EnhancedTurnResult {
    // 原有字段保持不变
    pub session_id: String,
    pub turn_id: String,
    pub had_tool_calls: bool,
    pub user_input: String,
    pub summary: String,
    pub tool_calls: Vec<String>,
    pub artifacts: Vec<TurnArtifact>,
    pub workspace_id: Option<String>,
    // 新增
    pub memory_candidates: Vec<MemoryCandidate>,
    pub turn_messages: Vec<TurnMessage>,
}
```

### 3. 统一提炼管道（process_enhanced_micro）

轮次结束前执行：

```
EnhancedTurnResult + 累积的 MemoryCandidate
  → extract_multi_type_memories()     // LLM 或规则提取
    → 检查决策信号 → 内联提取 Decision
    → 检查实体信号 → 内联提取 Entity
    → 始终提取至少一个 Episode
  → write_extraction_with_dedup()      // 去重写入
    → 关键词重叠 ≥ 0.7 + 标题相似度 > 0.6 → 更新已有记忆
    → 否则新建
  → link_written_memories()            // 跨类型关联
    → Episode → Entity: BelongsTo
    → Decision → Episode: LearnedFrom
    → Episode → Episode: RelatedTo（已有逻辑）
  → update_session_injection()
```

### 4. API 变更（MemoryHandle）

```rust
// 新增
pub fn submit_memory_candidate(&self, candidate: MemoryCandidate);
pub fn submit_memory_candidate_blocking(&self, candidate: MemoryCandidate);
pub fn run_enhanced_micro_rumination(&self, turn_result: EnhancedTurnResult);

// 旧接口标记 deprecated
pub fn run_micro_rumination(&self, turn_result: TurnResult);
```

### 5. 调用流程

```
react/engine.rs 工具执行循环:
  工具执行完成 → evaluate_tool_result_for_memory() → MemoryCandidate
  → handle.submit_memory_candidate(candidate)  // fire-and-forget，不阻塞

core/mod.rs worker_loop_async 轮次结束:
  build_enhanced_memory_turn_result() → EnhancedTurnResult
  → handle.run_enhanced_micro_rumination(enhanced)  // 触发统一提炼

tiangong-memory actor:
  累积 candidates + EnhancedTurnResult
  → process_enhanced_micro()
  → 多类型提取 + 去重 + 跨类型关联 + 注入更新
```

### 6. RuntimeRecallPolicy — 运行时渐进回忆策略

Memory 召回采用分层策略，避免每次都直接进入昂贵的混合检索：

1. **粗回忆（Rough Recall）**：程序运行时先用纯搜索引擎进行低成本召回。
   - 默认使用 Tantivy / BM25 / SQLite 元数据等本地搜索。
   - 不调用 embedding、rerank 或 Memory LLM。
   - 目标是快速判断是否存在明显相关的历史 Episode、Entity、Decision、Evidence。
2. **召回充分性评估（Recall Sufficiency）**：根据粗回忆结果和即将进行的操作判断是否足够。
   - 若命中为空，且任务包含历史指代、文件路径、模块名、问题复现、继续执行等信号，则进入深度回忆。
   - 若命中结果与下一步工具调用目标无关，则进入重新回忆。
   - 若命中结果只提供泛化线索，缺少文件、命令、决策原因或来源 Episode，则进入深度回忆。
3. **深度回忆（Deep / Hybrid Recall）**：必要时使用混合检索。
   - 使用 BM25 + vector + rerank 的混合检索。
   - 可调用 Memory LLM 做 query 改写、follow-up query、深度裁决和结果整理。
   - 沿 Memory 图关系展开相邻 Entity / Decision / Episode。
4. **执行中重新回忆（Re-recall During Execution）**：在工具调用前后动态判断上下文是否仍然有帮助。
   - 工具调用前，如果目标路径、模块、命令或操作理由在当前上下文中没有可靠依据，先重新回忆。
   - 工具调用失败后，如果失败原因像历史问题、环境差异、依赖缺失或已知规避策略，先重新回忆。
   - 多轮工具链中，如果连续工具结果无法推进用户目标，重新以“下一步操作意图 + 当前失败/发现”为 query 召回。

建议新增运行时结构：

```rust
pub struct RuntimeRecallPolicy {
    pub rough_limit: usize,          // 默认 5
    pub deep_limit: usize,           // 默认 10
    pub enable_hybrid_on_demand: bool,
    pub enable_rerecall: bool,
}

pub struct RecallSufficiency {
    pub sufficient: bool,
    pub reason: String,
    pub missing: Vec<String>,        // file_path、decision_reason、command_usage 等
    pub next_query: Option<String>,
    pub should_upgrade_to_hybrid: bool,
}
```

### 7. Memory 参与执行的触发点

Memory 不应只在用户说“之前、上次、刚刚”时触发。运行时需要在以下节点适时参与：

| 阶段 | 触发方式 | 行为 |
|------|----------|------|
| 用户消息进入后 | 粗回忆 | 用用户请求、当前 workspace、最近会话摘要做纯搜索召回 |
| 任务规划前 | 粗回忆结果不足时深度回忆 | 为计划提供历史约束、之前决策、相关文件线索 |
| 工具调用前 | 上下文无法支持下一步操作时重新回忆 | 用工具名、参数、目标路径、操作意图构造 query |
| 工具失败后 | 失败信号触发重新回忆 | 查找历史修复方式、依赖问题、环境约束 |
| 连续工具链停滞 | 重新回忆 | 用当前发现和下一步目标重新组织 query |
| 最终回复前 | 可选粗回忆 | 检查是否遗漏与用户目标直接相关的历史约束 |

“上下文无法支持下一步操作”的判断可以先用规则实现：

- 即将读取或修改某个路径，但当前上下文没有这个路径的历史来源或用途说明
- 即将运行命令，但当前上下文没有该命令在本项目中的历史用法
- 即将做架构/实现选择，但当前上下文没有相关 Decision 或过往取舍
- 工具失败信息包含 `error`、`failed`、`panic`、`timeout`、`not found`、`权限`、`依赖` 等信号
- 模型连续两轮调用搜索/读取类工具，但没有产生可执行结论

### 8. 与 `recall_memory` 工具的关系

`recall_memory` 仍然是主模型显式请求历史记忆的工具入口；本 RFC 新增的是运行时辅助召回策略：

- 主模型显式调用 `recall_memory`：保持现有 Tool 化召回路径。
- Runtime 粗回忆：作为系统内部运行时辅助，不直接伪造成用户消息。
- Runtime 深度回忆：必要时调用 Memory 内部 recall context 能力，结果以结构化工具上下文进入当前执行链。
- 重新回忆结果必须标注触发原因，避免模型误以为这是用户新输入。

该策略不改变“Memory 不自动进入稳定 system prompt”的边界。回忆结果应作为工具结果或 user-side system-reminder 类运行时上下文进入消息链，并受上下文预算控制。

## 关键文件

| 文件 | 变更 |
|------|------|
| `tiangong-memory/src/types.rs` | 新增 MemoryCandidate、EnhancedTurnResult、TurnMessage、ExtractionOutput |
| `tiangong-memory/src/types.rs` | 新增 RuntimeRecallPolicy、RecallSufficiency、RuntimeRecallContext |
| `tiangong-memory/src/command.rs` | 新增 MemoryCommand::SubmitCandidate、RunEnhancedMicroRumination、RoughRecall、EvaluateRecallSufficiency |
| `tiangong-memory/src/actor.rs` | 新增 pending_candidates 缓冲 + 命令分发 |
| `tiangong-memory/src/handle.rs` | 新增 submit_memory_candidate、run_enhanced_micro_rumination、rough_recall、evaluate_recall_sufficiency |
| `tiangong-memory/src/recall_context.rs` | 增加粗回忆、充分性评估、按需混合检索升级 |
| `tiangong-memory/src/rumination.rs` | 新增 process_enhanced_micro、check_episode_dedup、link_written_memories |
| `tiangong-memory/src/writer.rs` | 新增 extract_multi_type_memories、extract_inline_entities_and_decisions |
| `tiangong-core/src/memory/turn_result.rs` | 新增 evaluate_tool_result_for_memory、build_enhanced_memory_turn_result |
| `tiangong-core/src/memory/recall.rs` | 增加运行时粗回忆和重新回忆结果注入辅助 |
| `tiangong-core/src/react/engine.rs` | 工具执行循环中插入候选评估 |
| `tiangong-core/src/react/engine.rs` | 工具调用前后插入上下文充分性判断和重新回忆 |

## 实施阶段

### Phase 1: 新类型 + API 骨架（无行为变化）
添加所有新数据结构、命令变体和 Handle 方法，旧逻辑不变。

### Phase 2: 粗回忆与召回充分性评估（tiangong-memory 侧）
实现纯搜索粗回忆、`RecallSufficiency` 规则判断，不接入执行链。

### Phase 3: 候选评估（tiangong-core 侧）
实现 `evaluate_tool_result_for_memory`，在工具执行循环中提交候选。

### Phase 4: 增强版 Micro 反刍管道（tiangong-memory 侧）
实现 `process_enhanced_micro`、多类型提取、去重和跨类型关联。

### Phase 5: Runtime 重新回忆接入（tiangong-core 侧）
在用户消息进入后、工具调用前、工具失败后接入粗回忆/重新回忆；只有充分性不足时才升级混合检索。

### Phase 6: Inline Entity/Decision 提取
轻量 LLM 提示从单轮中即时提取 Entity/Decision。

### Phase 7: 集成验证
长对话多类型记忆、去重、关联、运行时重新回忆、向后兼容性测试。

验证场景：

- 用户问“继续上次那个模块”，粗回忆命中后直接提供文件/决策线索。
- 用户要求修改文件但未给路径，粗回忆不足后升级混合检索找到历史文件。
- 工具运行失败，重新回忆命中过往同类失败和修复命令。
- 连续读取多个文件仍无法推进时，基于当前发现重新召回相关 Decision。
- 简单普通问答不触发深度混合检索，避免额外成本。

## 状态

- [x] 运行时粗回忆与召回充分性评估已接入
- [x] 用户消息进入、工具调用前、工具失败后的重新回忆已接入
- [x] MemoryCandidate 与 EnhancedTurnResult 已实现
- [x] 候选提交 API（Command / Actor / Handle / IPC）已实现
- [x] 工具执行候选评估（evaluate_tool_result_for_memory）已实现
- [x] 增强版 Micro 反刍管道（process_enhanced_micro）已实现
- [x] 多类型提取、去重写入和跨类型关联已实现
- [ ] Inline Entity/Decision LLM 提取待开发
- [ ] 集成验证待执行
