15 KiB
PicoBot 记忆系统设计与迁移方案
编写日期:2026-06-17
历史说明:本文的
ContextCompressor、Timeline 回填和时间戳边界描述是 1.19.0 之前的基线。当前上下文恢复由单一活动 checkpoint 和 durableseq边界驱动,Timeline 只是提交后的派生检索记录;以 CONTEXT_COMPACTION_DESIGN.md 和 ARCHITECTURE.md 为准。
背景
PicoBot 当前已经具备最基础的记忆能力:
Knowledge记忆:长期事实、偏好、项目知识。Timeline记忆:由上下文压缩产生的会话摘要。memory_recall/timeline_recall:模型可主动检索记忆。- 上下文压缩时会把摘要写入
Timeline,并在恢复会话时回填最近摘要。
这套实现已经能工作,但它更像“存储 + 召回”的初版,而不是完整的记忆系统。主要短板是:
- 记忆写入依赖显式工具调用,缺少自动抽取与整理闭环。
- 检索主要是关键词/FTS,缺少排序、衰减和冲突消解。
Timeline、Knowledge、运行时上下文之间的边界还不够严格。- 记忆治理能力不足,缺少过期、失效、来源追踪、置信度等元数据。
本方案目标是把 PicoBot 的记忆从“能记住”升级为“记得准、找得到、能更新、会遗忘、可观测”。
现状基线
当前代码中的记忆链路大致如下:
SessionManager在处理用户消息时,先召回Knowledge记忆并拼进运行时上下文。ContextCompressor在上下文过大时压缩消息历史,并把压缩结果写为Timeline记忆。timeline_recall工具允许模型主动检索历史摘要。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等参数,但整体闭环还没有完全落地。
设计目标
必须达到
- 自动化
- 从对话中自动抽取稳定事实、偏好、项目约束、关键决定。
- 不依赖模型每次都显式调用
memory_store。
- 可控
- 记忆写入要有明确来源、置信度和类别。
- 支持更新、失效、覆盖、删除。
- 可检索
- 检索不能只依赖关键词匹配。
- 需要结合相关性、重要性、时效性、会话范围进行排序。
- 可回填
- 会话恢复时,要能回填“最近摘要 + 相关历史 + 相关知识”,但不能把噪声无限回灌。
- 可治理
- 需要定期清理过期 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 表从“扁平文本”升级为“可治理条目”。
推荐字段
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 中抽取结构化记忆:
factspreferencesdecisionsconstraintsopen_loops
抽取结果应满足:
- 只写“稳定可复用”的信息。
- 不写临时情绪、不写会话噪声、不写大段原文。
- 默认先进入“候选记忆区”,通过规则或 LLM 二次确认后再升格为 active knowledge。
3. 会话压缩写入 Timeline
当前 ContextCompressor 继续承担“会话摘要”的职责,但建议改成两步:
- 压缩当前上下文,生成可注入的短摘要。
- 同时生成结构化 timeline entry,写入 timeline store。
这样 timeline 摘要和给模型看的压缩摘要可以一致,但不必完全相同。
读取策略
默认读取顺序
每轮 user turn 建议按以下顺序构建上下文:
- 运行时上下文
- 当前会话最近消息
- 当前会话最近 timeline 摘要
- 与当前 query 相关的 Knowledge 记忆
- 必要时再补充更旧的 Timeline 召回
Knowledge 召回排序
建议使用混合评分:
final_score =
relevance_score
+ importance_weight
+ recency_weight
- redundancy_penalty
- superseded_penalty
建议排序规则:
- 先按相关性筛选候选。
- 再按重要性和更新时间重排。
- 被 supersede 的条目默认不参与主召回。
- 低置信度条目只在结果不足时补位。
Timeline 召回策略
Timeline 更适合按 session 和时间窗口召回:
- 恢复会话时优先加载最近几条摘要。
- 只有当模型显式需要回顾历史,或者当前话题明显切换,才主动拉更多 timeline。
- 跨会话回顾时先查同主题摘要,再查同 session 摘要。
冲突与失效
冲突类型
- 同 key 冲突
- 新记忆与旧记忆 key 相同。
- 处理方式:upsert,旧版本保留更新历史。
- 语义冲突
- 内容不同,但描述的是同一事实。
- 处理方式:标记旧条目 superseded,保留新条目为 active。
- 时效冲突
- 旧事实已经过期。
- 处理方式:按
expires_at或规则自动归档。
推荐处理流程
- 新记忆先入候选队列。
- 对候选记忆做相似度检查。
- 如果与已有 active knowledge 冲突:
- 保留新条目。
- 给旧条目标记
superseded_by。
- 如果置信度过低:
- 降权但不立即删除。
- 如果确定失效:
- 移入 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.rssrc/agent/context_compressor.rssrc/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 调用方。
推荐实现顺序
如果只做一轮最有性价比的改造,我建议按这个顺序:
schema + metadata自动 consolidation混合召回与重排冲突消解与衰减定时清理
这个顺序的好处是:
- 先把可观察和可治理的数据补齐。
- 再补自动写入,避免“写进去但以后无法管理”。
- 最后再优化召回体验。
风险与回退
风险
- 自动抽取可能把短期上下文误判成长期知识。
- 召回排序不稳时,模型可能看到过多或过少记忆。
- 迁移 schema 时如果没有兼容逻辑,老数据会丢。
回退策略
- 保留现有
memory_store/memory_recall作为兼容入口。 - 所有新字段都应可空。
- consolidation 可以通过配置关闭。
- 召回排序可退回 FTS5 + importance 的简化模式。
验收标准
记忆系统完成迁移后,应满足:
- 用户不手动存记忆时,系统仍能逐步积累稳定知识。
Knowledge的 recall 结果更准,噪声更少。Timeline不会无限膨胀,且能按 session 回放。- 旧事实可被新事实覆盖,冲突状态可追踪。
SessionManager不再承担记忆抽取的核心业务逻辑。- 任意记忆条目都能回答三个问题:
- 它从哪里来?
- 它为什么还活着?
- 它什么时候该被忘掉?
对 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 的记忆系统就会从“可用”变成“可信、可演进”。