refactor(memory): M45 裁决 D —— temporal 位撤出判定,只保留观测
按 r1 复审裁决 D:这一位先只做观测,`resolveRetention` 不许读它。理由(实测):
无条件 one_off ⇒ temp 会当天删 21 条长期约定;只放开 N6 位置判据能翻的 13 条
逐条是约定、而复审点名的三条目标行 0/3 救得了。在「模型真实标签分布」这份证据
缺位时不该做 7 天后物理删的不可逆决定。
撤接线(结论集逐字回到 M45 之前):
- retention.go:resolveRetention 不再读 item.Temporal;与之对比 dev base
(af61b8c)本文件**只有注释差异**,规则与判定入口逐字未变。
- extract_retention.go:judgeExtractionRetention 签名回退单参;删除
judgeRetentionTimepointWide 与 n6_timepoint_wide 规则码。与 dev base 对比
**同样只有注释差异** ⇒ 「结论集 = 不含 temporal 的那个集合」是结构性的,
不是靠调用点记得别传。
- 连带取消初版的 durable / recurring ⇒ long(零影响必须是双向的)。
保留观测面(一律未动):迁移 0027 的列、自举前滚、提取器的 temporal 产出、
prompt 枚举、闭集与 normalizeTemporal、store_temporal.go 的校验/编码。
另外把 prompt 里「标成 one_off 会让它进临时层、到期消失」这句**撤接线后已是假话**
改成中性标注说明 —— 否则会诱导模型避开 one_off,观测数据直接被污染。
测试:
- TestTemporalBitDoesNotAffectRetention —— 4 类语料 × 5 个取值,(档位,规则码)
与未分型逐字相同(核心正断言);
- TestTemporalOneOffDoesNotMakeAnythingTemporary / ...DoesNotRescueRuleHitRows
—— 零影响的双向点名;
- TestTemporalIsStillRecorded —— one_off 的约定行仍落 temporal='one_off' 而档位
long、expires 空(观测在、判定不动);
- TestTemporalDoesNotTouchExplicitTierOrRestrictedSource —— 人工入口与受限来源不变。
变异实测:把 `if item.Temporal == TemporalOneOff { return temp }` 插回
resolveRetention ⇒ 上述四条用例全红(FAIL 输出已贴给塔),恢复后全绿。
IngestRuleVersion 保持 d3.5,注释改准为「记录 temporal 信号,但不参与判定」
(留痕面变了:库里多了「模型当时标了什么」这一维,回退会让新旧留痕混在一起)。
P2 三条保留:store.go 500 行(拆 store_temporal.go)、temporal.go 头注('' 的口径
+ 两次被否的接线方案 + 接线前必须看的那两条统计 SQL)、报告语境段。
回归:go build ./... + go vet ./... 全绿;memory/db 包测试全绿;memory -race
38.2s 全绿。
This commit is contained in:
1 parent
3b9ae3f918
commit
c7c0294a16
11 files changed
+280
-551
No files matched your search
@@ -2,86 +2,71 @@ package memory
|
||||
|
||||
import "strings"
|
||||
|
||||
// 本文件是**提取器产出侧的时间性元数据位**(`temporal`,迁移 0027):它回答
|
||||
// 「这条记忆说的是**一次发生的事**,还是**反复 / 长期成立的事**」。
|
||||
// 本文件是**提取器产出侧的时间性元数据位**(`temporal`,迁移 0027):它记录模型对
|
||||
// 「这条记忆说的是**一次发生的事**,还是**反复 / 长期成立的事**」的判断。
|
||||
//
|
||||
// 为什么需要这一位(M37 复审的结论,逐字):「真正卡住上游的是**提取器的改写**:
|
||||
// 当初为什么要这一位(M37 复审的结论,逐字):「真正卡住上游的是**提取器的改写**:
|
||||
// 一次性事件被写成 30~80 字的第三人称长期陈述(同时带『近期/目前』和持久词),
|
||||
// 规则闸看不到『这是今天的一次性事件』这一位。这不是阈值问题,是**判定链缺信息位**。」
|
||||
//
|
||||
// 🔴 **这一位只接管 N6 的「位置判据」**(2026-10-27 r1 复审裁定):词项规则里唯一
|
||||
// 真正不可靠的是 **N6 的「时点词必须出现在句首 12 字内」**——提取器改写出的第三人称
|
||||
// 长句本来就把时点埋在中段,窗口再放宽(12→24)就会连「同一条长期约定的重复陈述」
|
||||
// 一起命中并删掉约定(M37 已量:2→14,多出的 11 条逐条都是约定)。因此:
|
||||
// 🔴 **当前状态:只观测,不参与判定**(r1 复审裁决 D,2026-10-27)。`resolveRetention`
|
||||
// **不读**这一位,判定的结论集与「没有这一位」逐字相同。保留它的唯一目的是**积累
|
||||
// 证据**:等真实提取跑出一批标签之后,再回答「模型到底怎么标」以及「接进判定是赚
|
||||
// 还是亏」。
|
||||
//
|
||||
// - 模型说 `one_off` ⇒ **N6 忽略位置窗口**(时点词出现在哪儿都算锚点);
|
||||
// - **N6 的全部语义安全阀照旧**(习惯词 / 硬性长期事实词 / 具体约束 / 永久化 /
|
||||
// 状态改变 / 寒暄情绪)—— 它们判的是「这句话不止是一个时刻」,与位置无关;
|
||||
// - N1~N5、N7、护栏①② 一字不动。
|
||||
// 为什么撤(两次候选接线都被生产重放否掉,别再凭直觉改回去):
|
||||
//
|
||||
// 🔴 r1 复审推翻的两条自造设计(别再写回来):
|
||||
// ① **无条件 `one_off ⇒ temp`**(任务书初版口径):最近 500 条 active 里人工按
|
||||
// 提取器口径标出的 23 条 `one_off`,其中 21 条是「约定 + 某一次没走成」形态 ⇒
|
||||
// 7 天后**把约定本身物理删掉**(不可撤回)。
|
||||
// ② **只放开 N6 的位置判据**(r1 复审给的改法):能翻的行在窗口内 13 条、**逐条都是
|
||||
// 约定**(`约定/约好 + 今天没走成`),而复审点名的三条目标行 **0/3** 救得了
|
||||
// (`…睡得比平时早` 被习惯词、`…以后不用再惦记` 被永久化标记、`自行车链条` 那条
|
||||
// 没有时点词)⇒ 收益为 0、风险集中在长期约定。
|
||||
//
|
||||
// ① **不要**给 `one_off` 再叠一层「约定/长期事实」词表否决 —— 它既把该救的行一起
|
||||
// 杀了(「昨晚…睡得比平时早」被习惯词杀掉、「今天就终于把 bug 修好了,以后不用
|
||||
// 再惦记」被永久化标记杀掉,而这两条正是这一位该救的),又挡不住同义异形的
|
||||
// 约定(`说定`/`约了` 不在任何词表里)—— 词面否决在**两个方向**上都不安全;
|
||||
// ② **不要**用「句首时点词且不触发任何阀门」当上界 —— 那种行 N6 本来就判 temp,
|
||||
// 拿它当上界是同义反复,恰好把本片要救的「时点词不在句首」整类排除在外。
|
||||
// 还有一层方法论上的理由:以上两轮数字**全部**建立在「对历史改写正文**人工**标注」
|
||||
// 上 —— 生产库里 `temporal` 列当时还不存在,模型从未在这批正文上产出过标签。
|
||||
// 用人工标签去决定一条「模型标签驱动」的接线,证据链是断的。
|
||||
//
|
||||
// ⚠️ **已知代价(2026-10-27 生产重放实测,r1 复审要求如实登记,不许美化)**:
|
||||
// 位置窗口今天**正是**那批「约定 + 某一次没走成」行的唯一保护(它们的时点词都埋在
|
||||
// 中段)。放开位置之后:
|
||||
// 将来真要接线,前置条件是**这份数据**(不是又一次重放):
|
||||
//
|
||||
// - 最近 500 条 active 里**能翻的 13 条逐条都是这种约定**(`约定/约好 + 今天没走成`);
|
||||
// - 全量 3694 条里能翻 26 条,其中 13 条是约定,另 13 条是「项目/宿舍/换装/探针」
|
||||
// 这类状态行;
|
||||
// - 而 r1 复审点名的两条目标行(`昨晚…睡得比平时早` 被习惯词、
|
||||
// `今天就终于把 bug 修好了,以后不用再惦记` 被永久化标记)**本片救不了** ——
|
||||
// 它们是被语义安全阀挡的,与位置无关。
|
||||
// SELECT temporal, COUNT(*) FROM memory_items GROUP BY temporal;
|
||||
// SELECT created_at, retention_tier, content FROM memory_items
|
||||
// WHERE temporal = 'one_off' ORDER BY created_at DESC LIMIT 10;
|
||||
//
|
||||
// 也就是说:**在当前语料上,这一位没有可测收益,风险集中在长期约定上。** 是否保留
|
||||
// 是产品裁决(M45 报告 §决策矩阵),不是代码判断;本文件只保证「放开位置的边界」是
|
||||
// 确定的、可测的、可回退的。
|
||||
// 看 `one_off` 那 10 条里有多少是长期约定 —— 那才是「这一位能不能用」的判据。
|
||||
//
|
||||
// 闭集(与迁移 0027 的列值口径逐字一致):
|
||||
//
|
||||
// one_off 一次性事件(今天 / 这次 / 某一次发生的事)⇒ 放开 N6 的位置判据
|
||||
// recurring 反复发生的规律(每天 / 每周 / 每次都…)⇒ long(不进规则)
|
||||
// durable 长期成立的事实 / 偏好 / 约定 / 称呼 / 身份 ⇒ long(不进规则)
|
||||
// unknown 模型拿不准,或没给出合法值 ⇒ **没有结论**,规则照旧
|
||||
// '' 尚未分型 ⇒ 与 unknown 同路(规则照旧)。它出现在两类行上:
|
||||
// ① 0027 之前的历史行;
|
||||
// ② **没做时间性判断的写入路径** —— `ManualExtractor`(「记住 X」,
|
||||
// 它把档位显式钉成 long,见 extract.go)、网关/管理面的手写记忆
|
||||
// (`RetentionTier: long`)、整理侧回填。这些路径本来就由调用方
|
||||
// 显式给档位,因此空串在这里不丢信息(档位判定第 1 步先命中)。
|
||||
// one_off 一次性事件(今天 / 这次 / 某一次发生的事)
|
||||
// recurring 反复发生的规律(每天 / 每周 / 每次都…)
|
||||
// durable 长期成立的事实 / 偏好 / 约定 / 称呼 / 身份
|
||||
// unknown 模型拿不准,或没给出合法值
|
||||
// '' 尚未分型:0027 之前的历史行 + 没做时间性判断的写入路径
|
||||
// (`ManualExtractor` 的「记住 X」、网关/管理面手写、整理侧回填 ——
|
||||
// 它们本来就由调用方显式给档位)
|
||||
//
|
||||
// 🔴 `unknown` 与 `”` 在判定上**走同一条路**(规则照旧),但在口径上是两件事:
|
||||
// `unknown` 是「问过模型了,它拿不准」;`”` 是「这条路径没问过」。把两者折成一个值
|
||||
// 会让「模型到底答了什么」这个观测面当场消失,而那正是本片要验证的东西。
|
||||
// 🔴 `unknown` 与 `”` 是两件事:`unknown` 是「问过模型了,它拿不准」;`”` 是
|
||||
// 「这条路径没问过」。把两者折成一个值会让「模型到底答了什么」这个观测面当场消失,
|
||||
// 而那正是保留这一位的全部意义。
|
||||
type TemporalKind string
|
||||
|
||||
const (
|
||||
// TemporalUnset 是「尚未分型」:历史行与没做时间性判断的写入路径(见文件头注)。
|
||||
// **它不是业务取值**,判定上与 TemporalUnknown 同路,但含义不同。
|
||||
// **它不是业务取值**,也**不是**「unknown」的同义词。
|
||||
TemporalUnset TemporalKind = ""
|
||||
|
||||
// TemporalOneOff 是一次性事件。它是本片唯一会**收紧**判定的时间性位:命中即
|
||||
// 让 N6 忽略位置窗口(时点词出现在句子中段也算锚点)。语义安全阀照旧,因此它
|
||||
// 仍然不是「无条件判 temp」——这一点是 r1 复审的裁定,别再改回去。
|
||||
// TemporalOneOff 是一次性事件。它目前**不改变任何判定**:写入时只落库,
|
||||
// 供事后统计「模型把哪些行标成了某一次发生的事」。
|
||||
TemporalOneOff TemporalKind = "one_off"
|
||||
|
||||
// TemporalRecurring 是反复发生的规律(「每天散步十分钟」「每周三开会」)。
|
||||
// 「反复」= 将来还会再发生,因此不许进临时层:命中即判 long,不再跑规则。
|
||||
TemporalRecurring TemporalKind = "recurring"
|
||||
|
||||
// TemporalDurable 是长期成立的事实 / 偏好 / 约定 / 称呼 / 身份。同样判 long、
|
||||
// 不再跑规则(方向仍是 fail-safe:多留一条)。
|
||||
// TemporalDurable 是长期成立的事实 / 偏好 / 约定 / 称呼 / 身份。
|
||||
TemporalDurable TemporalKind = "durable"
|
||||
|
||||
// TemporalUnknown 是「问过模型了,它拿不准」。**它绝不是「一次性」的同义词**:
|
||||
// 判定上它与未分型一样落到 N1~N7,不许因为「正文看着像今天的事」就判 temp ——
|
||||
// 那等于把本片要消灭的猜测重新引回判定链。
|
||||
// TemporalUnknown 是「问过模型了,它拿不准」。
|
||||
TemporalUnknown TemporalKind = "unknown"
|
||||
)
|
||||
|
||||
@@ -100,21 +85,12 @@ func (t TemporalKind) valid() bool {
|
||||
// 比较去替代它(那是「问过但拿不准」,不是「没问过」)。
|
||||
func (t TemporalKind) assigned() bool { return t != TemporalUnset }
|
||||
|
||||
// unlocksTimepointPosition 报告这一位是否**放开 N6 的位置判据**。
|
||||
//
|
||||
// 只有 `one_off` 会:模型已经回答了「这是某一次发生的事」,那么时点词埋在句中
|
||||
// (「…整理到一半,今天下午才收尾」)就不再是「它不是一个时刻锚点」的证据。
|
||||
// `unknown` / 未分型 / durable / recurring 一律不放开。
|
||||
func (t TemporalKind) unlocksTimepointPosition() bool {
|
||||
return t == TemporalOneOff
|
||||
}
|
||||
|
||||
// normalizeTemporal 把模型输出的自由文本归一到上面四个值。
|
||||
//
|
||||
// 🔴 **只做大小写折叠与首尾空白裁剪,绝不做同义词猜测**(「一次性」「once」→ 都不认):
|
||||
// 认不出来的答案就是解析失败,一律落 TemporalUnknown —— 这正是任务书
|
||||
// 「模型只能选那四个值,解析失败一律归 unknown(绝不猜)」的字面实现。
|
||||
// 猜同义词的代价是单向的:猜成 one_off 就删记忆,猜成别的只是多留一条。
|
||||
// 「模型只能选那四个值,解析失败一律归 unknown(绝不猜)」的字面实现。将来接线时
|
||||
// 这条尤其重要:猜同义词的代价是单向的(猜成 one_off 就删记忆)。
|
||||
func normalizeTemporal(s string) TemporalKind {
|
||||
switch TemporalKind(strings.ToLower(strings.TrimSpace(s))) {
|
||||
case TemporalOneOff:
|
||||
@@ -130,20 +106,3 @@ func normalizeTemporal(s string) TemporalKind {
|
||||
return TemporalUnknown
|
||||
}
|
||||
}
|
||||
|
||||
// temporalTier 给出**模型直接给出的档位结论**(只由 resolveRetention 第 4 步调用)。
|
||||
//
|
||||
// 只有 `durable` / `recurring` 有结论(long):模型已经回答了「这不是一次性事件」,
|
||||
// 此时再让词项规则判 temp 就是把「判定链缺的那一位」又丢掉;方向是 fail-safe(多留)。
|
||||
//
|
||||
// `one_off` **不在这里**出结论 —— 它作为**输入**进规则层(放开 N6 的位置判据),
|
||||
// 语义安全阀照旧(r1 复审裁定,见文件头注);`unknown` / 未分型同样没有结论,
|
||||
// 规则照旧。
|
||||
func temporalTier(t TemporalKind) (RetentionTier, bool) {
|
||||
switch t {
|
||||
case TemporalDurable, TemporalRecurring:
|
||||
return RetentionLong, true
|
||||
default:
|
||||
return RetentionTierUnset, false
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user