feat(memory): retention_tier 档位列 + defer 与档位合并为同一判定(M37)
主人 2026-10-26 对「记忆分型」裁定「两列」:kind(关于什么)保留,新增 retention_tier(留多久 / 干什么用)。本片把这条轴从列、判定、写入、读侧过滤 一路打通,并把「判第五态 defer」与「判 retention_tier=temp」合并成一个判定。 结构(迁移 0026 + 自举前滚): - memory_items 新增 retention_tier TEXT NOT NULL DEFAULT ''(空串 = 尚未分型, 历史行与受限来源行),索引 idx_memory_items_retention_tier;up/down 均实跑过 (在隔离的生产库副本上 migrate → 建 Store → down → 再 up,5073 行零丢失)。 - NewStore 幂等前滚该列 + 建索引(ensureRetentionSchema),与迁移同形由测试钉住。 判定(retention.go,全仓唯一入口): - RetentionTier 闭集 temp/daily/work/long/knowledge + 空串未分型;与 kind 正交。 - resolveRetention 的优先级:调用方显式档位 > 显式有效期(⇒ temp)> 受限来源 不分型 > 留存规则 N1~N7 ⇒ temp > 默认 long。判据靠元数据,规则不许推翻显式档位。 - tempDeadline 是「档位 → expires_at」的唯一换算:temp ⇔ 有效期非空,两列同一笔 INSERT 落下(M2 登记过的「INSERT 与补写之间的短窗口」就此关闭)。 合并(defer ⇔ temp): - judgeIngest 不再跑留存规则,只读 Add 落定的 item.RetentionTier;规则调用点因此 全仓唯一(源码级断言钉住)。 - 判定不再依赖同步闸是否开启:闸未接/关着时档位与有效期照样落库。 - 异步层对已是 temp 的行不重复改写(避免 TTL 随审查延迟漂移)。 - IngestRuleVersion d3.3 → d3.4(判据作用范围变了,按既有硬规矩升版)。 读侧: - 四条列表读路径的 SELECT 与 scanItem 读出 retention_tier(14 列);Get 同样读回。 - 新增 ListByPersonaTiers / ListScopeTiers / ListTiers / ListProjectTiers(可选档位 过滤,缺省不过滤 ⇒ 历史行照常返回;显式传空串只取未分型;非法档位报错)。 原方法签名逐字不变 —— 网关 MemoryStore 接口钉着它们,可变参数会编译不过。 - store.go 的读路径拆到 store_list.go,守 500 行上限。 回归:go build ./... + go vet ./... + go test ./... 全绿(内存包另有 -race)。
This commit is contained in:
1 parent
240b59708d
commit
cc6904b1b4
23 files changed
+1592
-181
No files matched your search
@@ -16,10 +16,10 @@ package memory
|
||||
// `ingestVerdict.Gray`,交**异步审查层**的 T1 再问一次
|
||||
// (preinsert_t1.go,design/23 §15.1 D3-2)。**本文件恒为零模型调用**。
|
||||
//
|
||||
// 另外在**候选比对之前**插一道**提取留存规则闸**(extract_retention.go,
|
||||
// design/03 §10.5 的 N1~N5):新条目自己就「显然不值得长期留存」时直接给第五态
|
||||
// `defer`(`reason=t0_low_value`),**不进候选比对、不进 T1**。它是纯规则、零模型
|
||||
// 调用,因此不改变上面那句「本文件恒为零模型调用」。
|
||||
// 另外在**候选比对之前**先看**已判定的档位**(`item.RetentionTier`,由
|
||||
// `Store.Add` 的 `resolveRetention` 落定,见 retention.go):档位是 temp 时直接给
|
||||
// 第五态 `defer`(`reason=t0_low_value`),**不进候选比对、不进 T1**。
|
||||
// 本文件因此**不再跑任何留存规则**,也仍然恒为零模型调用。
|
||||
//
|
||||
// 两条不可走样:
|
||||
// - **相似度只用来召回候选**,永不参与判定(design/23 §1.2「单阈值双向失效」);
|
||||
@@ -63,6 +63,12 @@ type ingestVerdict struct {
|
||||
// judgeIngest 对一条新落库的记忆做四态判定。cands 已按召回分数降序
|
||||
// (collectTidyPairs 的确定性排序),因此同分时的选择可复现。
|
||||
//
|
||||
// 🔴 **第五态 `defer` 不再在这里跑规则**(迁移 0026 / 切片 M37):档位「留多久」
|
||||
// 由 `Store.Add` 的**唯一判定**(resolveRetention → retention.go)落成
|
||||
// `item.RetentionTier`,本函数只读这一位 —— `tier == temp` ⇔ `defer`。
|
||||
// 于是「判 defer」与「判 retention_tier=temp」是**同一个判定**,不存在两套规则
|
||||
// 各判一遍(M27 体检的缺口正是两处判据各说各话、且几乎都不触发)。
|
||||
//
|
||||
// 无论结论是什么,留痕都要带上**本轮召回来源**(§15 D3-8 的「依据」):
|
||||
// 「没有命中任何包含关系」与「根本没召回到东西」是两件不同的事。
|
||||
func judgeIngest(item MemoryItem, cands []ingestCandidate) ingestVerdict {
|
||||
@@ -73,13 +79,14 @@ func judgeIngest(item MemoryItem, cands []ingestCandidate) ingestVerdict {
|
||||
if v, ok := judgeExactDuplicate(item, cands); ok {
|
||||
return v
|
||||
}
|
||||
// 提取留存规则闸(design/03 §10.5 的 N1~N5):新条目**自己**就「显然不值得
|
||||
// 长期留存」时直接给第五态 `defer`,**不进候选比对、不进 T1**
|
||||
// (design/23 §10.6:规则闸拦下的不进 T1,避免「模型推翻规则」)。
|
||||
// 档位已判临时(`retention_tier=temp`)⇒ 第五态 `defer`,**不进候选比对、
|
||||
// 不进 T1**(design/23 §10.6:规则闸拦下的不进 T1,避免「模型推翻规则」)。
|
||||
//
|
||||
// 它排在**精确重复之后**是刻意的:已有等价条目时 `skip` 只更新既有行、
|
||||
// **一行都不新增**,比 `defer`(新增一行临时记忆)更省也更干净。
|
||||
if rule := judgeExtractionRetention(item.Content); rule != retentionNone {
|
||||
// 档位与 `defer` 因此不是同一件事:`defer` = 「关系判为真新 ∧ 档位是 temp」,
|
||||
// 而档位本身对**每一行**都成立(被 skip 掉的那行也有档位,只是它不在册)。
|
||||
if item.RetentionTier == RetentionTemp {
|
||||
return ingestVerdict{
|
||||
Decision: IngestDefer, Reason: retentionLowValueReason, Mode: mode,
|
||||
Veto: VetoNone, Gray: nil,
|
||||
|
||||
Reference in new issue
Block a user