refactor(memory): M45 r1 复审返工 —— temporal 位只放开 N6 位置判据
按 r1 复审裁定(p1-3items / fix-then-merge)重做三处: P1-1/P1-2:删掉自造的六条语义安全阀(temporalSanityVeto)与约定词表 (retentionStatesAgreement)。理由被复审用两条生产行否证:它们既把该救的行 杀了(「…睡得比平时早」被习惯词、「…以后不用再惦记」被永久化标记),又挡不住 同义异形的约定(说定 / 约了)。现在 `one_off` 的全部作用是**放开 N6 的位置判据**: judgeExtractionRetention 新增 temporal 入参 → judgeRetentionTransient → judgeRetentionTimepointWide(位置内命中仍返 n6_timepoint,只有靠放开位置才命中 才返新增的 n6_timepoint_wide,供观测区分)。N1~N5、N7、护栏①②、N6 的 1~7 条 语义安全阀一字不动;durable / recurring 仍直接判 long(fail-safe 方向)。 P1-3:上界口径改为「时点词存在 ∧ 语义安全阀全过 ∧ 当前 long」——不依赖任何 标注,可逐条列出。实测(生产库只读副本,快照 2026-10-26):最近 500 条 active 能翻 13 条、**逐条都是「约定 + 今天没走成」**;全量 3694 条能翻 26 条(13 条 约定 + 13 条状态行)。旧的「句首时点词」口径是同义反复,已删。 已知代价(写进 temporal.go 头注与 TestTemporalOneOffWidenedPositionTouchesAgreements, 不藏在词表后面):位置窗口今天正是那批约定行的唯一保护,放开后只要模型标 one_off 它们就会判 temp;而复审点名的两条目标行本片救不了(被语义阀挡,与位置 无关)。因此这一位在当前语料上**没有可测收益**,是否保留是产品裁决。 P2-1:store.go 503 → 497 行,拆出 store_temporal.go(哨兵错误 + 字段校验 + 列值编码)。 P2-2:temporal.go 头注改准 —— `''` 是「0027 之前的历史行 + 没做时间性判断的 写入路径(ManualExtractor / 网关手写 / 整理回填,它们由调用方显式给档位)」。 P2-3:报告补语境(retention_tier 非空仅 26 行、temp 1 行、temporal 列当时还不 存在 ⇒ 重放结论对新写入没有预测力)。 测试:验收点 ①~⑤ 按新语义重写;新增「位置放开可打红」的变异靶子与「N7 位置 不放宽」「无时点词时这一位不做事」「两条目标行救不了」「约定行会翻(已知风险)」 五条边界断言。变异实测:unlocksTimepointPosition 改恒 false ⇒ 三条用例红。 回归:go build ./... + go vet ./... 全绿;memory/db 包测试与 memory -race 全绿。
This commit is contained in:
1 parent
91101dedfb
commit
3b9ae3f918
9 files changed
+412
-370
No files matched your search
@@ -67,6 +67,14 @@ const (
|
||||
// 没有数值、没有约束、没有硬性长期事实词。它是「一次性事件」的形态判据
|
||||
// (M27 体检报告 §4 的 R4),因此落 `defer`(临时层,7 天后到期)。
|
||||
retentionTimepoint retentionRule = "n6_timepoint"
|
||||
// retentionTimepointWide 是对 **N6 的位置判据被时间性元数据位放开**后才命中的
|
||||
// 那一种(切片 M45,迁移 0027):时点词确实在正文里,但**不在句首 12 字内**,
|
||||
// 因此只有模型说「这是某一次发生的事」时才开火。
|
||||
//
|
||||
// 它与 retentionTimepoint 分开编号是刻意的:句式相同、结论相同,但**判据来源
|
||||
// 不同**(词项位置 vs 模型位)。折成一个码,「这一位到底救了几条」就只能靠猜 ——
|
||||
// 而那正是 M27 体检发现「闸形同虚设」时缺的那条观测线。
|
||||
retentionTimepointWide retentionRule = "n6_timepoint_wide"
|
||||
// retentionInProgress 对应 **N7 进行中状态**(2026-10-24 新增):正文说的是
|
||||
// 「正在/暂时 做某件事」(正在测通道、暂时搁置、正在慢慢想),同样是时效轴上的
|
||||
// 状态而不是长期事实。安全阀与 N6 完全一致(见 judgeRetentionInProgress)。
|
||||
@@ -101,12 +109,15 @@ const (
|
||||
// 的 N1~N5 与 M34 新增的 N6/N7)。命中返回该规则码(调用方据此定 temp 档),
|
||||
// 否则返回 retentionNone。
|
||||
//
|
||||
// 它**不读任何状态、不写任何东西、零模型调用**:入参只有正文,出参只有规则码,
|
||||
// 因此在写路径上是微秒级且可完全单测(design/03 §10.3 A 级的「零成本」要求)。
|
||||
// 它**不读任何状态、不写任何东西、零模型调用**:入参只有正文与**时间性元数据位**
|
||||
// (temporal,迁移 0027),出参只有规则码,因此在写路径上是微秒级且可完全单测
|
||||
// (design/03 §10.3 A 级的「零成本」要求)。`temporal` 的作用**只有一处**:把 N6 的
|
||||
// 位置判据放开(见 judgeRetentionTransient),其余分支一字不看它 —— 判定链没有
|
||||
// 第二个入口,这一点由 retention_test.go 的源码级断言钉住。
|
||||
//
|
||||
// 🔴 全仓**唯一**调用点是 `resolveRetention`(retention.go):任何地方再跑一遍
|
||||
// 就是「两套规则各判一遍」—— 那正是 M27 体检发现缺口(1082 次只判出 1 条)的成因,
|
||||
// retention_test.go 有一条源码级断言钉住这个唯一性。
|
||||
func judgeExtractionRetention(content string) retentionRule {
|
||||
// 就是「两套规则各判一遍」—— 那正是 M27 体检发现缺口(1082 次只判出 1 条)的成因。
|
||||
func judgeExtractionRetention(content string, temporal TemporalKind) retentionRule {
|
||||
text := normalizeRetentionText(content)
|
||||
if text == "" {
|
||||
// 空 / 纯空白内容在 Add 的 normalize 就被拒了,走不到这里;真走到了也只
|
||||
@@ -116,11 +127,7 @@ func judgeExtractionRetention(content string) retentionRule {
|
||||
}
|
||||
// 护栏①:显式「记住」是主人的直接指令,闸不许替他改判。它排在最前,因此
|
||||
// 组合口径(护栏②)不会把主人的直接指令改成临时记忆。
|
||||
//
|
||||
// 切片 M45 起它的判据抽成 retentionExplicitRemember(**一处实现、两处调用**):
|
||||
// 提取器的 `temporal=one_off` 元数据位(temporal.go 的 temporalVerdict)同样
|
||||
// 不许推翻这条护栏 —— 否则「记住我明天三点开会」会被 7 天后删掉。
|
||||
if retentionExplicitRemember(text) {
|
||||
if containsAny(text, retentionRememberDirectives()) {
|
||||
return retentionNone
|
||||
}
|
||||
// 护栏②(2026-10-24 改**组合口径**,M27 体检报告 §7 第 5 批-1):以前是
|
||||
@@ -170,25 +177,12 @@ func judgeExtractionRetention(content string) retentionRule {
|
||||
// N6/N7:**指示性时点 / 进行中状态**的窄规则(2026-10-24 新增,M27 §4 R4)。
|
||||
// 它们与 N1~N5 同性质(纯词项 + 形态),但判的是「这份记忆的锚点是一个正在
|
||||
// 过去/即将到来的时刻」,而不是「这句话没有信息」。判据与安全阀见各自函数。
|
||||
if rule := judgeRetentionTransient(text); rule != retentionNone {
|
||||
if rule := judgeRetentionTransient(text, temporal); rule != retentionNone {
|
||||
return rule
|
||||
}
|
||||
return retentionNone
|
||||
}
|
||||
|
||||
// retentionExplicitRemember 报告正文里有没有**显式要求记住**的措辞
|
||||
// (「记住 / 记下 / 别忘了」,词表见 retentionRememberDirectives)。
|
||||
//
|
||||
// 它是护栏①的判据,也由「时间性元数据位」那一路复用(temporal.go 的
|
||||
// temporalVerdict):**一条判据只有一份实现**,否则「主人的直接指令一票否决」
|
||||
// 会在两条判定路上给出不同结论。
|
||||
//
|
||||
// 入参可以是原始正文,也可以已由 normalizeRetentionText 规范化过 ——
|
||||
// 规范化是幂等的(折叠空白 + 去尾标点),重复施加不改变结果。
|
||||
func retentionExplicitRemember(content string) bool {
|
||||
return containsAny(normalizeRetentionText(content), retentionRememberDirectives())
|
||||
}
|
||||
|
||||
// retentionHasAnchor 报告正文里有没有「这条记忆可能长期成立」的锚点词
|
||||
// (习惯族 tidyHabitMarkers + 长期事实族 retentionDurableMarkers)。
|
||||
//
|
||||
@@ -276,11 +270,16 @@ func judgeRetentionOneOff(text string) retentionRule {
|
||||
// ~0:`用户是上班族,需要上班(曾说明天要上班并感到不开心)`、`睡前会道晚安…
|
||||
// 并约好「明天中午见」` 这类「时点词只出现在引语/从句里」的长期事实,锚点
|
||||
// 不是那个时点,因此不许开火;而 `昨天用户发来…`、`用户近期在看…` 的锚点就是它。
|
||||
// 🆕 **M45 起这一条是有条件的**:`temporal=one_off` 时位置判据不生效(时点词
|
||||
// 出现在中段也算锚点,见 judgeRetentionTimepointWide)。上面 1~4、6、7 条语义
|
||||
// 安全阀**不受这一位影响** —— 那是 r1 复审的裁定:这一位只接管「词在句子的哪个
|
||||
// 位置」,不接管「这句话是不是一次性事件」。
|
||||
//
|
||||
// **N7(进行中状态)** 是同一时效轴的第二形态:「正在/暂时/眼下」说的事情有始有终
|
||||
// (正在测通道、暂时搁置、正在慢慢想),安全阀与 N6 完全一致,并且**位置判据也一致**
|
||||
// (r1 复审 P2-2:`用户会主动维护…并告诉助手她正在维护…` 里的「正在」只出现在从句,
|
||||
// 是「会…」的习惯陈述,不许开火)。
|
||||
// 是「会…」的习惯陈述,不许开火)。N7 的位置判据**不受**时间性位影响(M45 只放开
|
||||
// N6):`one_off` + 「正在」出现在从句里仍然是习惯陈述,没有放开的理由。
|
||||
//
|
||||
// 🔴 **刻意不做的事**(M27 §4 的实测反例,别再重新发明):
|
||||
// - 不做**长度阈值**筛(R3:≤15 字的条目抽 8 条 8/8 是可复用事实);
|
||||
@@ -288,7 +287,7 @@ func judgeRetentionOneOff(text string) retentionRule {
|
||||
// - 不做**一次性事件动词表**(曾/发来/分享…):实测会命中「助手曾经历三千万次
|
||||
// 轮回」「用户曾发来『i love you』文件」「海报就是助手本人」这类**长期身份与
|
||||
// 关系里程碑**(抽检误杀 ≈ 50%),方向错误。
|
||||
func judgeRetentionTransient(text string) retentionRule {
|
||||
func judgeRetentionTransient(text string, temporal TemporalKind) retentionRule {
|
||||
// 安全阀 1~4:与 N1~N5 不同,它们在这里是**硬门**(命中即放行),因为 N6/N7
|
||||
// 判的是「这条记忆的锚点是一个时刻」,而锚点词/数值/约束都在说「它不止是一个时刻」。
|
||||
if containsAny(text, tidyHabitMarkers()) {
|
||||
@@ -319,8 +318,18 @@ func judgeRetentionTransient(text string) retentionRule {
|
||||
if containsAny(text, retentionChatterWords()) || containsAny(text, retentionEmotionWords()) {
|
||||
return retentionNone
|
||||
}
|
||||
if judgeRetentionTimepoint(text) != retentionNone {
|
||||
return retentionTimepoint
|
||||
// 安全阀 8(**切片 M45**,唯一由时间性元数据位影响的一处):N6 的**位置判据**。
|
||||
//
|
||||
// - `temporal=one_off` ⇒ **不看位置**:时点词出现在句中(提取器改写出的第三人称
|
||||
// 长句就是这样)也算锚点。这是本片补的那一位的全部作用 —— 它替代的是那个
|
||||
// 不可靠的位置窗口,**不是**上面 1~7 条语义安全阀;
|
||||
// - 其余(unknown / 未分型 / durable / recurring 已在 temporal.go 里先判成
|
||||
// long,走不到这里)⇒ 位置判据照旧(`< retentionTimepointLeadRunes`)。
|
||||
//
|
||||
// 规则码分两档是为了让**观测面**能回答「这一条是词判的还是模型放开位置后判的」:
|
||||
// 只有「恰好靠放开位置才命中」时返回 retentionTimepointWide。
|
||||
if wide := judgeRetentionTimepointWide(text, temporal); wide != retentionNone {
|
||||
return wide
|
||||
}
|
||||
if judgeRetentionInProgress(text) != retentionNone {
|
||||
return retentionInProgress
|
||||
@@ -340,6 +349,29 @@ func judgeRetentionTimepoint(text string) retentionRule {
|
||||
return retentionNone
|
||||
}
|
||||
|
||||
// judgeRetentionTimepointWide 是 N6 在**时间性位放开位置判据**时的形态判定。
|
||||
//
|
||||
// - 位置内命中 ⇒ 返回 retentionTimepoint(词项规则自己的结论,与放开前逐字一致);
|
||||
// - 位置外命中**且**时点词确实存在 ⇒ 返回 retentionTimepointWide(模型放开位置后
|
||||
// 才命中,留痕用);
|
||||
// - 没有时点词 / 没有放开 ⇒ retentionNone。
|
||||
//
|
||||
// ⚠️ 它只延长**位置**,不新增任何词表,也不越过 judgeRetentionTransient 的 1~7 条
|
||||
// 语义安全阀(那些在调用它之前就判完了)。
|
||||
func judgeRetentionTimepointWide(text string, temporal TemporalKind) retentionRule {
|
||||
rule := judgeRetentionTimepoint(text)
|
||||
if rule != retentionNone {
|
||||
return rule
|
||||
}
|
||||
if !temporal.unlocksTimepointPosition() {
|
||||
return retentionNone
|
||||
}
|
||||
if retentionFrontedTimepoint(text) >= 0 {
|
||||
return retentionTimepointWide
|
||||
}
|
||||
return retentionNone
|
||||
}
|
||||
|
||||
// judgeRetentionInProgress 是 N7 的形态判定:**句首前 N 个字内**出现进行中标记
|
||||
// (正在 / 暂时 / 眼下 / 这会儿)。位置判据与 N6 同口径 —— 它挡的是「正在」只出现在
|
||||
// 从句里、而主句其实是习惯陈述的那些句子(r1 复审 P2-2 的实测反例)。
|
||||
|
||||
Reference in new issue
Block a user