docs: add memory system design proposal
This commit is contained in:
parent
e3b2ff2472
commit
28a313bf21
551
docs/MEMORY_SYSTEM_DESIGN.md
Normal file
551
docs/MEMORY_SYSTEM_DESIGN.md
Normal file
@ -0,0 +1,551 @@
|
||||
# PicoBot 记忆系统设计与迁移方案
|
||||
|
||||
编写日期:2026-06-17
|
||||
|
||||
## 背景
|
||||
|
||||
PicoBot 当前已经具备最基础的记忆能力:
|
||||
|
||||
- `Knowledge` 记忆:长期事实、偏好、项目知识。
|
||||
- `Timeline` 记忆:由上下文压缩产生的会话摘要。
|
||||
- `memory_recall` / `timeline_recall`:模型可主动检索记忆。
|
||||
- 上下文压缩时会把摘要写入 `Timeline`,并在恢复会话时回填最近摘要。
|
||||
|
||||
这套实现已经能工作,但它更像“存储 + 召回”的初版,而不是完整的记忆系统。主要短板是:
|
||||
|
||||
- 记忆写入依赖显式工具调用,缺少自动抽取与整理闭环。
|
||||
- 检索主要是关键词/FTS,缺少排序、衰减和冲突消解。
|
||||
- `Timeline`、`Knowledge`、运行时上下文之间的边界还不够严格。
|
||||
- 记忆治理能力不足,缺少过期、失效、来源追踪、置信度等元数据。
|
||||
|
||||
本方案目标是把 PicoBot 的记忆从“能记住”升级为“记得准、找得到、能更新、会遗忘、可观测”。
|
||||
|
||||
## 现状基线
|
||||
|
||||
当前代码中的记忆链路大致如下:
|
||||
|
||||
1. `SessionManager` 在处理用户消息时,先召回 `Knowledge` 记忆并拼进运行时上下文。
|
||||
2. `ContextCompressor` 在上下文过大时压缩消息历史,并把压缩结果写为 `Timeline` 记忆。
|
||||
3. `timeline_recall` 工具允许模型主动检索历史摘要。
|
||||
4. `memory_store` / `memory_recall` / `memory_forget` 允许模型手动管理 `Knowledge` 记忆。
|
||||
|
||||
现有实现的特点:
|
||||
|
||||
- 存储是 SQLite。
|
||||
- 检索是 FTS5 + LIKE 回退。
|
||||
- 记忆条目只有 `key/content/category/importance/session_id/timestamps`。
|
||||
- 配置里已经预留了 `recall_limit`、`timeline_retention_days`、`idle_consolidation_minutes` 等参数,但整体闭环还没有完全落地。
|
||||
|
||||
## 设计目标
|
||||
|
||||
### 必须达到
|
||||
|
||||
1. 自动化
|
||||
- 从对话中自动抽取稳定事实、偏好、项目约束、关键决定。
|
||||
- 不依赖模型每次都显式调用 `memory_store`。
|
||||
|
||||
2. 可控
|
||||
- 记忆写入要有明确来源、置信度和类别。
|
||||
- 支持更新、失效、覆盖、删除。
|
||||
|
||||
3. 可检索
|
||||
- 检索不能只依赖关键词匹配。
|
||||
- 需要结合相关性、重要性、时效性、会话范围进行排序。
|
||||
|
||||
4. 可回填
|
||||
- 会话恢复时,要能回填“最近摘要 + 相关历史 + 相关知识”,但不能把噪声无限回灌。
|
||||
|
||||
5. 可治理
|
||||
- 需要定期清理过期 timeline。
|
||||
- 低质量或冲突记忆要可降权、可 supersede、可追溯。
|
||||
|
||||
### 暂不做
|
||||
|
||||
- 不在第一阶段引入复杂的分布式记忆服务。
|
||||
- 不强制接入外部向量数据库。
|
||||
- 不把记忆系统做成一个独立的产品边界;它仍然属于 PicoBot runtime。
|
||||
|
||||
## 目标架构
|
||||
|
||||
建议把记忆系统拆成四层。
|
||||
|
||||
### 1. 运行时上下文层
|
||||
|
||||
用途:
|
||||
|
||||
- 当前轮的系统提示词。
|
||||
- 运行时间、会话 ID、临时提醒、技能提示。
|
||||
- 不应被长期记忆污染。
|
||||
|
||||
规则:
|
||||
|
||||
- 只属于当前 turn。
|
||||
- 不入库,或仅作为可追踪的审计记录入库,不参与长期 recall。
|
||||
|
||||
### 2. Timeline 层
|
||||
|
||||
用途:
|
||||
|
||||
- 会话摘要。
|
||||
- 上下文压缩结果。
|
||||
- 历史状态回放。
|
||||
|
||||
规则:
|
||||
|
||||
- 按 session 归属。
|
||||
- 可被 `timeline_recall` 查询。
|
||||
- 默认保留有限时间,过期可清理。
|
||||
- 适合作为“发生过什么”的记录,而不是“世界上长期成立的事实”。
|
||||
|
||||
### 3. Knowledge 层
|
||||
|
||||
用途:
|
||||
|
||||
- 用户稳定偏好。
|
||||
- 项目事实。
|
||||
- 长期决策。
|
||||
- 可复用的经验和约束。
|
||||
|
||||
规则:
|
||||
|
||||
- 需要来源追踪和更新时间。
|
||||
- 可以被时间衰减、冲突消解、覆盖和删除。
|
||||
- 适合被 `memory_recall` 检索并注入 system/runtime context。
|
||||
|
||||
### 4. Archive 层
|
||||
|
||||
用途:
|
||||
|
||||
- 低价值但不该直接丢弃的历史。
|
||||
- 被 supersede 的旧知识。
|
||||
- 过期 timeline 的冷存档。
|
||||
|
||||
规则:
|
||||
|
||||
- 不参与默认 recall。
|
||||
- 仅在排障、导出、审计或手工恢复时查看。
|
||||
|
||||
## 统一数据模型
|
||||
|
||||
建议将 `memories` 表从“扁平文本”升级为“可治理条目”。
|
||||
|
||||
### 推荐字段
|
||||
|
||||
```text
|
||||
id 唯一 ID
|
||||
key 语义 key,稳定标识一条知识或摘要
|
||||
content 正文
|
||||
category knowledge / timeline / archive / scratch
|
||||
session_id 归属会话
|
||||
source_session_id 来源会话
|
||||
source_message_id 来源消息或 turn 标识
|
||||
source_type explicit_tool / auto_consolidation / context_compression / manual
|
||||
importance 重要性 0.0-1.0
|
||||
confidence 置信度 0.0-1.0
|
||||
created_at 创建时间
|
||||
updated_at 更新时间
|
||||
last_accessed_at 最近召回时间
|
||||
expires_at 过期时间,可空
|
||||
superseded_by 被哪条记忆覆盖
|
||||
status active / superseded / archived / deleted
|
||||
tags 便于过滤和检索
|
||||
embedding_ref 未来可选的向量引用
|
||||
```
|
||||
|
||||
### 字段意义
|
||||
|
||||
- `importance`:这条记忆值不值得保留。
|
||||
- `confidence`:这条记忆有多可靠。
|
||||
- `source_*`:这条记忆从哪里来,方便审计和冲突处理。
|
||||
- `expires_at`:是否该被自动遗忘。
|
||||
- `superseded_by`:是否已经被更可信的新版本覆盖。
|
||||
|
||||
## 写入策略
|
||||
|
||||
### 1. 显式写入
|
||||
|
||||
仍保留 `memory_store` 工具。
|
||||
|
||||
适用场景:
|
||||
|
||||
- 用户明确要求记住。
|
||||
- 模型确认了稳定事实。
|
||||
- 人工/外部流程显式提供知识条目。
|
||||
|
||||
要求:
|
||||
|
||||
- 必须带稳定 `key`。
|
||||
- 建议附带 `importance` 和 `confidence`。
|
||||
- 允许覆盖同 key 的旧值,但要保留变更痕迹。
|
||||
|
||||
### 2. 自动知识抽取
|
||||
|
||||
新增一条 consolidation 流程,从 turn 中抽取结构化记忆:
|
||||
|
||||
- `facts`
|
||||
- `preferences`
|
||||
- `decisions`
|
||||
- `constraints`
|
||||
- `open_loops`
|
||||
|
||||
抽取结果应满足:
|
||||
|
||||
- 只写“稳定可复用”的信息。
|
||||
- 不写临时情绪、不写会话噪声、不写大段原文。
|
||||
- 默认先进入“候选记忆区”,通过规则或 LLM 二次确认后再升格为 active knowledge。
|
||||
|
||||
### 3. 会话压缩写入 Timeline
|
||||
|
||||
当前 `ContextCompressor` 继续承担“会话摘要”的职责,但建议改成两步:
|
||||
|
||||
1. 压缩当前上下文,生成可注入的短摘要。
|
||||
2. 同时生成结构化 timeline entry,写入 timeline store。
|
||||
|
||||
这样 timeline 摘要和给模型看的压缩摘要可以一致,但不必完全相同。
|
||||
|
||||
## 读取策略
|
||||
|
||||
### 默认读取顺序
|
||||
|
||||
每轮 user turn 建议按以下顺序构建上下文:
|
||||
|
||||
1. 运行时上下文
|
||||
2. 当前会话最近消息
|
||||
3. 当前会话最近 timeline 摘要
|
||||
4. 与当前 query 相关的 Knowledge 记忆
|
||||
5. 必要时再补充更旧的 Timeline 召回
|
||||
|
||||
### Knowledge 召回排序
|
||||
|
||||
建议使用混合评分:
|
||||
|
||||
```text
|
||||
final_score =
|
||||
relevance_score
|
||||
+ importance_weight
|
||||
+ recency_weight
|
||||
- redundancy_penalty
|
||||
- superseded_penalty
|
||||
```
|
||||
|
||||
建议排序规则:
|
||||
|
||||
- 先按相关性筛选候选。
|
||||
- 再按重要性和更新时间重排。
|
||||
- 被 supersede 的条目默认不参与主召回。
|
||||
- 低置信度条目只在结果不足时补位。
|
||||
|
||||
### Timeline 召回策略
|
||||
|
||||
Timeline 更适合按 session 和时间窗口召回:
|
||||
|
||||
- 恢复会话时优先加载最近几条摘要。
|
||||
- 只有当模型显式需要回顾历史,或者当前话题明显切换,才主动拉更多 timeline。
|
||||
- 跨会话回顾时先查同主题摘要,再查同 session 摘要。
|
||||
|
||||
## 冲突与失效
|
||||
|
||||
### 冲突类型
|
||||
|
||||
1. 同 key 冲突
|
||||
- 新记忆与旧记忆 key 相同。
|
||||
- 处理方式:upsert,旧版本保留更新历史。
|
||||
|
||||
2. 语义冲突
|
||||
- 内容不同,但描述的是同一事实。
|
||||
- 处理方式:标记旧条目 superseded,保留新条目为 active。
|
||||
|
||||
3. 时效冲突
|
||||
- 旧事实已经过期。
|
||||
- 处理方式:按 `expires_at` 或规则自动归档。
|
||||
|
||||
### 推荐处理流程
|
||||
|
||||
1. 新记忆先入候选队列。
|
||||
2. 对候选记忆做相似度检查。
|
||||
3. 如果与已有 active knowledge 冲突:
|
||||
- 保留新条目。
|
||||
- 给旧条目标记 `superseded_by`。
|
||||
4. 如果置信度过低:
|
||||
- 降权但不立即删除。
|
||||
5. 如果确定失效:
|
||||
- 移入 archive 或直接删除。
|
||||
|
||||
## 过期与清理
|
||||
|
||||
### Timeline 清理
|
||||
|
||||
Timeline 默认保留 90 天是合理起点,但建议把它变成真正的后台任务:
|
||||
|
||||
- 每日或定时运行。
|
||||
- 清理 `category = timeline` 且过期的条目。
|
||||
- 清理前先做统计和日志记录。
|
||||
|
||||
### Knowledge 清理
|
||||
|
||||
Knowledge 不建议简单按天数删。
|
||||
|
||||
更合理的是:
|
||||
|
||||
- 低重要度 + 低置信度 + 长时间未访问的记忆,先降权。
|
||||
- 明确过期的条目进入 archive。
|
||||
- 被 superseded 的条目保留一段审计窗口后再清理。
|
||||
|
||||
### 噪声过滤
|
||||
|
||||
禁止写入或默认不回灌的内容:
|
||||
|
||||
- 自动压缩摘要的中间副本。
|
||||
- 运行时元信息。
|
||||
- 工具回显噪声。
|
||||
- 模板/框架泄漏。
|
||||
- 明显的无意义重复片段。
|
||||
|
||||
## 与现有代码的映射
|
||||
|
||||
建议的模块职责演进如下:
|
||||
|
||||
### `src/memory/`
|
||||
|
||||
保留高层 API,但增加:
|
||||
|
||||
- 条目元数据
|
||||
- 记忆状态机
|
||||
- 统一评分接口
|
||||
- 冲突处理接口
|
||||
|
||||
### `src/storage/memory.rs`
|
||||
|
||||
负责:
|
||||
|
||||
- SQLite CRUD
|
||||
- FTS/LIKE 检索
|
||||
- Timeline 清理
|
||||
- 批量更新 superseded 状态
|
||||
|
||||
后续可扩展:
|
||||
|
||||
- `last_accessed_at` 更新
|
||||
- 记忆状态批处理
|
||||
- 按 session / namespace 的索引优化
|
||||
|
||||
### `src/agent/context_compressor.rs`
|
||||
|
||||
继续负责上下文压缩,但建议拆成两步:
|
||||
|
||||
- 压缩历史
|
||||
- 产出 timeline 记录
|
||||
|
||||
并把“是否生成 timeline / 是否写入 memory”做成明确开关。
|
||||
|
||||
### `src/session/session.rs`
|
||||
|
||||
负责:
|
||||
|
||||
- 选择哪些记忆进入当前 turn
|
||||
- 会话恢复时注入最近 timeline
|
||||
- 持久化 session 级别的压缩/归档状态
|
||||
|
||||
不应承担记忆抽取和冲突消解的重逻辑。
|
||||
|
||||
### `src/tools/memory.rs`
|
||||
|
||||
保留工具接口,但建议:
|
||||
|
||||
- 增加 `confidence`、`expires_at`、`tags` 等参数。
|
||||
- `memory_recall` 支持更清晰的过滤条件。
|
||||
- `memory_forget` 支持软删和 hard delete 两种模式。
|
||||
|
||||
## 迁移方案
|
||||
|
||||
建议分 5 个阶段推进。
|
||||
|
||||
### Phase 0: 文档和协议对齐
|
||||
|
||||
目标:
|
||||
|
||||
- 先把目标讲清楚,避免边改边跑偏。
|
||||
|
||||
产物:
|
||||
|
||||
- 本文档。
|
||||
- 更新 `README` 中的记忆入口。
|
||||
- 补齐记忆数据模型和流程图。
|
||||
|
||||
验收:
|
||||
|
||||
- 团队能明确区分 runtime / timeline / knowledge / archive 四层。
|
||||
|
||||
### Phase 1: 只扩 schema,不改行为
|
||||
|
||||
目标:
|
||||
|
||||
- 给后续治理能力留好数据位。
|
||||
|
||||
改动建议:
|
||||
|
||||
- `memories` 表增加 `confidence`、`source_type`、`source_message_id`、`source_session_id`、`last_accessed_at`、`expires_at`、`superseded_by`、`status`、`tags` 等字段。
|
||||
- 兼容老数据:老字段缺省时回退到旧逻辑。
|
||||
- `SessionMeta` 可继续保留现有压缩时间戳。
|
||||
|
||||
验收:
|
||||
|
||||
- 老数据能正常读写。
|
||||
- 现有记忆工具和压缩流程不需要改调用方。
|
||||
|
||||
### Phase 2: 补自动 consolidation
|
||||
|
||||
目标:
|
||||
|
||||
- 从“显式写记忆”升级为“自动抽取记忆”。
|
||||
|
||||
改动建议:
|
||||
|
||||
- 在一次 turn 结束后,异步启动 consolidation。
|
||||
- 从最近 turn 中抽取:
|
||||
- timeline summary
|
||||
- knowledge candidates
|
||||
- 新增去噪逻辑:
|
||||
- 不写工具噪声
|
||||
- 不写运行时上下文
|
||||
- 不写重复摘要
|
||||
|
||||
推荐落点:
|
||||
|
||||
- `src/session/session.rs`
|
||||
- `src/agent/context_compressor.rs`
|
||||
- `src/memory/`
|
||||
|
||||
验收:
|
||||
|
||||
- 用户不手动调用 `memory_store` 时,也能逐步积累稳定知识。
|
||||
- timeline 记录与知识条目不再混写。
|
||||
|
||||
### Phase 3: 引入混合召回与排序
|
||||
|
||||
目标:
|
||||
|
||||
- 让 recall 真正“像记忆”而不是“像全文搜索”。
|
||||
|
||||
改动建议:
|
||||
|
||||
- 召回增加排序层:
|
||||
- 相关性
|
||||
- 重要性
|
||||
- 时效性
|
||||
- 状态过滤
|
||||
- `Knowledge` 与 `Timeline` 使用不同召回策略。
|
||||
- 恢复会话时优先加载最近 timeline,再按 query 召回知识。
|
||||
|
||||
可选增强:
|
||||
|
||||
- 后续接入 embedding / rerank。
|
||||
|
||||
验收:
|
||||
|
||||
- 长会话和多主题对话的召回质量明显提升。
|
||||
- 旧记忆不会总是压过新记忆。
|
||||
|
||||
### Phase 4: 冲突消解、失效与清理
|
||||
|
||||
目标:
|
||||
|
||||
- 让记忆系统可治理。
|
||||
|
||||
改动建议:
|
||||
|
||||
- 对知识条目做冲突检测。
|
||||
- 支持 supersede。
|
||||
- 对 timeline 做定期清理。
|
||||
- 对低质量或长期未访问的知识降权或归档。
|
||||
|
||||
推荐定时任务:
|
||||
|
||||
- timeline retention cleanup
|
||||
- stale memory decay
|
||||
- archive compaction
|
||||
|
||||
验收:
|
||||
|
||||
- 过期内容不会无限膨胀。
|
||||
- 旧事实能被新事实覆盖。
|
||||
|
||||
### Phase 5: 兼容层收口
|
||||
|
||||
目标:
|
||||
|
||||
- 把临时兼容逻辑收敛成稳定接口。
|
||||
|
||||
改动建议:
|
||||
|
||||
- 清理历史遗留的“摘要直接当知识”路径。
|
||||
- 统一只从 memory service 读取记忆,不再绕过治理层直接查表。
|
||||
- 为 debug/export 保留只读视图,但不参与默认注入。
|
||||
|
||||
验收:
|
||||
|
||||
- 记忆系统对外只有稳定接口。
|
||||
- 内部实现可继续演进,而不影响 Session / Agent 调用方。
|
||||
|
||||
## 推荐实现顺序
|
||||
|
||||
如果只做一轮最有性价比的改造,我建议按这个顺序:
|
||||
|
||||
1. `schema + metadata`
|
||||
2. `自动 consolidation`
|
||||
3. `混合召回与重排`
|
||||
4. `冲突消解与衰减`
|
||||
5. `定时清理`
|
||||
|
||||
这个顺序的好处是:
|
||||
|
||||
- 先把可观察和可治理的数据补齐。
|
||||
- 再补自动写入,避免“写进去但以后无法管理”。
|
||||
- 最后再优化召回体验。
|
||||
|
||||
## 风险与回退
|
||||
|
||||
### 风险
|
||||
|
||||
- 自动抽取可能把短期上下文误判成长期知识。
|
||||
- 召回排序不稳时,模型可能看到过多或过少记忆。
|
||||
- 迁移 schema 时如果没有兼容逻辑,老数据会丢。
|
||||
|
||||
### 回退策略
|
||||
|
||||
- 保留现有 `memory_store` / `memory_recall` 作为兼容入口。
|
||||
- 所有新字段都应可空。
|
||||
- consolidation 可以通过配置关闭。
|
||||
- 召回排序可退回 FTS5 + importance 的简化模式。
|
||||
|
||||
## 验收标准
|
||||
|
||||
记忆系统完成迁移后,应满足:
|
||||
|
||||
1. 用户不手动存记忆时,系统仍能逐步积累稳定知识。
|
||||
2. `Knowledge` 的 recall 结果更准,噪声更少。
|
||||
3. `Timeline` 不会无限膨胀,且能按 session 回放。
|
||||
4. 旧事实可被新事实覆盖,冲突状态可追踪。
|
||||
5. `SessionManager` 不再承担记忆抽取的核心业务逻辑。
|
||||
6. 任意记忆条目都能回答三个问题:
|
||||
- 它从哪里来?
|
||||
- 它为什么还活着?
|
||||
- 它什么时候该被忘掉?
|
||||
|
||||
## 对 PicoBot 当前实现的落地建议
|
||||
|
||||
最直接的落点是:
|
||||
|
||||
- 先在 `src/storage/memory.rs` 和 `src/memory/types.rs` 扩字段。
|
||||
- 再在 `src/agent/context_compressor.rs` 增加结构化摘要输出。
|
||||
- 然后在 `src/session/session.rs` 增加 consolidation hook。
|
||||
- 最后把 `src/tools/memory.rs` 升级为带治理字段的工具接口。
|
||||
|
||||
如果只做最小闭环,至少要先实现:
|
||||
|
||||
- `Timeline` 的定时清理
|
||||
- `Knowledge` 的自动抽取
|
||||
- `Knowledge` 的冲突消解
|
||||
- 召回结果的时间衰减和重排
|
||||
|
||||
这样 PicoBot 的记忆系统就会从“可用”变成“可信、可演进”。
|
||||
Loading…
x
Reference in New Issue
Block a user