feat(memory): M45 提取器时间性元数据位 temporal 驱动留存判定
背景(M37 复审的原话):「真正卡住上游的是提取器的改写:一次性事件被写成 30~80 字的第三人称长期陈述……这不是阈值问题,是判定链缺信息位。」本片补的 就是那一位,不动 N1~N7 的任何阈值。 产出侧 - temporal.go:TemporalKind 闭集(one_off / recurring / durable / unknown, 空串 = 未分型),normalizeTemporal 只认四个字面值,其余一律归 unknown(绝不猜 同义词);提取提示词追加该枚举(模型只负责标注,判定在 Go 闭集 switch 里)。 - llmextractor:候选解析后归一并随 MemoryItem 带出;非法/缺失值不报错、不拖累 同批其他候选。 落库 - 迁移 0027_memory_temporal(up: ADD COLUMN temporal TEXT NOT NULL DEFAULT ''; down: DROP COLUMN,只撤结构、不改写任何行数据)+ NewStore 的自举前滚 (duplicate column 是预期路径);Store.Add 的同一笔 INSERT 写该列,判定与 留痕不分叉。 判定(仍是 resolveRetention 一处收口,judgeIngest 一字未改) - one_off ⇒ temp(+ expires_at),但先过 temporalSanityVeto 六条语义安全阀: 显式「记住」/ 在陈述约定 / 习惯词 / 硬性长期事实词 / 永久化 / 状态改变 —— 命中即不给结论、落回 N1~N7。依据是离线重放的实测:去掉「记住+约定」两条时 21 条长期约定会被判 temp(4.80%),全开后在最近 500 条窗口内翻转条数为 0。 - durable / recurring ⇒ long 且不再跑规则;unknown / 未分型 ⇒ 落回 N1~N7 (不许因为「正文看着像今天的事」就判 temp)。 - IngestRuleVersion d3.4 → d3.5(判定结论会变,留痕必须可分)。 验证 - go build ./... + go vet ./... 全绿;go test -count=1 ./internal/runtime/memory/... ./internal/db/... 全绿;memory -race 全绿。 - 新增 temporal_test.go(验收点 ①~⑤ + 两条交叉护栏 + 闭集/归一化)与 temporal_migration_test.go(0027 up/down/同形/真实启动顺序)。变异实测: 摘掉 one_off 支路 ⇒ ① 全红;关掉 temporalSanityVeto ⇒ 三条护栏用例全红。 - 迁移测试的「回滚 N 步」改为从 schema_migrations 现算(migrateDownToBefore), 新加一条迁移不再打红 0025/0026 的用例。
This commit is contained in:
1 parent
af61b8ccfe
commit
91101dedfb
15 files changed
+1130
-37
No files matched your search
@@ -95,8 +95,18 @@ type retentionVerdict struct {
|
||||
// 2. **显式给了有效期**(`item.ExpiresAt` 非零)⇒ 这一行会到期 ⇒ 它就是临时记忆。
|
||||
// 两者是同一根轴的两种表达,因此这里不是「推导」,而是同一个事实的补全。
|
||||
// 3. **受限来源**(群聊 / 陌生人)⇒ 不分型(空串)。见文件头注 ②。
|
||||
// 4. **留存规则命中**(design/03 §10.5 的 N1~N7)⇒ temp,并带回规则码。
|
||||
// 5. 其余 ⇒ long(默认落点,fail-safe)。
|
||||
// 4. **提取器的时间性元数据位**(迁移 0027、temporal.go)⇒ `one_off` 判 temp
|
||||
// (先过 temporalSanityVeto:正文自己还在说「长期事实 / 反复规律 / 一份约定 /
|
||||
// 主人明确要求记住」时不给结论,落回旧规则);`durable` / `recurring` 判 long
|
||||
// 并**不再跑规则**;`unknown` 与未分型 ⇒ **没有结论**,落到下一步。
|
||||
// 🔴 `unknown` 绝不等于「一次性」:不许因为正文看着像今天的事就判 temp
|
||||
// (fail-safe 方向:宁可多留)。
|
||||
// 5. **留存规则命中**(design/03 §10.5 的 N1~N7)⇒ temp,并带回规则码。
|
||||
// 6. 其余 ⇒ long(默认落点,fail-safe)。
|
||||
//
|
||||
// 排在第 3 步之后的理由与它自己一致:受限来源的留存由来源面决定(后台到期清扫
|
||||
// 跳过它们,expire.go 的 `nonOwnerSource`),若时间性位把它们判成 temp,就会造出
|
||||
// 一条「对读面隐藏、却永远不会被删」的行 —— 那是本条要避免的形态。
|
||||
//
|
||||
// 🔴 「判第五态 `defer`」与「判 `retention_tier=temp`」**就是这一步**:闸与异步审查
|
||||
// 都只读 `item.RetentionTier`(preinsert_judge.go),不再各自跑一遍规则。M27/M37 的
|
||||
@@ -115,6 +125,9 @@ func resolveRetention(item MemoryItem) (retentionVerdict, error) {
|
||||
if nonOwnerSource(item.SourceConversation) {
|
||||
return retentionVerdict{Tier: RetentionTierUnset}, nil
|
||||
}
|
||||
if verdict, ok := temporalVerdict(item); ok {
|
||||
return verdict, nil
|
||||
}
|
||||
if rule := judgeExtractionRetention(item.Content); rule != retentionNone {
|
||||
return retentionVerdict{Tier: RetentionTemp, Rule: rule}, nil
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user