recallnest 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.md +222 -254
  3. package/README_CN.md +206 -243
  4. package/package.json +3 -2
  5. package/src/capture-engine.ts +26 -4
  6. package/src/context-composer-stable.ts +5 -1
  7. package/src/embedder.ts +24 -0
  8. package/src/env-config.ts +18 -0
  9. package/src/ingest.ts +225 -17
  10. package/src/kg-extractor.ts +17 -0
  11. package/src/memory-output.ts +35 -0
  12. package/src/pivot-apply.ts +52 -1
  13. package/src/pivot-evidence.ts +128 -0
  14. package/src/ppr-traversal.ts +25 -1
  15. package/src/runtime-config.ts +9 -0
  16. package/src/session-engine.ts +11 -4
  17. package/src/session-output.ts +9 -1
  18. package/src/session-store.ts +2 -3
  19. package/src/skill-promotion.ts +67 -1
  20. package/src/__tests__/access-tracker-novelty.test.ts +0 -324
  21. package/src/__tests__/activity-counter.test.ts +0 -125
  22. package/src/__tests__/adaptive-fidelity.test.ts +0 -102
  23. package/src/__tests__/admission-control.test.ts +0 -206
  24. package/src/__tests__/age-label.test.ts +0 -33
  25. package/src/__tests__/anchor-generator.test.ts +0 -67
  26. package/src/__tests__/api-server-security.test.ts +0 -102
  27. package/src/__tests__/auto-consolidation.test.ts +0 -168
  28. package/src/__tests__/auto-recall.test.ts +0 -131
  29. package/src/__tests__/babel-integration.test.ts +0 -144
  30. package/src/__tests__/batch-dedup.test.ts +0 -65
  31. package/src/__tests__/belief-history.test.ts +0 -220
  32. package/src/__tests__/canary-eval.test.ts +0 -114
  33. package/src/__tests__/capture-engine.test.ts +0 -1328
  34. package/src/__tests__/capture-heuristic.test.ts +0 -171
  35. package/src/__tests__/cascade-forget.test.ts +0 -210
  36. package/src/__tests__/category-min-scores.test.ts +0 -116
  37. package/src/__tests__/cc8-cc1-essential-context.test.ts +0 -298
  38. package/src/__tests__/circuit-breaker.test.ts +0 -99
  39. package/src/__tests__/cluster-consolidation.test.ts +0 -1236
  40. package/src/__tests__/confidence-metadata.test.ts +0 -125
  41. package/src/__tests__/confidence-tracker.test.ts +0 -261
  42. package/src/__tests__/conflict-advisor.test.ts +0 -206
  43. package/src/__tests__/conflict-escalation.test.ts +0 -149
  44. package/src/__tests__/conflict-lifecycle.test.ts +0 -83
  45. package/src/__tests__/conflict-output.test.ts +0 -85
  46. package/src/__tests__/conflict-store.test.ts +0 -418
  47. package/src/__tests__/connector-v1.test.ts +0 -181
  48. package/src/__tests__/consolidation-engine.test.ts +0 -520
  49. package/src/__tests__/constructive-retrieval.test.ts +0 -509
  50. package/src/__tests__/context-collapse-renderer.test.ts +0 -214
  51. package/src/__tests__/context-composer-pins.test.ts +0 -64
  52. package/src/__tests__/context-composer-task-selection.test.ts +0 -24
  53. package/src/__tests__/context-reconstructor.test.ts +0 -65
  54. package/src/__tests__/context-renderer.test.ts +0 -91
  55. package/src/__tests__/continuity-eval.test.ts +0 -275
  56. package/src/__tests__/continuity-schema.test.ts +0 -97
  57. package/src/__tests__/contracts.test.ts +0 -262
  58. package/src/__tests__/conversation-importer.test.ts +0 -692
  59. package/src/__tests__/core-recall-eval.test.ts +0 -235
  60. package/src/__tests__/core-summary.test.ts +0 -109
  61. package/src/__tests__/data-checkup.test.ts +0 -304
  62. package/src/__tests__/decay-exemption.test.ts +0 -129
  63. package/src/__tests__/dedup-actions.test.ts +0 -58
  64. package/src/__tests__/dedup-guard.test.ts +0 -39
  65. package/src/__tests__/deterministic-id.test.ts +0 -39
  66. package/src/__tests__/distill-lock.test.ts +0 -305
  67. package/src/__tests__/distill-session-argshape.test.ts +0 -79
  68. package/src/__tests__/doctor.test.ts +0 -319
  69. package/src/__tests__/dream-pipeline.test.ts +0 -820
  70. package/src/__tests__/dream-scope-isolation.test.ts +0 -386
  71. package/src/__tests__/dream-write-batching.test.ts +0 -211
  72. package/src/__tests__/drill-down.test.ts +0 -108
  73. package/src/__tests__/embedder.test.ts +0 -122
  74. package/src/__tests__/emotion-decay.test.ts +0 -46
  75. package/src/__tests__/emotion-detector.test.ts +0 -56
  76. package/src/__tests__/emotion-phase1-integration.test.ts +0 -153
  77. package/src/__tests__/emotion-retrieval.test.ts +0 -56
  78. package/src/__tests__/entity-resolver.test.ts +0 -58
  79. package/src/__tests__/env-config-invariant.test.ts +0 -80
  80. package/src/__tests__/env-config.test.ts +0 -211
  81. package/src/__tests__/error-signature-boost.test.ts +0 -87
  82. package/src/__tests__/error-signature.test.ts +0 -95
  83. package/src/__tests__/error-taxonomy.test.ts +0 -129
  84. package/src/__tests__/ethics-layer.test.ts +0 -476
  85. package/src/__tests__/event-segmenter.test.ts +0 -153
  86. package/src/__tests__/feature-flags-integration.test.ts +0 -54
  87. package/src/__tests__/frequency-tracker.test.ts +0 -113
  88. package/src/__tests__/freshness.test.ts +0 -186
  89. package/src/__tests__/graph-export.test.ts +0 -489
  90. package/src/__tests__/hotness-blend.test.ts +0 -283
  91. package/src/__tests__/ingest-chunking.test.ts +0 -44
  92. package/src/__tests__/ingest-scope-prefix.test.ts +0 -69
  93. package/src/__tests__/ingest.test.ts +0 -360
  94. package/src/__tests__/interference-detector.test.ts +0 -203
  95. package/src/__tests__/kg-extractor.test.ts +0 -192
  96. package/src/__tests__/kg-store.test.ts +0 -300
  97. package/src/__tests__/llm-client-pivot.test.ts +0 -628
  98. package/src/__tests__/llm-consolidation.test.ts +0 -226
  99. package/src/__tests__/maintenance-locks.test.ts +0 -197
  100. package/src/__tests__/mcp-efficiency.test.ts +0 -288
  101. package/src/__tests__/mcp-related-scopes.test.ts +0 -151
  102. package/src/__tests__/memory-boundaries.test.ts +0 -282
  103. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +0 -362
  104. package/src/__tests__/memory-evolution.test.ts +0 -271
  105. package/src/__tests__/memory-health-rebalance.test.ts +0 -144
  106. package/src/__tests__/memory-lint.test.ts +0 -421
  107. package/src/__tests__/memory-output.test.ts +0 -224
  108. package/src/__tests__/memory-promotion.test.ts +0 -315
  109. package/src/__tests__/memory-utility.test.ts +0 -256
  110. package/src/__tests__/migrate-dedup-ids.test.ts +0 -65
  111. package/src/__tests__/multi-vector.test.ts +0 -242
  112. package/src/__tests__/narrative-architecture.test.ts +0 -456
  113. package/src/__tests__/noise-filter.test.ts +0 -205
  114. package/src/__tests__/noise-prototype-bank.test.ts +0 -280
  115. package/src/__tests__/obsidian-connector.test.ts +0 -363
  116. package/src/__tests__/omitted-reporting.test.ts +0 -98
  117. package/src/__tests__/openai-contract.test.ts +0 -513
  118. package/src/__tests__/pending-review.test.ts +0 -77
  119. package/src/__tests__/pii-redact.test.ts +0 -333
  120. package/src/__tests__/pivot-apply.test.ts +0 -373
  121. package/src/__tests__/pivot-distill-supervisor.test.ts +0 -457
  122. package/src/__tests__/pivot-distill.test.ts +0 -1178
  123. package/src/__tests__/post-retrieval-filter.test.ts +0 -178
  124. package/src/__tests__/ppr-traversal.test.ts +0 -256
  125. package/src/__tests__/prediction-engine.test.ts +0 -349
  126. package/src/__tests__/predictive-prospective-memory.test.ts +0 -416
  127. package/src/__tests__/preference-guard-smoke.test.ts +0 -211
  128. package/src/__tests__/preference-matcher.test.ts +0 -282
  129. package/src/__tests__/preference-slots.test.ts +0 -205
  130. package/src/__tests__/promotion-distinct-sources.test.ts +0 -162
  131. package/src/__tests__/prospective-memory.test.ts +0 -283
  132. package/src/__tests__/pull-from-macbook-sync-only.test.ts +0 -679
  133. package/src/__tests__/query-entity-detector.test.ts +0 -171
  134. package/src/__tests__/query-expander-alias.test.ts +0 -146
  135. package/src/__tests__/query-expander.test.ts +0 -34
  136. package/src/__tests__/recall-gate.test.ts +0 -266
  137. package/src/__tests__/recall-governor.test.ts +0 -227
  138. package/src/__tests__/recall-mode.test.ts +0 -64
  139. package/src/__tests__/resolve-db-path.test.ts +0 -130
  140. package/src/__tests__/resolve-tier.test.ts +0 -64
  141. package/src/__tests__/result-synthesizer.test.ts +0 -198
  142. package/src/__tests__/resume-context.test.ts +0 -4898
  143. package/src/__tests__/retrieval-eval.test.ts +0 -163
  144. package/src/__tests__/retrieval-trace.test.ts +0 -213
  145. package/src/__tests__/retrieve-audit-revision.test.ts +0 -255
  146. package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
  147. package/src/__tests__/retriever-lazy-init-dispatch.test.ts +0 -123
  148. package/src/__tests__/retroactive-boost.test.ts +0 -223
  149. package/src/__tests__/salience-scoring.test.ts +0 -106
  150. package/src/__tests__/scope-inventory.test.ts +0 -200
  151. package/src/__tests__/scope-policy.test.ts +0 -69
  152. package/src/__tests__/scope-suggester.test.ts +0 -80
  153. package/src/__tests__/session-checkpoint.test.ts +0 -291
  154. package/src/__tests__/session-distiller.test.ts +0 -640
  155. package/src/__tests__/session-gc.test.ts +0 -215
  156. package/src/__tests__/skill-engine.test.ts +0 -552
  157. package/src/__tests__/skill-promotion.test.ts +0 -654
  158. package/src/__tests__/skill-verifier.test.ts +0 -122
  159. package/src/__tests__/source-heartbeat.test.ts +0 -254
  160. package/src/__tests__/stable-context-length-contract.test.ts +0 -62
  161. package/src/__tests__/store-patch-metadata.test.ts +0 -235
  162. package/src/__tests__/store-read-consistency.test.ts +0 -122
  163. package/src/__tests__/store-schema-migration.test.ts +0 -87
  164. package/src/__tests__/store-scope-audit.test.ts +0 -81
  165. package/src/__tests__/store-upsert.test.ts +0 -201
  166. package/src/__tests__/supersede-chain.test.ts +0 -154
  167. package/src/__tests__/synthesis-contract.test.ts +0 -252
  168. package/src/__tests__/synthesis-promotion.test.ts +0 -369
  169. package/src/__tests__/temporal-parser.test.ts +0 -241
  170. package/src/__tests__/temporal-validity.test.ts +0 -160
  171. package/src/__tests__/term-registry-terminal-roster.test.ts +0 -127
  172. package/src/__tests__/term-registry.test.ts +0 -121
  173. package/src/__tests__/tool-error-surfacing.test.ts +0 -98
  174. package/src/__tests__/tool-output-compressor.test.ts +0 -160
  175. package/src/__tests__/topic-tag.test.ts +0 -113
  176. package/src/__tests__/trusted-memory.test.ts +0 -867
  177. package/src/__tests__/ui-server-security.test.ts +0 -93
  178. package/src/__tests__/usage-tracker.test.ts +0 -287
  179. package/src/__tests__/utility-weight.test.ts +0 -57
  180. package/src/__tests__/verbatim-self-recall.test.ts +0 -279
  181. package/src/__tests__/version-manager.test.ts +0 -248
  182. package/src/__tests__/workflow-observation-skill-outcome.test.ts +0 -352
  183. package/src/__tests__/workflow-observation.test.ts +0 -433
  184. package/src/__tests__/write-verifier.test.ts +0 -122
package/README_CN.md CHANGED
@@ -26,8 +26,126 @@
26
26
 
27
27
  编程 Agent 每开一个窗口就失忆。项目配置、调试决策、实体映射——散落在 Claude Code、Codex、Kimi、Antigravity 以及你打开的每一个终端里,互相不通。
28
28
 
29
- RecallNest 的解法:**一个 LanceDB 驱动的记忆层,供你的编程 Agent 共读共写**。一个窗口存入的上下文,另一个窗口自动召回。会话退出时 checkpoint,启动时 resume。记忆会衰减、演化、自组织——不是简单的日志堆积。
29
+ RecallNest 是**一个 LanceDB 驱动的记忆层,你所有的 Agent 共读共写**。一个窗口存入的上下文,另一个窗口能召回。会话退出时 checkpoint,启动时 resume。记忆会衰减、演化、自组织——它不是一堆等你 grep 的日志。
30
30
 
31
+ ### 一次召回长什么样
32
+
33
+ ```text
34
+ Query : deploy rollback
35
+ Hits : 5
36
+
37
+ # ID Score Category Tier Source Date Age Retrieval Path
38
+ 1 ee79037a 46.1% cases peripheral cc 2026-08-25 2d vector
39
+ [assistant] Rolled back to the previous image and pinned the digest so the next…
40
+ prov : evidence/transcript-ingest
41
+ imgs : 52 agent-made in this session · read sess=dca70d4a
42
+ ```
43
+
44
+ 这一小块里有三样东西,几乎就是整个设计:
45
+
46
+ - **`Source cc` · `Age 2d`** —— 它出自两天前的一个 Claude Code 窗口,而你正在另一个终端、
47
+ 甚至另一台机器上读它。整个项目就建在这个前提上。
48
+ - **`prov : evidence/…`** —— 每一行都标着自己待在哪一层。从对话里刮下来的碎片,永远没资格
49
+ 冒充你真正做过的决定;要变成稳定记忆,得另走一道带证据门槛的提升流程。
50
+ - **`imgs : …`** —— 那个 session 里有 52 张图,**一张都没进数据库**。这一行的存在只是让你知道
51
+ 那儿有东西可看,而产生它没花一次模型调用、没多一个向量、没占一份存储。
52
+
53
+ 最后这条就是整套做法的缩影:**只存"让一样东西能被找到"所需的部分,不存"将来可能被问到"的一切。**
54
+ 完整的推理——包括最显然的那种实现错在哪两个地方——在
55
+ [图片:可寻址,而不是被编码](#图片可寻址而不是被编码)。
56
+
57
+
58
+ ## 核心能力
59
+
60
+ ### 接入与安装
61
+
62
+ | 能力 | 说明 |
63
+ |---|---|
64
+ | **CC Plugin** | 一行命令装入 Claude Code,无需手动配置 MCP |
65
+ | **共享索引** | 所有客户端共用同一个 LanceDB 存储,跨机指向 `ssh` 即可共用真相源 |
66
+ | **双通道接入** | MCP(stdio)给 CLI agent 与能填 MCP 配置的 GUI app + HTTP API 给只会发请求的客户端 |
67
+ | **一键接入** | 集成脚本同时安装 MCP 和 continuity 规则 |
68
+
69
+ ### 检索与连续性
70
+
71
+ | 能力 | 说明 |
72
+ |---|---|
73
+ | **混合检索** | 6 通道:向量 + BM25 + L0/L1/L2 多向量 + KG 图(PPR) |
74
+ | **4 套检索策略** | default、writing、debug、fact-check —— 按任务类型调优 |
75
+ | **会话连续性** | `checkpoint_session` + `resume_context`(full/light/summary 三种模式)+ 仓库状态守卫 |
76
+ | **会话蒸馏** | 3 层对话压缩:微缩 → LLM 结构化摘要 → 知识提取 |
77
+ | **对话导入** | 支持 Claude Code、Claude.ai、ChatGPT、Slack、纯文本 |
78
+ | **Topic Tags** | scope 内 topic 分区,自动检测,搜索时可过滤 |
79
+ | **关联 scope 侧栏** | 显式传 `includeRelatedScopes` 时,按 `scopeRelations` 白名单额外检索,并和主 scope 排序分开展示 |
80
+
81
+ ### 记忆生命周期与治理
82
+
83
+ | 能力 | 说明 |
84
+ |---|---|
85
+ | **记忆演化** | Supersede 链、衰减评分、LLM 重要性、聚合、归档 |
86
+ | **显式升级** | Evidence → Durable Memory,带冲突守卫、合并决议、审计日志 |
87
+ | **隐私分级** | 4 级(`ephemeral` / `private` / `durable` / `shared`)+ 级联遗忘 |
88
+ | **准入控制** | 写入时门控:噪音过滤、重要性下限、去重、限流 |
89
+ | **Memory Lint** | 矛盾、重复、过期、孤儿检测 + 健康评分 |
90
+ | **离线整合** | `dream` 命令:聚类、合并、修剪积累的记忆 |
91
+
92
+ ### 推理与结构
93
+
94
+ | 能力 | 说明 |
95
+ |---|---|
96
+ | **Knowledge Graph** | 实体关系图 + PPR 算法,支持多跳问题 |
97
+ | **建构式检索** | 多源候选扩展 + 溯源锚定的上下文重建 |
98
+ | **叙事架构** | 三层自传式元数据(生命阶段 → 一般事件 → 具体事件) |
99
+ | **Skill Memory** | 存储、检索、自动提升来自重复模式的可执行技能 |
100
+ | **预测式提醒** | 行为信号预测引擎,主动浮现"你可能需要这个" |
101
+ | **6 类记忆** | profile、preferences、entities、events、cases、patterns —— 类别分化合并策略 |
102
+
103
+ ### 可视化与运维
104
+
105
+ | 能力 | 说明 |
106
+ |---|---|
107
+ | **Dashboard** | Web UI 首页:统计卡片、类别分布、增长趋势、健康概览 |
108
+ | **Workflow Observation** | 专门的 append-only 工作流观测层,不混入普通 memory |
109
+ | **结构化资产** | Pin、Brief、Distill —— 不只是原始日志 |
110
+ | **Data Checkup** | 记忆存储数据质量健康检查(含数据源健康) |
111
+ | **数据源心跳** | 按数据源自动追踪 ingest 健康状态,过期告警 |
112
+ | **导出图谱** | 导出交互式 HTML 知识图谱可视化 |
113
+ | **批量操作** | 单次调用存储最多 20 条记忆,自带去重 |
114
+ | **Connector 框架** | 标准 connector-v1 格式接入外部数据源,附带适配器示例 |
115
+
116
+ ---
117
+
118
+ ## 架构
119
+
120
+ ```
121
+ CLIENTS ACCESS CORE ENGINE STORAGE
122
+ ────────────────────── ─────────────────────── ──────────────────────────── ──────────────────────
123
+
124
+ Claude Code MCP over stdio Retriever LanceDB
125
+ Codex ───▶ 44 tools, 3 tiers ───▶ vector + BM25 + RRF ───▶ vector + columnar
126
+ Kimi · Antigravity Classifier · 6 categories
127
+ Doubao desktop Context composer
128
+ HTTP API :4318 resume_context Jina embeddings v5
129
+ your scripts · cron ───▶ 21 endpoints ───▶ Decay · Weibull half-life ─▶ 1024-dim, task-aware
130
+ Conflict · audit + merge
131
+ phone app ───▶ read-only gateway ───▶ Capture: evidence → durable
132
+ :8791, token-gated
133
+ ```
134
+
135
+ > 图内保留英文术语:等宽字体下中文占两格,混排会把对齐打乱。四段从左到右分别是
136
+ > 客户端、接入层(MCP / HTTP / 只读网关)、核心引擎、存储层。
137
+
138
+ ### 内部设计
139
+
140
+ - **L0 / L1 / L2 动态折叠** —— 每条记忆存储 3 个粒度层(一句话 / 要点概要 / 完整内容);检索时根据相关性分数和 token 预算动态选择返回哪个层级
141
+ - **Weibull 衰减 + 情绪调制** —— 记忆沿参数化 Weibull 曲线衰减;情绪显著性可额外延长半衰期最高 30%
142
+ - **向量预筛 + LLM 去重** —— 90% 的去重决策用低成本余弦相似度(≥ 0.92);仅临界情况调用 LLM 判断
143
+ - **类别分化合并策略** —— `profile` 和 `preferences` 采用冲突合并(新版覆盖);`events` 和 `cases` 采用追加(保留历史)
144
+ - **展示分 vs 淘汰分双轨制** —— 检索使用双轨评分:tier floor 防止核心记忆被淘汰,decay boost 让新鲜记忆临时浮现而不永久挤掉稳定记忆
145
+
146
+ > 完整架构详解:[`docs/architecture.md`](docs/architecture.md)
147
+
148
+ ---
31
149
 
32
150
  ## 谁能接上
33
151
 
@@ -161,265 +279,70 @@ bun run src/cli.ts doctor
161
279
 
162
280
  ---
163
281
 
164
- ## Web UI
282
+ ## 图片:可寻址,而不是被编码
165
283
 
166
- <p align="center">
167
- <img src="assets/dashboard.png" alt="RecallNest Dashboard" width="800" />
168
- <br><em>Dashboard —— 总量、类别分布、健康评分、增长趋势一目了然。</em>
169
- </p>
284
+ 对话里有图,而文本记忆层没有。通常的答案是上一个多模态 embedding 模型,把每张图编码进
285
+ 和文字同一个向量空间。那个答案对相册和商品库是对的,放在这里却是错的形状,理由很便宜:
286
+ **在对话里,一张图几乎从不孤零零地出现。** 它总被"你看这个报错"包着,而紧跟其后的回复
287
+ 通常已经描述了图里是什么。图周围的文字,本来就是这张图的索引。缺的从来不是对像素做语义
288
+ 检索,而是**知道那儿有一张图**。
170
289
 
171
- <p align="center">
172
- <img src="assets/screenshots/ui-full.png" alt="RecallNest Search Workbench" width="800" />
173
- <br><em>Search Workbench —— 混合检索 + Topic Tag 过滤 + 4 套检索策略 + Skills 浏览 + 资产管理。</em>
174
- </p>
290
+ 所以 RecallNest 不编码图片。它只记下**这条记忆所属的 session 里有几张图**,剩下的交给你判断
291
+ 要不要去翻原始对话。图的含义在需要的那一刻、由当时在问的那个模型现场解读。
175
292
 
176
- <p align="center">
177
- <img src="assets/knowledge-graph.png" alt="RecallNest Knowledge Graph" width="800" />
178
- <br><em>Knowledge Graph —— 交互式力导向图,语义桥接揭示跨域隐藏关联。</em>
179
- </p>
293
+ 代价值得说清楚:**不需要多模态模型,不重新 embedding,不存图片,不改动任何一个向量。**
294
+ 给存量的 21,319 条记忆补上这个标记,只动了 metadata。
180
295
 
181
- ```bash
182
- bun run src/ui-server.ts
183
- # → http://localhost:4317
184
- ```
296
+ 这里面有两个不那么显然的选择,而且两次的第一版都是错的。
185
297
 
186
- ---
298
+ ### 粒度取 session,是故意的
187
299
 
188
- ## 核心能力
300
+ 标记数的是整个 session,不是单轮——比第一眼该有的粒度更粗,而粗正是重点。
189
301
 
190
- ### 接入与安装
302
+ 一轮如果只有一张截图、几乎没有文字,它根本过不了长度闸,压根没进过库。在真实对话上实测,
303
+ **含用户贴图的轮次里有 12.5% 是整轮被丢掉的**——而且丢的恰恰是最值钱的那些:七张截图一个字
304
+ 没配,或者「操作步骤在这里」配一张图、而那张图**就是**步骤本身。轮次级的标记,对最该被找到的
305
+ 那批图**无处可挂**。session 级的标记会落到同一个 session 里**其他**进了库的记忆上,而那些才是
306
+ 搜索会捞出来的东西。
191
307
 
192
- | 能力 | 说明 |
193
- |---|---|
194
- | **CC Plugin** | 一行命令装入 Claude Code,无需手动配置 MCP |
195
- | **共享索引** | 所有客户端共用同一个 LanceDB 存储,跨机指向 `ssh` 即可共用真相源 |
196
- | **双通道接入** | MCP(stdio)给 CLI agent 与能填 MCP 配置的 GUI app + HTTP API 给只会发请求的客户端 |
197
- | **一键接入** | 集成脚本同时安装 MCP 和 continuity 规则 |
308
+ 代价不加掩饰:同一个 session 的每条记忆都带同一个数字,所以那些图未必和你眼前这一条有关。
309
+ 输出里写的是 `in this session` 而不是 `in this memory`,就是为了这个。
198
310
 
199
- ### 检索与连续性
311
+ ### 分成两类,因为它们回答的不是同一个问题
200
312
 
201
- | 能力 | 说明 |
202
- |---|---|
203
- | **混合检索** | 6 通道:向量 + BM25 + L0/L1/L2 多向量 + KG 图(PPR) |
204
- | **4 套检索策略** | default、writing、debug、fact-check —— 按任务类型调优 |
205
- | **会话连续性** | `checkpoint_session` + `resume_context`(full/light/summary 三种模式)+ 仓库状态守卫 |
206
- | **会话蒸馏** | 3 层对话压缩:微缩 → LLM 结构化摘要 → 知识提取 |
207
- | **对话导入** | 支持 Claude Code、Claude.ai、ChatGPT、Slack、纯文本 |
208
- | **Topic Tags** | scope 内 topic 分区,自动检测,搜索时可过滤 |
209
- | **关联 scope 侧栏** | 显式传 `includeRelatedScopes` 时,按 `scopeRelations` 白名单额外检索,并和主 scope 排序分开展示 |
210
-
211
- ### 记忆生命周期与治理
212
-
213
- | 能力 | 说明 |
214
- |---|---|
215
- | **记忆演化** | Supersede 链、衰减评分、LLM 重要性、聚合、归档 |
216
- | **显式升级** | Evidence → Durable Memory,带冲突守卫、合并决议、审计日志 |
217
- | **隐私分级** | 4 级(`ephemeral` / `private` / `durable` / `shared`)+ 级联遗忘 |
218
- | **准入控制** | 写入时门控:噪音过滤、重要性下限、去重、限流 |
219
- | **Memory Lint** | 矛盾、重复、过期、孤儿检测 + 健康评分 |
220
- | **离线整合** | `dream` 命令:聚类、合并、修剪积累的记忆 |
221
-
222
- ### 推理与结构
223
-
224
- | 能力 | 说明 |
225
- |---|---|
226
- | **Knowledge Graph** | 实体关系图 + PPR 算法,支持多跳问题 |
227
- | **建构式检索** | 多源候选扩展 + 溯源锚定的上下文重建 |
228
- | **叙事架构** | 三层自传式元数据(生命阶段 → 一般事件 → 具体事件) |
229
- | **Skill Memory** | 存储、检索、自动提升来自重复模式的可执行技能 |
230
- | **预测式提醒** | 行为信号预测引擎,主动浮现"你可能需要这个" |
231
- | **6 类记忆** | profile、preferences、entities、events、cases、patterns —— 类别分化合并策略 |
232
-
233
- ### 可视化与运维
234
-
235
- | 能力 | 说明 |
236
- |---|---|
237
- | **Dashboard** | Web UI 首页:统计卡片、类别分布、增长趋势、健康概览 |
238
- | **Workflow Observation** | 专门的 append-only 工作流观测层,不混入普通 memory |
239
- | **结构化资产** | Pin、Brief、Distill —— 不只是原始日志 |
240
- | **Data Checkup** | 记忆存储数据质量健康检查(含数据源健康) |
241
- | **数据源心跳** | 按数据源自动追踪 ingest 健康状态,过期告警 |
242
- | **导出图谱** | 导出交互式 HTML 知识图谱可视化 |
243
- | **批量操作** | 单次调用存储最多 20 条记忆,自带去重 |
244
- | **Connector 框架** | 标准 connector-v1 格式接入外部数据源,附带适配器示例 |
245
-
246
- ---
247
-
248
- ## v3.0 新增:一个受支持的运行时,和终于能被用起来的结论
249
-
250
- v3.0 是大版本,原因有两个:一个在安装时看得见,一个在记忆的行为里。
251
-
252
- **运行时边界提到了 Node 22。** RecallNest 一直带着 `openai@4`,它拽着
253
- `formdata-node` → `node-domexception` 这条弃用链。openai 从 v5 起每个版本都是零依赖,
254
- 所以升到哪一版这条链都会消失 —— 但 v7 自己声明 `engines.node >= 22.0.0`,于是
255
- 「抬 Node 边界」和「甩掉弃用依赖链」变成同一件事,而不是两件。`engines.node` 现在是
256
- `>=22`。这是本次大版本里唯一的破坏性改动。
257
-
258
- **合成出的结论终于能进稳定记忆。** `dream` 把 cluster insight 和 cross-memory pattern
259
- 写在 evidence 层是有意的 —— 模型对自己记忆的再加工是通往源头的线索,不是压过源头的
260
- 权威 —— 但稳定记忆的选取对 evidence 层一律拒收。后果是绝对的:一条合成结论无论证据
261
- 多充分,下游都没法靠它。`promote_synthesis`(MCP)与 `recallnest promote-synthesis`
262
- (CLI)给了它一条路,判据是这条结论自己那份经过校验的证据集,而不是「重复出现几次」
263
- —— 合成物本身就是跨条目聚合,要求它重复是把一件事数了几次。合成行本身一个字不改;
264
- 晋升写的是另一条 durable 记录,带 `promotedFrom` 指回去,走的是和其他晋升同一条通道、
265
- 同一套 `canonicalKey` 去重。
266
-
267
- 本次还有:
268
-
269
- - **retrieve 审计行会说清服务了什么。** 它以前只记查询词和命中数。而信念是就地修订的
270
- —— id 不变、版本号加一、旧正文作为 `superseded` 行留下 —— 所以「memory X 被检索了」
271
- 这句话跨过任何一次信念变更都是有歧义的。现在每条结果的 id、版本、生命周期状态和
272
- boundary 都记下来;列表有上限,而且**被截断时会说自己被截断了**。
273
- - **embeddings 与 chat completions 有了 HTTP 契约测试。** 此前所有测试都把 SDK client
274
- 换成桩函数,没有一条碰过 socket,传输层的回归会全绿着放过去。新测试驱动真的类、经真的
275
- SDK、打到本地回环服务器,覆盖成功 / 错误 / 超时三态,以及默认 OpenAI 形态加上 Jina、
276
- Qwen 兼容端点。零出网、零凭据。
277
- - **修复:被限流时反而会猛打对方。** 是上面那批测试当场查出来的。embedder 遇到
278
- context-length 错误会分块重试,而分块器对短文本原样返回,判定「这是 context 错误」的
279
- 那条正则又会命中限流措辞 —— 于是一个 429 会拿同一段文本无限重打。实测 5 秒 61,000 多
280
- 个请求,对着一个正在叫我们慢一点的端点。现在失败是有界的。
281
- - **一个已知的召回缺口变成了能跑的回归。** 一条又长、又旧、很少被读的记忆,可能用它自己
282
- 正文里的原话都召不回,而短的、新的、被频繁读的条目排在它前面。这是排序问题不是过滤
283
- 问题 —— 那条记忆过了所有阈值,然后排最后一名。复现已经进仓;排序的修复不在本次。
284
- - **MCP 工具 44 个**(三层),此前 43 个。
285
-
286
- ### 从 v2.6 升级
287
-
288
- - **需要 Node 22 或更新版本。** 用 Bun 的不受影响。这是本次唯一的破坏性改动。
289
- - 已有 LanceDB 数据原地打开,不需要导出导入。
290
- - `promote_synthesis` 默认 dry-run,不传 `dryRun=false` 不写任何东西。
291
- - `audit.jsonl` 里检索那类行变大了(它现在列出服务了哪几条)。轮转仍是手动的,接近
292
- 50 MB 时归档一下。
293
-
294
- ### 关于 npm 历史的一句说明
295
-
296
- npm 上的 `recallnest@2.6.1` 是一次维护发布,发布时这个包还没有配置 Trusted Publishing,
297
- 所以它不带构建 provenance。这是它当时怎么发的一个事实,不是包本身的缺陷,而且**无法追认**
298
- —— 同一个 npm 版本不能为了补 provenance 再发一次。
299
-
300
- ---
301
-
302
- ## v2.6 新增:跨进程一致性与可靠分发
303
-
304
- v2.6 把 v2.5.4 以来的开发整理成一版可升级、可安装、版本口径一致的正式候选:
305
-
306
- - **跨进程即时可见** —— LanceDB 默认每次读取都检查外部提交,常驻 MCP/API/UI 无需重启即可看见 CLI ingest 写入。可用 `RECALLNEST_READ_CONSISTENCY_INTERVAL=<秒>` 设置允许的延迟窗口,或设为 `off` 恢复旧版不检查行为。
307
- - **更安全的记忆演化** —— 信念变化不再原地覆盖,旧记录保留为 `superseded`;程序性记忆免于时间衰减;冷启动和长度归一化不再压低短实体查询与稳定记忆。
308
- - **更可靠的整合管线** —— `dream` 增加失败分型、总时长预算、聚类前向量回填,以及“成功必须真的产出结果”的断言。
309
- - **更完整的对话来源** —— Kimi、AGY/Antigravity 和 minis 已同步纳入 ingest、scope 边界与术语识别。
310
- - **一个公开版本号** —— npm 元数据、CLI `--version`、MCP 握手、HTTP 健康接口和 Claude Code marketplace 统一遵守 `2.6.0` 发布契约。
311
- - **真正可安装的 Claude Code 插件** —— 安装后自动注册 43 个 MCP 工具与连续性 skill,通过敏感插件配置接收 Jina key,并把配置与 LanceDB 数据放进 Claude Code 的持久插件数据目录。
312
-
313
- ### 从 v2.5.4 升级
314
-
315
- - 现有 LanceDB 数据原地打开;缺少新字段的旧表会自动迁移,空表也覆盖。
316
- - 插件数据位于版本缓存之外,更新插件不会清空;手动 clone 仍沿用现有 `config.json` 与数据库路径。
317
- - 检索操作现已写入审计日志。`audit.jsonl` 暂无自动轮转,接近 50 MB 时建议人工归档。
318
- - `RECALLNEST_LAYER_ADMISSION` 仍是显式开启项(`observe` 或 `on`),默认 `off`。
319
-
320
- ---
321
-
322
- ## v2.1 新增:记忆哲学驱动的架构升级
323
-
324
- v2.0 建立了完整的记忆操作平台;v2.1 加入了记忆哲学驱动的记忆行为。
325
-
326
- 来自 9 个记忆哲学研究维度的 5 项工程升级:
327
-
328
- - **情绪感知衰减** *(情感记忆理论)* —— 带有强烈情绪的记忆衰减慢 20-30%。基于关键词的情绪检测计算 `salience`(记忆显著性),注入 Weibull 半衰期公式和重新平衡的 4 因子演化评分。零 LLM 成本。
329
-
330
- - **记忆伦理层** *(遗忘权 / GDPR 第 17 条)* —— 四级隐私:`ephemeral`(临时)/ `private`(私密)/ `durable`(持久)/ `shared`(共享)。级联遗忘引擎贯穿 KG 三元组、演化链、Pin 资产和摘要。完整审计日志。`forget_memory` MCP 工具支持 Agent 驱动的删除。
331
-
332
- - **自传叙事架构** *(叙事身份理论 / Conway 三层模型)* —— 记忆标注 `生命阶段 → 一般事件 → 具体事件` 层级,正交于现有 6 类别。检索自动拉取叙事兄弟。上下文渲染按生命阶段分组。支持中英文的规则式标注器。
333
-
334
- - **建构式检索** *(模拟理论 / Michaelian)* —— 不再返回原始存储文本,而是从扩展候选集(KG 邻居 + 演化链 + 聚类成员 + 叙事兄弟)中重建上下文。Source-map 语义覆盖率替代词汇重叠。矛盾检测与标记。
335
-
336
- - **预测式前瞻记忆** *(精神时间旅行 / Tulving)* —— 启发式预测引擎从行为信号中浮现"你可能需要这个"提醒:过期的 checkpoint 待办事项、被纠正的工作流观察、高频休眠记忆和未覆盖的查询主题。零 LLM 成本。7 天未接受自动过期。
337
-
338
- ---
339
-
340
- ## v2.2 新增:检索质量强化
341
-
342
- v2.1 加入了哲学记忆行为;v2.2 补齐前沿扫描(ACC、PI-LLM、TSM)发现的最后三个引擎层空白。
343
-
344
- - **记忆置信度元标签** *(ACC / 双过程不确定性量化)* —— 每条记忆携带结构化 `ConfidenceMetadata`(分数 + 可靠性层级:`direct` 用户亲述 / `inferred` LLM 推断 / `hearsay` 二手转述)。写入时根据 source 自动赋值(manual=0.9, agent=0.7, conversation_import=0.5)。检索评分加权置信度。`resume_context` 对低置信条目标注 `[低置信]`。
345
-
346
- - **干扰检测 + 主动遗忘门** *(PI-LLM / SleepGate)* —— 语义聚簇检测识别竞争检索的近重复记忆群。增强版 RIF 每簇仅保留 top-K(默认 3),多余的降权 50% 而非删除。写入时预警:scope 内累积 ≥5 条高相似活跃记忆时,最弱的标记 `pending_review`。`data_checkup` 报告干扰密度。
347
-
348
- - **时间有效性窗口** *(TSM / TiMem / Zep)* —— `store_memory` 接受 `validUntil`(过期时间)和 `eventTime`(事件发生时间)。`search_memory` 支持 `validAt`(时间点查询)和 `includeExpired`(降权 80% 而非隐藏)。auto-GC 对过期记忆施加 2 倍衰减加速。
349
-
350
- - **Usage-adjusted Auto-GC** *(默认关闭)* —— `RECALLNEST_USAGE_DECAY=true` 且建构式检索 use 信号开启时,GC 专用分支才会对 cold 记忆的 frequency 项打折;在线检索排序不读取这个分支。
351
-
352
- ---
353
-
354
- ## v2.3 新增:Connector 生态 + 数据源健康
355
-
356
- v2.2 强化了检索质量;v2.3 通过标准 Connector 框架和运维健康监控,把 RecallNest 开放给外部数据源。
357
-
358
- - **Connector-v1 标准** *(GB-2)* —— 一套 JSON 格式(`ConnectorOutputV1`),任何外部脚本都可以输出。Obsidian 笔记、邮件、RSS、日志文件——标准化一次,走完整的 dedup/embed/extract 管线入库。规范见 [`docs/connector-spec.md`](docs/connector-spec.md),适配器骨架见 [`connectors/examples/`](connectors/examples/)(email、logs、RSS)。
313
+ | 类别 | 是什么 | 回答什么问题 |
314
+ |---|---|---|
315
+ | user-pasted | 人贴进消息里的图 | *我发过的那张截图在哪?* |
316
+ | agent-made | 这个 session 产生的其余所有图 | *那个页面当时长什么样?我当时生成的图是哪张?* |
359
317
 
360
- - **Obsidian Vault 接入** *(GB-1)* —— 官方 Obsidian 连接器:扫描 `.md` 文件,提取 frontmatter + wikilink,文件夹结构映射为 tag。一条命令:`lm ingest --obsidian /path/to/vault`。
318
+ 只留第一类很诱人——人翻自己的记忆,想找的是自己的截图。但一个 agent 回溯自己干过的活,
319
+ 要的是第二类:它画的示意图、它截的渲染结果、它为一篇文章配的插图。**1,767 个带图 session 里,
320
+ 有 1,103 个一张人贴的图都没有。** 只留第一类,这些 session 就全哑了——而它们恰恰是 agent
321
+ 做了大量视觉工作的那些。
361
322
 
362
- - **数据源健康监控** *(GB-3)* —— 每次 connector ingest 写心跳到 `data/source-heartbeat.json`。`data_checkup` 标记过期数据源(>7 天 warning,>30 天 error)。`doctor --ci` 按数据源展示心跳摘要(如"obsidian: 2h ago")。
323
+ ### 第二类是补集,不是清单
363
324
 
364
- ---
325
+ 第一版的实现是靠**枚举**定义"AI 产的图":`tool_result` 里的、`payload.output` 里的、
326
+ `tool.result` 里的。每一个位置都真实存在,这份清单依然是错的,因为**图片可能出现的方式只会
327
+ 越来越多**,而枚举会悄无声息地漏掉它没预料到的那一种。
365
328
 
366
- ## 架构
329
+ 所以第二类改成了补集:数出这条记录里所有的图片信号,减去能明确认定为"人贴的",剩下的
330
+ 一律归入,不问它从哪儿来。在 9,619 份对话记录上:
367
331
 
368
- ```
369
- ┌──────────────────────────────────────────────────────────┐
370
- │ 客户端层 │
371
- ├──────────┬──────────┬──────────┬──────────────────────────┤
372
- │ Claude │ Gemini │ Codex │ 自定义 Agent / curl │
373
- │ Code │ CLI │ │ │
374
- └────┬─────┴────┬─────┴────┬─────┴──────┬──────────────────┘
375
- │ │ │ │
376
- └──── MCP (stdio) ───┘ HTTP API(端口 4318)
377
- │ │
378
- ▼ ▼
379
- ┌──────────────────────────────────────────────────────────┐
380
- │ 集成层 │
381
- │ ┌─────────────────────┐ ┌────────────────────────────┐ │
382
- │ │ MCP Server │ │ HTTP API Server │ │
383
- │ │ 44 个工具 │ │ 21 个端点 │ │
384
- │ └─────────┬───────────┘ └──────────┬─────────────────┘ │
385
- └────────────┼─────────────────────────┼───────────────────┘
386
- └──────────┬──────────────┘
387
- ▼
388
- ┌──────────────────────────────────────────────────────────┐
389
- │ 核心引擎 │
390
- │ │
391
- │ ┌────────────┐ ┌────────────┐ ┌─────────────────────┐ │
392
- │ │ 检索器 │ │ 分类器 │ │ 上下文编排器 │ │
393
- │ │(向量 + │ │(6 类分类) │ │(resume_context) │ │
394
- │ │ BM25 + RRF)│ │ │ │ │ │
395
- │ └────────────┘ └────────────┘ └──────────────────────┘ │
396
- │ ┌────────────┐ ┌────────────┐ ┌─────────────────────┐ │
397
- │ │ 衰减引擎 │ │ 冲突引擎 │ │ 捕获引擎 │ │
398
- │ │(Weibull) │ │(审计 + │ │(evidence → durable)│ │
399
- │ │ │ │ 合并) │ │ │ │
400
- │ └────────────┘ └────────────┘ └──────────────────────┘ │
401
- └──────────────────────────┬───────────────────────────────┘
402
- ▼
403
- ┌──────────────────────────────────────────────────────────┐
404
- │ 存储层 │
405
- │ ┌─────────────────────┐ ┌────────────────────────────┐ │
406
- │ │ LanceDB │ │ Jina Embeddings v5 │ │
407
- │ │(向量 + 列式存储) │ │(1024 维,任务感知) │ │
408
- │ └─────────────────────┘ └────────────────────────────┘ │
409
- └──────────────────────────────────────────────────────────┘
410
- ```
411
-
412
- ### 内部设计
413
-
414
- - **L0 / L1 / L2 动态折叠** —— 每条记忆存储 3 个粒度层(一句话 / 要点概要 / 完整内容);检索时根据相关性分数和 token 预算动态选择返回哪个层级
415
- - **Weibull 衰减 + 情绪调制** —— 记忆沿参数化 Weibull 曲线衰减;情绪显著性可额外延长半衰期最高 30%
416
- - **向量预筛 + LLM 去重** —— 90% 的去重决策用低成本余弦相似度(≥ 0.92);仅临界情况调用 LLM 判断
417
- - **类别分化合并策略** —— `profile` 和 `preferences` 采用冲突合并(新版覆盖);`events` 和 `cases` 采用追加(保留历史)
418
- - **展示分 vs 淘汰分双轨制** —— 检索使用双轨评分:tier floor 防止核心记忆被淘汰,decay boost 让新鲜记忆临时浮现而不永久挤掉稳定记忆
332
+ | | 枚举 | 补集 |
333
+ |---|---|---|
334
+ | AI 产的图 | 5,812 | **10,938** |
335
+ | 有图的 session | 1,507 | **1,767** |
336
+ | 人贴的图 | 1,629 | 1,629 |
419
337
 
420
- > 完整架构详解:[`docs/architecture.md`](docs/architecture.md)
338
+ **枚举漏掉了 5,126 张图和 376 个 session**,接近一半。漏得最多的是**生图**——它待在那份清单
339
+ 知道的两个容器之外。而人贴的图在两种口径下数字完全一致,这正是关键的校验点:放宽第二类
340
+ 没有污染精确的第一类。有一条回归测试钉住这件事:它喂给解析器一个源码里从未出现过的
341
+ `image_generation_call`,断言它落进第二类;在枚举式实现下那条测试必红。
421
342
 
422
- ---
343
+ 一点诚实的说明:补集数的是*信号*,不是确认过的图片张数。一次生图可能同时留下调用和完成
344
+ 两条记录,被数成两张。这个方向是刻意选的——它要回答的是"这儿有没有东西可看",而不是
345
+ "到底几张"。
423
346
 
424
347
  ## 接口
425
348
 
@@ -566,6 +489,46 @@ bun run src/cli.ts doctor
566
489
 
567
490
  ---
568
491
 
492
+ ## Web UI
493
+
494
+ <p align="center">
495
+ <img src="assets/dashboard.png" alt="RecallNest Dashboard" width="800" />
496
+ <br><em>Dashboard —— 总量、类别分布、健康评分、增长趋势一目了然。</em>
497
+ </p>
498
+
499
+ <p align="center">
500
+ <img src="assets/screenshots/ui-full.png" alt="RecallNest Search Workbench" width="800" />
501
+ <br><em>Search Workbench —— 混合检索 + Topic Tag 过滤 + 4 套检索策略 + Skills 浏览 + 资产管理。</em>
502
+ </p>
503
+
504
+ <p align="center">
505
+ <img src="assets/knowledge-graph.png" alt="RecallNest Knowledge Graph" width="800" />
506
+ <br><em>Knowledge Graph —— 交互式力导向图,语义桥接揭示跨域隐藏关联。</em>
507
+ </p>
508
+
509
+ ```bash
510
+ bun run src/ui-server.ts
511
+ # → http://localhost:4317
512
+ ```
513
+
514
+ ---
515
+
516
+ ## 最近更新
517
+
518
+ **v3.0** 把运行时下限提到 **Node 22**(本次唯一的破坏性变更,Bun 用户不受影响),
519
+ 并给"合成出来的结论"开了一条通往稳定记忆的路:`dream` 产出的洞察现在可以凭它自己
520
+ 那套已验证的证据被提升,而不是永远卡在证据层、下游谁也不敢依赖。
521
+
522
+ 同批还修了一个限流回复引发的无限请求风暴——实测在一个正让我们慢下来的接口上
523
+ 五秒打了六万多次请求。它是被新加的 HTTP 契约测试抓出来的:那些测试让真实的客户端类
524
+ 去打一个本地回环服务,而不是把 SDK 打桩糊过去。
525
+
526
+ 已有的 LanceDB 数据原地打开,不需要导出导入。
527
+
528
+ **完整变更历史(v3.0 到 v1.0,含每个版本的升级说明)在
529
+ [CHANGELOG.md](CHANGELOG.md)。**
530
+
531
+
569
532
  ## 多语言支持
570
533
 
571
534
  RecallNest 开箱即用支持英文。如需多语言记忆(中文、日文、泰文及 20+ 种语言),安装 [babel-memory](https://github.com/AliceLJY/babel-memory) 及所需语言包:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recallnest",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "RecallNest — MCP-native, local-first memory search and distillation for AI conversations",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -22,7 +22,8 @@
22
22
  "eval/continuity/*.json",
23
23
  "integrations/",
24
24
  "README_CN.md",
25
- "src/"
25
+ "src/",
26
+ "!src/**/__tests__/**"
26
27
  ],
27
28
  "scripts": {
28
29
  "search": "bun run src/cli.ts search",
@@ -461,6 +461,15 @@ export async function writeDurableEntry(
461
461
  entry: MemoryEntry;
462
462
  disposition: "stored" | "updated" | "deduped" | "promoted" | "conflict";
463
463
  conflictId?: string;
464
+ /**
465
+ * True when this write REPLACED the content of an existing canonical row
466
+ * (a belief version was archived). Two different paths can do that — the
467
+ * latest-wins branch reports `updated`, while the deterministic-id branch
468
+ * below still reports `stored` — so disposition alone cannot tell them
469
+ * apart. Consumers that mirror memory content elsewhere (KG triples) must
470
+ * drop the old projection when this is set.
471
+ */
472
+ supersededPrevious?: boolean;
464
473
  }> {
465
474
  const matches = await findCanonicalMatches(deps.store, params.canonicalKey);
466
475
  const categoryMatches = matches.filter((entry) => entry.category === params.category);
@@ -605,6 +614,7 @@ export async function writeDurableEntry(
605
614
  return {
606
615
  entry: updated,
607
616
  disposition: params.promotedFrom ? "promoted" : "updated",
617
+ supersededPrevious: true,
608
618
  };
609
619
  }
610
620
 
@@ -617,6 +627,7 @@ export async function writeDurableEntry(
617
627
  ? deterministicId(params.scope, params.canonicalKey)
618
628
  : undefined;
619
629
  let metadataForStore = params.metadata;
630
+ let supersededPrevious = false;
620
631
 
621
632
  if (targetId && deps.store.getById) {
622
633
  const rowAtTargetId = await deps.store.getById(targetId);
@@ -629,6 +640,7 @@ export async function writeDurableEntry(
629
640
  { now },
630
641
  );
631
642
  metadataForStore = buildSupersedingBeliefMetadata(params.metadata, previousEvo, historyId, now);
643
+ supersededPrevious = true;
632
644
  }
633
645
  }
634
646
 
@@ -647,6 +659,7 @@ export async function writeDurableEntry(
647
659
  return {
648
660
  entry: stored,
649
661
  disposition: params.promotedFrom ? "promoted" : "stored",
662
+ supersededPrevious,
650
663
  };
651
664
  }
652
665
 
@@ -1106,7 +1119,7 @@ export async function persistMemory(
1106
1119
  metadata = JSON.stringify(parsed);
1107
1120
  }
1108
1121
 
1109
- const { entry, disposition, conflictId } = await writeDurableEntry(deps, {
1122
+ const { entry, disposition, conflictId, supersededPrevious } = await writeDurableEntry(deps, {
1110
1123
  text: input.text,
1111
1124
  vector,
1112
1125
  category: input.category,
@@ -1124,9 +1137,18 @@ export async function persistMemory(
1124
1137
  const entryPrivacyTier = parsePrivacyTier(metadata);
1125
1138
  const kgAllowed = entryPrivacyTier !== "ephemeral" && entryPrivacyTier !== "private";
1126
1139
  if (deps.kgExtractor && disposition !== "deduped" && kgAllowed) {
1127
- deps.kgExtractor
1128
- .extractAndStore(input.text, entry.id, resolvedScope)
1129
- .catch(() => {}); // Silently ignore — KG extraction must never block memory writes
1140
+ const kgExtractor = deps.kgExtractor;
1141
+ // When this write replaced existing content, the edges extracted from the OLD
1142
+ // text are still in the graph: triple ids are content-derived, so re-extracting
1143
+ // merely adds new rows beside the stale ones and both compete during traversal.
1144
+ // Purge first, then re-extract — strictly ordered, or the purge would race ahead
1145
+ // and delete what we just wrote.
1146
+ const purged = supersededPrevious
1147
+ ? kgExtractor.purgeForMemory(entry.id)
1148
+ : Promise.resolve();
1149
+ purged
1150
+ .then(() => kgExtractor.extractAndStore(input.text, entry.id, resolvedScope))
1151
+ .catch(() => {}); // Silently ignore — KG upkeep must never block memory writes
1130
1152
  }
1131
1153
 
1132
1154
  // F-1: Audit log — record store operation (non-blocking, silent on failure)
@@ -1,6 +1,7 @@
1
1
  import type { PinAsset } from "./memory-assets.js";
2
2
  import type { RetrievalResult } from "./retriever.js";
3
3
  import type { SessionCheckpointRecord } from "./session-schema.js";
4
+ import { isFallbackSummary } from "./session-engine.js";
4
5
  import { selectPinnedContext } from "./context-composer-pins.js";
5
6
  import { cleanText, dedupeText } from "./context-composer-text.js";
6
7
  import { selectStableResults } from "./context-composer-stable-selection.js";
@@ -74,7 +75,10 @@ function buildCheckpointStableContext(
74
75
  if (!checkpoint) return [];
75
76
 
76
77
  const items = [
77
- checkpoint.summary ? `Checkpoint summary: ${checkpoint.summary}` : "",
78
+ // 兜底文案不占 slot:它零信息,占着会把真正有内容的 decision / next 挤出 limit。
79
+ checkpoint.summary && !isFallbackSummary(checkpoint.summary)
80
+ ? `Checkpoint summary: ${checkpoint.summary}`
81
+ : "",
78
82
  checkpoint.task ? `Checkpoint focus: ${checkpoint.task}` : "",
79
83
  checkpoint.decisions[0] ? `Checkpoint decision: ${checkpoint.decisions[0]}` : "",
80
84
  checkpoint.nextActions[0] ? `Checkpoint next: ${checkpoint.nextActions[0]}` : "",
package/src/embedder.ts CHANGED
@@ -103,6 +103,24 @@ export interface EmbeddingConfig {
103
103
  normalized?: boolean;
104
104
  /** Enable automatic chunking for documents exceeding context limits (default: true) */
105
105
  chunking?: boolean;
106
+
107
+ /**
108
+ * Per-request timeout (ms) handed to the OpenAI-compatible client.
109
+ *
110
+ * **Unset is the default and keeps the historical behavior**: no `timeout` key is
111
+ * passed to the SDK at all, so the client falls back to its own default (600s in
112
+ * openai v7). That default is deliberately left alone — bulk embedding of long
113
+ * documents is legitimately slow, and lowering it globally would turn working
114
+ * ingests into failures.
115
+ *
116
+ * Worth setting on latency-sensitive callers: the SDK default stacks with the SDK's
117
+ * own 2 retries *and* Embedder's 2 transient retries, so a black-holed endpoint can
118
+ * hold a single embed call for far longer than any caller expects.
119
+ *
120
+ * Non-finite or non-positive values are ignored (treated as unset) rather than
121
+ * throwing, matching how `env-config.ts` validates its other numeric knobs.
122
+ */
123
+ timeoutMs?: number;
106
124
  }
107
125
 
108
126
  // Known embedding model dimensions
@@ -205,9 +223,15 @@ export class Embedder {
205
223
  // Enable auto-chunking by default for better handling of long documents
206
224
  this._autoChunk = config.chunking !== false;
207
225
 
226
+ const timeoutMs =
227
+ typeof config.timeoutMs === "number" && Number.isFinite(config.timeoutMs) && config.timeoutMs > 0
228
+ ? config.timeoutMs
229
+ : undefined;
230
+
208
231
  this.client = new OpenAI({
209
232
  apiKey: resolvedApiKey,
210
233
  ...(config.baseURL ? { baseURL: config.baseURL } : {}),
234
+ ...(timeoutMs !== undefined ? { timeout: timeoutMs } : {}),
211
235
  });
212
236
 
213
237
  this.dimensions = getVectorDimensions(config.model, config.dimensions);