本文档定义 Empty「读者记忆(ReaderMemory)」的产品目标、架构、分阶段落地计划,以及第三方 Memory 技术选型结论。供实现者(含 Claude)按任务拆解执行。
相关文档:ARCHITECTURE.md
Empty 已有:
- 书内 RAG:
ChunkRetriever+ 防剧透(Chunk.fullyReadPredicate) - 思维链接:
ThoughtLinkFinder— 选中文本后,词法匹配跨书高亮,可选 AI 解释 - 阅读 Agent v1:
ReadingAgent+ReadingToolbox(6 个工具,写操作待确认)
这些能力分散、被动,无法支撑「越读越懂你」:
| 场景 | 今天 | 有了 ReaderMemory 之后 |
|---|---|---|
| 伴读问「这和我之前读过的什么有关?」 | 只能查当前书已读 Chunk | 先召回跨书高亮/链接卡/历史 Q&A,再解释 |
| 思维链接 | UI 选中文本才触发;词法阈值粗 | 语义召回 + Agent 多步编排;伴读内可主动查 |
| 长期偏好 | 无 | 「读者关注:减法、自然、词汇 X」可检索、可导出 |
| 备份 | 高亮/卡片散落在本机 store | .empty-notes 读者笔记包可迁移元数据;正文仍本地 |
Agent 不是目的,是编排层:在防剧透边界内,串联「书内检索 → 读者记忆 → 思维链接 → 解释 → 建议存卡」。
- 防剧透仍是结构性的 — Memory 不得包含未读章节原文;写入时校验来源。
- 同步读者数据,不同步书籍正文 — Memory 摘要/元数据可同步;Chunk/embedding 仍本地。
- 记忆源以读者行为为准 — 高亮、链接卡、词汇、确认的伴读 Q&A 是 ground truth;LLM 提炼的是派生索引。
- 写操作一律待确认 — 与
CompanionAction一致;Agent 只能propose_memory,不能静默改写长期记忆。 - 不引入通用 coding agent 框架 — 保持
ReadingAgent薄循环;扩展ReadingToolbox。
- 不接 Claude Agent SDK(Python/TS,面向写代码)
- 不把书正文上传第三方 Memory 服务
- 当前不做 Passkey / 自建账号体系 / 云同步;备份先只考虑本地读者笔记包
┌─────────────────────────────────────────────────────────────┐
│ UI:阅读器 / MacCompanionPanel / IOSCompanionSheet │
│ 思维链接 chip · 朱批 steps · 确认按钮 │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ ReadingAgent(保持现有薄循环,maxSteps 3–4) │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ ReadingToolbox(扩展工具) │
│ 已有:search_passages, recap, explain, find_link, ... │
│ 新增:recall_reader_memory, search_highlights, │
│ propose_memory(待确认) │
└───────┬─────────────────────────────┬───────────────────────┘
│ │
▼ ▼
┌───────────────┐ ┌─────────────────────────────┐
│ Chunk RAG │ │ ReaderMemory(新建) │
│ 书内已读段落 │ │ ingest · index · recall │
└───────────────┘ └───────────┬─────────────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
Highlight StudyCardEntry VocabEntry
(高亮快照) (问答卡/链接卡) (生词)
│ │
└─────────┬─────────┘
▼
MemoryItem(新建,Synced Store)
MemoryEmbedding(新建,Local Store,可选)
| 职责 | 说明 |
|---|---|
| Ingest | 从高亮、链接卡、问答卡、词汇、(可选)伴读轮次派生 MemoryItem |
| Index | 结构化过滤 + 本地语义索引(复用 NLEmbedding / 现有 SemanticScorer 模式) |
| Recall | 给 Toolbox 返回 top-K 片段,带 provenance(书名、章节、类型、时间) |
| Propose | 生成 CompanionAction 建议写入/更新记忆(读者确认后落库) |
ThoughtLinkFinder 升级为 ReaderMemory 的子能力,而非孤立 UI 服务:
ThoughtLinkFinder.findLink()
→ 内部调用 ReaderMemory.recall(.semantic, query: passage, excludeUnread: true)
→ 词法 + 语义双路打分(保留 LexicalScorer 作 fast path)
→ explainLink() 不变
阅读器 chip 与伴读 find_link / recall_reader_memory 共用同一召回管线。
enum MemoryKind: String, Codable {
case highlightNote // 高亮 + 读者笔记
case thoughtLink // 链接卡(StudyCardKind.link)
case companionQA // 保存的问答卡
case theme // 派生主题(LLM 提炼,可选手动确认)
case vocabPattern // 派生:常查词/主题词
}
@Model
final class MemoryItem {
var id: UUID
var kindRawValue: String
var title: String // 短标题,UI / 检索用
var body: String // 摘要正文(≤ 2KB),禁止未读原文
var bookID: UUID? // 来源书,可空(跨书主题)
var chapterIndex: Int?
var sourceLabel: String? // "Walden · Ch.2"
var tags: [String] // 主题标签,可空
var sourceRefID: UUID? // Highlight.id / StudyCardEntry.id
var sourceRefKind: String? // "highlight" | "studyCard" | ...
var createdAt: Date
var updatedAt: Date
var isUserConfirmed: Bool // 派生记忆需 true 才参与 recall
}存储约束:无 unique 约束;tags 用可序列化形式存储,方便未来导出。
与 Chunk 类似,仅存本地:
@Model
final class MemoryEmbedding {
var memoryItemID: UUID
var embedding: Data? // Float 向量,复用 Chunk+Embedding 编解码
var indexedAt: Date
}正文不上云;换设备可对 MemoryItem.body 本地重建 embedding。
| 来源 | 触发 ingest | MemoryKind |
|---|---|---|
Highlight 创建/更新 note |
自动 | highlightNote |
StudyCardEntry kind=.link |
保存链接卡时 | thoughtLink |
StudyCardEntry kind=.qa |
存为卡片时 | companionQA |
VocabEntry 复习多次 |
Phase 2 | vocabPattern |
| 伴读多轮后提炼 | Phase 2,propose_memory 确认后 |
theme |
@MainActor
struct ReaderMemory {
let modelContext: ModelContext
/// 从已有读者数据增量同步(幂等,按 sourceRefID 去重)
func syncFromReaderData() throws -> Int
/// 召回:结构化 + 语义,不返回未确认派生项
func recall(
query: String,
kinds: Set<MemoryKind>? = nil,
bookID: UUID? = nil,
limit: Int = 8
) throws -> [MemoryRecall]
/// 供 Toolbox:格式化为 observation 字符串
func recallObservation(query: String, limit: Int = 5) throws -> String
}
struct MemoryRecall: Sendable {
var itemID: UUID
var kind: MemoryKind
var title: String
var body: String
var sourceLabel: String?
var score: Double
}
打分:finalScore = 0.4 * lexical + 0.6 * semantic(与 ChunkRetriever 一致);无 embedding 时退化为词法。
| 工具名 | 参数 | 行为 | traceLabel 示例 |
|---|---|---|---|
recall_reader_memory |
主题/问题 | ReaderMemory.recall |
忆「减法」 |
search_highlights |
关键词 | 只搜 Highlight.textSnapshot + note |
搜高亮「自然」 |
find_link(升级) |
段落 | ThoughtLinkFinder + Memory 语义路 | 找关联 |
propose_memory |
一句摘要 | 返回 CompanionAction 待确认 |
建议记住(待确认) |
CompanionAction.Kind 新增:
case saveMemory(title: String, body: String, tags: [String])CompanionModel.perform() 确认后写入 MemoryItem(isUserConfirmed: true)。
状态:MemoryItem、ReaderMemory.syncFromReaderData()、recall_reader_memory、search_highlights、propose_memory、旧问答压缩为 theme、ThoughtLinkFinder 记忆召回路、本地 MemoryEmbedding 持久语义路均已落地;伴读主题提炼已有自动 + 手动入口。当前没有云同步:MemoryItem 元数据只在本机,可随 .empty-notes 读者笔记包导出 / 导入,语义向量与正文仍只留本机。
| 任务 ID | 内容 | 验收 |
|---|---|---|
| P1-1 | 新增 MemoryItem 模型 + AppStores 注册(Synced) |
迁移测试通过 |
| P1-2 | 实现 ReaderMemory.syncFromReaderData() |
导入后从高亮/链接卡/问答卡生成 item |
| P1-3 | 实现 recall() 词法路 |
单元测试:按 query 命中链接卡 |
| P1-4 | Toolbox 加 recall_reader_memory、search_highlights |
ReadingAgentTests 扩展 |
| P1-5 | 升级 ThoughtLinkFinder 走 ReaderMemory |
同一 fixture 召回率 ≥ 词法基线 |
| P1-6 | UI:伴读朱批展示 memory 步骤 | 已有 steps 管道,无新屏 |
预估改动文件:
- 新建:
Empty/Models/MemoryItem.swift、Empty/Models/MemoryEmbedding.swift、Empty/Services/MemoryIndexer.swift - 修改:
AppStores.swift、ReaderMemory.swift、ReadingToolbox.swift、ThoughtLinkFinder.swift、CompanionModel.swift - 测试:
EmptyTests/ReaderMemoryTests.swift
| 任务 ID | 内容 | 状态 |
|---|---|---|
| P1b-1 | MemoryEmbedding 本地 store |
已实现:按 itemID 保存 MemoryItem 语义向量 |
| P1b-2 | recall() 语义路 |
已实现:ReaderMemory.recall() 优先复用持久向量;无向量时退词法 |
| 任务 ID | 内容 | 状态 |
|---|---|---|
| P2-1 | propose_memory + CompanionAction.saveMemory |
已实现:不确认不入库,确认后写 theme |
| P2-2 | 伴读结束时可选提炼主题(仅 cloud / 本机 FM) | 已实现基础版:Mac / iOS 伴读可手动「提炼本轮主题」,连续有效问答后也会自动提出 theme 草案 |
| P2-3 | 记忆压缩:旧 companionQA 合并为 theme |
已实现:手动压缩入口 + 旧问答退出 recall,仅保留派生 theme |
| 任务 ID | 内容 | 状态 |
|---|---|---|
| P3-1 | 定义 .empty-notes manifest 与 JSON payload |
已实现:单文件 JSON,schemaVersion 1 |
| P3-2 | 导出 Book/Highlight/Vocab/Bookmark/StudyCard/MemoryItem | 已实现:ReaderNotesBackupStore.exportPackage |
| P3-3 | 导入合并 + 保守记忆冲突策略 | 已实现:同 id upsert;本机较新的 MemoryItem 不被旧备份覆盖 |
| 任务 ID | 内容 | 验收 |
|---|---|---|
| P3+-1 | 自建 relay 持有 delegate key;App 只调自家 API | 私钥不进客户端 |
| P3+-2 | 仅同步 isUserConfirmed 的 theme / 链接摘要(≤2KB,无书摘全文) |
ingest 审计通过 |
| P3+-3 | 设置项「导出到 Walrus 便携记忆」默认关;本地 recall 优先 | Walrus 失败不影响伴读 |
Phase 1–2 用自研 ReaderMemory(SwiftData + 本地检索)作为主存储;第三方仅作可选「派生层」或云端 Claude 路径的参考实现,不替代高亮/链接卡源数据。
| 方案 | 类型 | Swift / iOS | 与 Empty 匹配度 | 主要问题 |
|---|---|---|---|---|
| 自研 ReaderMemory | 应用内 SwiftData | 原生 | ★★★★★ | 需自己写 ingest/recall(可控) |
| Mem0 Platform | 托管 SaaS | REST;社区 Mem0Swift | ★★★☆☆ | 记忆主体是 chat 提炼,非高亮结构化数据;数据出境;BYOK 场景重复 |
| Mem0 OSS | 自托管 Python | REST API,无官方 Swift SDK | ★★☆☆☆ | 需另跑服务 + 向量库;移动端架构重 |
| Zep / Graphiti | 时序知识图谱 | Python,Neo4j/FalkorDB 等 | ★★★☆☆ | 擅关系与事实三元组;运维重;适合「实体关系网」产品形态 |
| Claude Memory Tool | API client-side tool | 需在 Swift 实现 handler | ★★★☆☆ | 仅适用于 Anthropic 云端;模型管文件式记忆,难与 Highlight 源数据一致 |
| Claude claude.ai Memory | 消费产品功能 | 无 API | ☆☆☆☆☆ | 无法嵌入 Empty |
| Letta (MemGPT) | Agent 平台 | letta-swift 非官方;服务端 Docker | ★★☆☆☆ | 完整 agent 运行时,与现有 ReadingAgent 重叠 |
| Cognee | 知识图谱记忆 | Python | ★★☆☆☆ | 同类 GraphRAG;阅读 App 过重 |
| Walrus Memory (MemWal) | 可携带 agent 记忆层 | TS/Python;MemWal;无 Swift SDK | ★★☆☆☆ | Beta;需联网;结构化阅读记忆弱;默认 relayer 见明文;Sui+Walrus 栈重 |
| Apple Foundation Models | 本机模型 | 原生 | ★★★★☆ | 有 Tool/Session,无跨会话用户 Memory 产品化 API |
- 是什么:从对话中自动抽取、去重、检索「用户事实」的托管/自托管记忆层。
- 优点:add/search/update API 成熟;MCP 集成;适合「聊越多越懂你」的 chatbot。
- 缺点:
- 官方 SDK 仅 Python / JavaScript;iOS 需 REST 或社区 Mem0Swift。
- Empty 的核心记忆是 高亮快照、链接卡、阅读轨迹 — 若全走 Mem0,要做双向 ETL,且书摘原文隐私叙事变复杂。
- 与「不同步书正文」原则冲突风险:若把段落塞进 Mem0,等同把读者库上传第三方。
- 若要用:仅作 Companion 派生层 — 只同步
theme级摘要(读者确认后),MemoryItem仍为 source of truth。
- 是什么:时序知识图谱(实体–关系–事实),Graphiti 开源,Zep 为企业托管版。
- 优点:跨书 关系(「梭罗–减法–瓦尔登湖」)表达力强;混合检索延迟低(官方标 ~150ms)。
- 缺点:需图数据库后端;Swift 端只有 HTTP;对单人阅读 App Phase 1 过重。
- 若要用:Phase 3+ 若产品重心变成「个人知识图谱」,可评估 Graphiti 自托管;短期不推荐。
- 是什么:Anthropic Messages API 的 client-side tool;模型发
view/create/edit等命令,应用实现/memories目录读写。 - 优点:与 Claude 云端伴读路径天然契合;可 ZDR;你控制存储(SwiftData 可实现同一协议)。
- 缺点:
- 无官方 Swift SDK 示例(需按 Python/TS 示例移植 handler)。
- 本机 Foundation Models 路径不适用。
- 记忆形态是「模型管理的文件」,与结构化
MemoryItem需做映射层。
- 若要用:作为 AnthropicAIService 云端可选后端 —
AnthropicMemoryBackend读写MemoryItem,而不是单独一套文件系统。
- 是什么:带 core/archival/recall 内存分层的 agent 服务器。
- 缺点:部署导向;Empty 已有 Agent loop,引入则双运行时。
- 结论:不采用。
-
是什么:Walrus Memory — Mysten/Walrus 出品的 可携带 Agent 记忆层(开源 MemWal)。记忆经 relayer 做 embedding → Seal 加密 → 存 Walrus 去中心化存储;所有权与 delegate 权限由 Sui 链上合约 约束。文档:What is Walrus Memory?
-
核心 API:
remember(异步 job 写入)、recall(语义检索)、analyze(LLM 抽事实)、ask(召回 + 回答)。SDK:@mysten-incubation/memwal(TypeScript)、memwal(Python);无官方 Swift SDK,需按 Relayer API 自实现 Ed25519 签名 HTTP 客户端。 -
优点:
- 跨 App / 跨 Agent 便携 — 记忆不锁死在单一 LLM 或单一应用(Claude Code、Cursor、自建 agent 可共用 namespace)。
- 可验证 + 用户主权叙事 — 链上 owner/delegate;加密 blob 在 Walrus;支持
restore从链上重建索引。 - 与「记忆跟我走」长期愿景部分重合 — 若读者希望把「朱」提炼的主题带到其他 AI 工具,Walrus 比 Mem0 更强调 portability 与 verifiability。
-
缺点(对 Empty 致命项):
- 无 Swift / iOS 一等支持 — 移动端需自写签名协议或经自建 relay 转发。
- 必须联网 — recall/remember 依赖 relayer;与离线阅读、无网伴读冲突。
- 记忆形态是文本 blob + 向量 — 高亮锚点、链接卡结构、防剧透章节边界需自建 ETL;非阅读域一等公民。
- 默认信任边界 — 托管 relayer 在 remember/recall 时 能看到明文(见 Trust and Security Model);与 Empty「本机优先、私密阅读」叙事冲突,除非自托管 relayer 或
MemWalManual客户端加密。 - 客户端不宜持钥 —
MemWal.create需 Ed25519 delegate private key + SuiaccountId;不能把 delegate key 打进 iOS App(可提取、可滥用配额)。生产应走 自建 relay,与 BYOK 同类约束。 - 不能把书摘原文同步上去 — 违反「不同步书正文」;仅适合读者确认后的摘要/主题。
- 产品成熟度 — 文档标明 beta、actively evolving;依赖 Sui + Walrus + PostgreSQL(pgvector) relayer,运维与限流(如托管 relayer 账号约 1GB 存储配额)需单独评估。
- 身份与 Passkey 不直接等价 — Walrus 用 Sui 地址 + Ed25519 delegate;WebAuthn Passkey 需额外映射层(Passkey → 你的账号 → Walrus accountId)。
-
接入路径评估:
路径 可行性 说明 A. iOS 直连 relayer.memory.walrus.xyz❌ 不推荐 私钥进 App;明文经第三方 relayer;无 Swift SDK B. 自建 relay + 只同步确认后摘要 ⚠️ Phase 3+MemoryItem仍为 source of truth;relay 持 delegate keyC. 伴读 recall 插件(本地优先 + Walrus 增强) ⚠️ 可选ReaderMemory.recall先本地,登录且开启便携层再合并 Walrus 结果D. MemWalManual客户端加密⚠️ 高成本relayer 不见明文,但 Seal/Sui 集成重,仍无 Swift 官方包 -
与 Mem0 / 自研对比(Walrus 差异化):
维度 自研 ReaderMemory Mem0 Walrus Memory Swift / 离线 原生 / ✅ REST / ❌ 自写 HTTP / ❌ 结构化高亮/链接 ✅ ⚠️ ⚠️ 跨 App 便携 ❌ ⚠️ ✅ 核心卖点 可验证 / 去中心化 ❌ ❌ ✅ 实现复杂度 中 中 高 -
结论:技术上可做,不适合作为 Empty 主 Memory 层;与 Mem0 同属「对话/摘要型」外挂,但更重、更偏 Web3 agent 基础设施。当前版本不接入,只保留为远期研究材料。详见 §7.5。
Phase 1–2(现在) → 自研 ReaderMemory(本方案主路径)
Phase 3 备份 → 本地 `.empty-notes` 读者笔记包
实验性/不作为主路径 → 云同步、账号、多用户先不做
-
当前主路径:ReaderMemory 只在本机参与伴读。
-
便携记忆(可选后续):若未来需要把主题摘要带到别的 Agent/App,先做显式导出;不在当前 app 内预留账号或 server 壳层。
-
伴读合并策略(若启用路径 C):
recall_reader_memory(query) 1. ReaderMemory.recall (local, 必跑) 2. if 便携层已开启 && 网络可用 → relay.recall → 合并去重 3. 格式化 observation 进 Agent transcript
按顺序 PR,每个 PR 可独立 review:
-
MemoryItem+MemoryKind -
AppStoresreader data store 注册 -
ReaderMemoryTests: 插入与 fetch
-
syncFromReaderData()从 Highlight / StudyCardEntry 幂等写入 - 在
HighlightStore保存、链接卡保存处 hook(或首次 recall 前 lazy sync)
-
recall()词法实现 -
recall_reader_memory、search_highlights工具 -
ReadingAgentTests覆盖多步 recall → finish
-
ThoughtLinkFinder改用ReaderMemory - 保持 UI API 不变(
MacReaderScreen/ReadingView)
-
propose_memory+CompanionAction.saveMemory -
CompanionModel.perform()持久化
-
MemoryEmbedding+SemanticIndexer扩展
- 仅当
AnthropicAIService+ 云端路由时,实现 memory tool handler →MemoryItem
- 自建 relay API(remember/recall);delegate key 仅服务端
- 仅 export
isUserConfirmed且body.count ≤ 2048的MemoryItem - 设置项 +
ReaderMemory合并 recall;失败降级为纯本地
| 层级 | 用例 |
|---|---|
| 单元 | ingest 幂等;recall 排序;未确认 theme 不可召回 |
| 集成 | Agent:recall_reader_memory → find_link → finish |
| 防剧透 | ingest 拒绝含未读章节原文的 body(校验 chapterIndex ≤ 当前进度) |
| 隐私 | 无网络下 recall 仅本地 store |
| 问题 | 建议默认 |
|---|---|
| 中文语义召回 | Phase 1 词法为主;Phase 1b 英文 embedding;长期可接多语 embedder |
| 记忆上限 | 单用户 5,000 MemoryItem;recall limit 8;body ≤ 2KB |
| 删除 | 跟 Highlight / 卡片级联删除对应 MemoryItem |
| Mem0 试点 | 不做,除非 Phase 2 结束仍缺「对话提炼」质量 |
| Walrus Memory | Phase 3+ 再评估;默认不做;若做则仅摘要 + 自建 relay + 用户显式开启 |
- 伴读问「和我之前读过的什么有关」时,能引用 其他书 的链接卡/高亮(朱批可见
忆「…」→ 找关联)。 - 思维链接语义召回优于纯词法(同一测试集 top-1 命中率提升)。
- 派生记忆未经确认不出现在 recall 结果中。
- 不破坏防剧透:未读书籍章节文本不出现在 Memory 与 Agent observation 中。
- 全程可不依赖第三方 Memory 服务运行。
文档版本:2026-06-11(含 Walrus Memory 调研 §7.3 / §7.5)· 与 Reading Agent v1、双 SwiftData Store 架构对齐