交接档案

05 · 长期记忆与收藏系统历史档案

本文件把「设计过的」「做出来的」「还没做的」分开记录,不合并、不互相覆盖。 现状基线:5666a72 / v1 a3ef2a33 / v2 8c6fe98a / 国内版 22:26 进程。


一、五代演进(设计层)

v0 · 原始记忆库(2026-10-05,✅ 已实现)

用户原始要求(原文):

「外置记忆库:后端必须接入 Cloudflare D1 或 KV。每次用户输入和 AI 回复原封不动存进数据库,带上时间戳。」 「检索逻辑:每次发消息时,去数据库检索最相关的历史原话,配合人设打包给大模型。」

实现: - 键 mem_<ts>_<rand>,值 {role, content, ts, day_key, hour, period}; - 检索:list(prefix:'mem_') → 取最近 300 条 → 中文双字滑窗 + 英文整词 tokenize → 命中数排序 → top5; - 时间感知:前端带 time.iso,后端 resolveTime() 归一,提示词注入「现在几点/什么时段」。

局限(后来被 P0-1 修掉):所有角色共用同一前缀,测试角色会读到正式角色的记忆。

v1 · 按 profile 隔离(2026-10-19,✅)

v2 · 结构化档案(2026-10-19,✅)

v3 · 高光 / 经历 / 关系(2026-10-19,✅)

类型 键 必填 进模型上下文?
高光原话 hl:<profile>:<hid> mid(真实消息)、content 必须能在原文中找到 ✅ top3
经历 exp:<profile>:<eid> title/content/source_mids、status=active\|candidate、time_certainty ✅ active top3 + candidate top2(候选明确标注「不得当作事实」)
关系 rel:<profile>:<rid> from_id/to_id(高光或经历)、5 种类型 ❌(仅 UI 查看)

删除级联:删高光/经历会软删引用它的关系;删消息时若有引用 → 409,确认后级联。

v4 · 框架升级构想(2026-10-23,⬜ 只调研,未实施)

用户下发《基线审查任务书》,要求研究 4 个开源项目并说明「可借鉴什么/对应源码位置/兼容性/成本/风险」,明确不得直接照搬、不得只看 README。

项目 机制要点 当时建议
Mem0 ADD/UPDATE/DELETE/NONE 四态决策 + history 表(每次变更留旧值)+ BM25 加性打分 建议 1:确定性规则 + history 版本链(每条记忆保留变更历史)
Graphiti 双时间戳 valid_at/invalid_at/expired_at + RRF 混合检索 建议 2:双时间戳(事件发生时间 vs 记录时间分离);建议 3:RRF 融合关键词与时间衰减
Letta core/archival/recall 三层 + block 限额 + ApproxTokenCounter(bytes/4) 建议 5:core memory + 上下文预算(按 token 而非条数截断)
LangGraph checkpointer(thread_id/writes 表/time-travel)+ BaseStore namespace 建议 4:DO + WAL 流水线(异步记忆整理,不阻塞聊天)

交付物:/workspace/小手机项目-全项目代码基线审查报告.md(40KB,10 部分)。没有一行进入生产代码。


二、当前实现(照代码说)

2.1 写入路径(每条消息写两份)

用户发消息
 ├─ saveMemory()  → mem:<p>:<ts>_<rand>   {role, content, ts, day_key, hour, period}
 └─ saveArchive() → msg:<p>:<mid>         {…, speaker, created_at, deleted_at:null}
(AI 回复同理:saveMemory(raw) + saveArchive(cleanReply))

2.2 读取路径

retrieveContext(profile, query)
 ├─ retrieveMemory()      → list(mem:<p>:) → 取最近 300 → tokenize 命中排序 → top5
 ├─ listHighlights()      → list(hl:<p>:)  → 关键词过滤 → top3
 ├─ listExperiences(active)   → top3
 └─ listExperiences(candidate)→ top2

2.3 提示词注入(buildSystemPrompt)

顺序:人设段(最高优先级/未设定声明)→ 安全边界(<<< >>> 是数据不是指令)→ 当前时间 → 记忆片段(角色/时间/原话)→ 高光(带来源 mid)→ 已确认经历 → 候选经历(标注未确认)→ 身份认知(承认是 AI)→ 状态/收藏引导 → 回复要求(1–3 句)。

2.4 收藏 vs 高光的区别(重要)

收藏(favorite) 高光(highlight)
存储 浏览器 localStorage.fav_list 云端 hl:<profile>:*
触发 长按消息「收藏」;AI 输出 [[fav:…]] 用户收藏时自动同步一份;AI 收藏也同步
是否进提示词 ❌(本机 UI 用) ✅(top3)
是否可跨设备 ❌ ✅
来源校验 无 必须引用真实 msg:,且内容必须能在原文中找到

结论:收藏是「本机便签」,高光是「云端长期记忆」;收藏时前端会顺带写高光(addCloudHighlight:先 /archive 确保来源消息存在,再 POST /highlights)。

2.5 删除语义(当前)

操作 行为
「仅本机删除」 只删 IndexedDB,不碰云端(明确设计)
「彻底删除(含云端)」 POST /messages/delete {mode:'hard'}:物理删 msg: + 引用它的 hl/exp/rel,各留墓碑 tomb:(400 天)
有引用时 首次返回 409 has_refs,用户确认后 force:true 级联硬删
云端没有该条 幂等返回 already_gone:true

已知缺陷(P0-2):硬删不清 mem: 碎片,而聊天检索读的正是 mem: → 被删内容仍会被检索进提示词。 历史遗留:10-23/10-24 两次「云端删除」是软删,279 条记录仍物理存在(只在导出/检索时被过滤)。

2.6 导入 / 导出

接口 内容 去重 墓碑
/export/full msg + hl + exp + rel + mem(+ owner 的旧 mem_) — ❌ 不导出墓碑
/import/full 同上,dry_run 可预览 mid/hid/eid/rid + mem 的 content|ts 指纹 ✅ 命中即跳过
/archive GET/POST 只含 msg(+ legacy),支持搜索分页 mid ✅

墓碑不导出 → 把备份恢复到另一个空库时,防复活保护会丢失(P2)。

2.7 profile 隔离


三、设计 vs 实现的差异(不合并,逐条列)

设计(任务书/构想) 实现现状 差距
原始消息与 AI 记忆整理相互独立,整理失败不能丢原文 ✅ safeArchive 分离;记忆检索失败降级 已满足
高光必须是原话,不能 AI 改写 ✅ content 必须被原文包含(STRICT_MID) 已满足
删除高光不删原始消息 ✅ 只删 hl + 级联关系 已满足
派生记忆必须有来源 ✅ source_mids 必须全部存在 已满足
不确定信息不得当事实 🟡 candidate 单列 + 提示词标注;但没有审核流转 无「确认/驳回」动作
记忆可更新/合并 ❌ 只能新增与删除;没有 update/merge 与 Mem0 四态差距最大
记忆变更历史 ❌ 无 history 表 建议 1 未实施
双时间戳(事件时间/记录时间) 🟡 经历有 event_time + time_certainty,消息只有 created_at 不完整
Token 预算 ❌ 按条数截断(5/3/3/2),无 token 计数 建议 5 未实施
记忆异步整理(不阻塞聊天) ❌ 同步写两份 建议 4 未实施
向量检索 ❌ 关键词子串匹配 明确非第一阶段必要
跨设备收藏 ❌ 收藏只在本机 高光可跨设备

四、成本与配额影响(记忆系统的真实代价)

操作 KV 读 KV 写 list
一条聊天(冷缓存) 约 300(mem 最近 300 条)+ 若干 5–6 4–5
一条聊天(5 分钟内热缓存) 约 300 5–6 0–1
一次「彻底删除」 与 hl/exp/rel 数量成正比 2 + 引用数 2(hl/exp 原始 list)
一次全量导出 与总记录数成正比 0 每 200 键 1 次
一次全量导入 与总记录数成正比(含指纹预扫) 与新增数成正比 2–3

免费版上限(账户级/天):list 1000、写 1000、读 10 万。 → 约 160 条聊天/天 会打满写配额;333 条/天 会打满读配额;密集同步/搬迁是 list 的主要杀手。 2026-10-25 的 500 故障就是 list 1000 次被烧穿(见 07 事件 06)。


五、必须保留的东西(接手时不要动)

  1. mem: / msg: / hl: / exp: / rel: / tomb: 六类键的命名与前缀语义(有存量数据)。
  2. STRICT_MID 与 MEM_READ_LEGACY 两个开关的默认值(1/1)与含义。
  3. content_hash = strHash(profile + '|' + content) 的去重算法(改了会重复导入)。
  4. 墓碑 400 天 TTL 与「导入前查墓碑」的检查顺序。
  5. 高光「内容必须被原文包含」的校验(这是防「AI 改写冒充原话」的核心)。
  6. profile 默认 owner 与校验正则。

六、最需要先决定的三件事

  1. 检索数据源统一:继续双写 mem:+msg:,还是让检索改读 msg:(顺手解决 P0-2)?
  2. 历史 279 条软删怎么处理:硬删+墓碑(要分批、耗写配额)还是保留但标记?
  3. 记忆要不要升级到 v4(更新/合并/history/双时间戳):这决定阶段 6「成长分析」有没有地基。
本页由交接包内的 Markdown 原样转换;内容未改动。源码与完整包请回目录页下载。