自动落地现在三类:merge(字面重复 ∨ 事实等价 ∧ ≥0.95)· archive(one_off_event 且不像习惯)· **reclassify(新增)**。reclassify 只改 `kind` 一列 (`reclassifyTidyItemSQL` 只写 kind+updated_at),**不动正文、不动作用域、不碰第二条行**。 判据(纯函数 `tidyAutoReclassifyKind`,四个条件全中才自动): ① 恰 1 个 source 且无 target;② 理由 = `kind_mismatch`;③ 建议 kind 合法; ④ **改前 kind 合法且 ≠ 建议值** —— ③④ 是 **revert 抓手**:没有它自动落地就不可回滚。 **边界**:只读 ProposedKind / KindA,**一概不读** ProposedContent / MergedContent / ContentA/B / 分区 / 时间字段。 六条护栏逐条覆盖它:软动作(同一条 apply 路径)/ 每轮上限(三类同占一个池子, 默认 50 硬顶 500,混合时截断如实)/ 审计 `memory.tidy.apply`(`auto=true`, `reclassified=%d` + stats + 日志 + run 审计,**只带计数与理由、不带正文**)/ **可 revert**(`revertTidyReclassify` 按 KindA 写回,且只在当前 kind 仍等于建议值时生效)/ 幂等(候选条件更新 + 行 `AND status='active'` 双闸)/ 开关关闭 ⇒ 连查询都不发。 另补一道本片新增的门:**受限来源**(群聊/陌生人)与跨分区的 reclassify **skipped** (原先只有 merge/archive 有这道门)。 验收(主会话复跑 + 亲自变异):整树 `go build ./...` + vet 绿;memory/gateway 两包 测试绿;我用 -overlay 禁掉「必须真的变了」那一格 ⇒ `…EligibilityBoundary` **3 条子例变红** (改前 kind 缺失/非法/与建议值相同 ⇒ 都"没法回滚")。子代理另做 4 个变异(连正文一起写 / 摘受限来源门 / 去掉 reclassified 计数 / 不要求可回滚抓手)全红。 🔴 **未动的红线(一行未改)**:`archive` 的「习惯类不归档」守卫(design/23 §5.2-1) —— `looksLikeHabit` / `tidyHabitMarkers` / `tidyReasonOneOffEvent` 零改动, `TestTidyAutoNeverArchivesHabitMisjudgedAsOneOff` 仍绿。要连它也自动请主人明说。 ⚠️ 未部署(要一次 build + restart);⚠️ design/23 的 §7「reclassify 交人工」与 §13 开放点 5「全部人工应用」**现已过期**,主会话同步。 ⚠️ 已知遗留(需新授权):历史积压里指向**已被吸收行**的 reclassify 候选会每轮被选中 又永远 `source_not_active` 跳过,白占一次每轮名额;彻底解法是「永久不可用的候选自动 标 rejected」(新行为)。
主人原话:「那么多 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/ 待登记