主人原话:「那么多 link 是干什么的?该删就删,怎么这个记忆整理机制这么保守呢」。 ① **link 停产**(依据 design/23 §14 把它列为「未细化」⇒ 既无落点也无数据模型; `applyable()` 也只认 merge/archive/reclassify ⇒ 应用 link 恒 `skipped`,是纯噪音): - 规则层 `containNone` 判定改为 `keep`(不再产出 501 条不可应用的待办); - `TidyLink` 常量保留,只为**读历史行**与「不可应用」判定; - 存量出路:新端点 `POST /api/v1/admin/memories/candidates/reject?action=&limit=` (**幂等、只改候选 status、不碰记忆**),主会话用它清掉存量 501 条。 ② **轮末自动落地**(默认开,仅两类,判据是纯函数): - `merge`:**字面重复** ∨「事实集合等价 ∧ 相似度 ≥ 0.95」; - `archive`:理由 = `one_off_event` **且不像习惯**(`design/23 §5.2-1` 红线: 习惯类会因正文带钟点被误判成一次性事件,归档它是错的)。 - **不自动**:`reclassify`、低置信 merge、跨分区/跨角色、任何改正文、受限来源 (群聊/陌生人)—— 新增执行面守卫 `tidySkipSourceRestricted`。 ③ **六条护栏**(缺一不可):软动作(复用既有 apply 路径)/ 每轮上限 50(硬顶 500, 超限如实 `Truncated` + 真总数)/ 审计 `memory.tidy.apply`(`auto=true`,不带正文)/ **可 revert**(复用既有回滚)/ 幂等(复用候选状态条件更新)/ **一键关回只产建议** (`$DATA_DIR/config/memory-tidy.json` 的 `autoApply:false`,**零新 env 键**)。 验收(主会话复跑 + 亲自变异):memory 与 gateway 两包 build/vet/test 全绿;我用 -overlay 短路「习惯不归档」守卫 ⇒ TestTidyAutoNeverArchivesHabitMisjudgedAsOneOff 变红(逐字报「作息习惯被自动归档了 …design §5.2-1 红线」)。 ⚠️ 未部署(要 build+restart 才生效);⚠️ 契约欠登记(新端点 B25 + §7.2),主会话补; ⚠️ 真机端到端未跑(不能重启后端)。
主人实测:memory_items 1191 行、候选 77 条全 pending、memory_tidy_runs 仅 1 行、两晚定时没跑。 根因两条(都在这片里修掉): ① **以前根本没有执行面** —— 只有只读试算,候选永远 pending ② **调度被重启饿死** —— interval=24h 的 ticker 从进程启动算起,两天重启十几次 ⇒ 24h 永远走不完 - 应用路径:逐条 / 按 run 批量(默认 100、硬顶 500、超限 truncated)/ revert;**软合并** (status + merged_into/merged_from/merge_count,**绝不改 content、行不删**)+ 审计 (memory.tidy.apply / memory.tidy.revert)+ **幂等**(候选状态条件更新 + 行 status 闸两道) - 调度:改成**到点即跑**(catchUp:无历史+窗口内 / 距上次 run ≥ interval / 窗口起点晚于上次 run), 「上次 run」以 memory_tidy_runs 为**持久事实**;注入时钟用例证明**重启不饿死** - 手动真跑端点 POST /admin/memories/tidy/run(同档鉴权 + 审计 + 回执:新增/合并/软删/耗时) - 候选表加 run_id 列 + 索引(前滚;顺序=建表→补列→建索引,否则既有库会 no such column) - 3 个变异自检(删 catchUp / 去掉两道 status 闸 / truncated 恒 false) ⚠️ 生产库零动作:1191 行真跑与 77 条候选应用**未执行**(按红线由主人确认后主会话执行) ⚠️ 契约段在报告里(candidates 列表/apply/revert/tidy.run),Docs/ 待登记
主人要「现在就想看到在 736 条上它会建议合并哪些」,而不是等 23:00 的低峰首跑。 - 后端:Tidier.DryRun + TidyService.DryRun,**复用同一条扫描→召回→判定→构造候选路径**(抽出 tidyRoundPolicy,生产轮与试算轮只差「候选出口」与「召回降级策略」两处;禁止第二套实现) - 🔴 零副作用:不写 memory_tidy_candidates / memory_tidy_runs,绝不改 memory_items 任何列;机械证明 = 跑前跑后 cyrene.db 与 -wal 的 SHA256 一致(测试断言「表行数不变」) - 失败码诚实:500 tidy_unavailable / 409 tidy_disabled / 500 tidy_recall_unavailable(**embedding 不可用不降级成 0 条**)/ 503 tidy_timeout / 500 tidy_dry_run_failed;**空数组唯一含义 = 算过了没建议** - 端点 POST /api/v1/admin/memories/tidy/dry-run(writeAuth);面板「记忆」页加试算卡(点了才跑、无轮询、三态分开、明写「仅试算,不会改动任何记忆」) - 真机 e2e(741 条活库副本):76 ms / 15 分区 / 53 候选 / 41 skipped / 1 vetoed(low_confidence);样例 merge reason=fact_subset score=1.0000、link reason=similar_but_not_contained score=0.9248 - 变异:dry-run 顺手写库 ⇒ 两条测试红(行数 0→2);自造阈值 ⇒ 红;失败回 200 空数组 ⇒ 红 - 如实登记:Compared=688 被 400/分区护栏截断、候选上限 20/分区被打满 ⇒ 护栏值需在 741 条量级重定(非 bug)