feat(memory): 整理不再只产建议 —— 轮末自动落地高置信 + 停产 link 候选
主人原话:「那么多 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),主会话补;
⚠️ 真机端到端未跑(不能重启后端)。
This commit is contained in:
1 parent
353293e807
commit
6d92536276
27 files changed
+2063
-197
No files matched your search
@@ -71,6 +71,22 @@ type tidyVerdict struct {
|
||||
Reason string
|
||||
}
|
||||
|
||||
// 候选与判定结论的理由标签(落 memory_tidy_candidates.reason;`tidyAutoApplyEligible`
|
||||
// 会把其中两条读回来当自动落地的判据)。抽成常量是因为它们各有两处以上使用:
|
||||
// 「三次复制必须抽取」(AGENTS §10)。
|
||||
const (
|
||||
// tidyReasonSimilarNotContained 是「相似但互不包含」的判定结论。2026-10-07 起
|
||||
// 这种对**不再产候选**(判 keep,见 tidy_rules.go),它只留在判定结果与日志里。
|
||||
tidyReasonSimilarNotContained = "similar_but_not_contained"
|
||||
// tidyReasonFactSubset 是包含式合并(A ⊆ B,把 A 并入 B)的理由。
|
||||
tidyReasonFactSubset = "fact_subset"
|
||||
// tidyReasonOneOffEvent 是一次性事件的归档理由,**也是自动档唯一认的归档理由**:
|
||||
// 理由不同(例如将来新增的归档类别)就不在新口径的授权范围内,交人工。
|
||||
tidyReasonOneOffEvent = "one_off_event"
|
||||
// tidyReasonKindMismatch 是 kind 误分类的理由(只产候选,不自动改)。
|
||||
tidyReasonKindMismatch = "kind_mismatch"
|
||||
)
|
||||
|
||||
// archiveCandidate 为一次性事件产一条归档建议(design/23 §5.1 T0-5)。
|
||||
func archiveCandidate(it MemoryItem, now time.Time) TidyCandidate {
|
||||
return TidyCandidate{
|
||||
@@ -82,7 +98,7 @@ func archiveCandidate(it MemoryItem, now time.Time) TidyCandidate {
|
||||
Action: TidyArchive,
|
||||
SourceIDs: []string{it.ID},
|
||||
ProposedContent: it.Content,
|
||||
Reason: "one_off_event",
|
||||
Reason: tidyReasonOneOffEvent,
|
||||
Evidence: TidyEvidence{
|
||||
Containment: "none",
|
||||
ContentA: it.Content,
|
||||
@@ -121,12 +137,10 @@ func buildCandidates(pr tidyPair, v tidyVerdict, now time.Time) []TidyCandidate
|
||||
}
|
||||
|
||||
if v.Action != TidyMerge || v.Target == nil {
|
||||
// 相似但互不包含:只建链接/分组,绝不合并(design §1.3 的 A-MEM 口径)。
|
||||
base.Action = TidyLink
|
||||
base.SourceIDs = []string{pr.a.ID, pr.b.ID}
|
||||
base.Kind = pr.a.Kind
|
||||
base.Reason = "similar_but_not_contained"
|
||||
return []TidyCandidate{base}
|
||||
// 非 merge 的判定结论**不产候选**:link 没有执行语义(design/23 §14 的待
|
||||
// 细化项,见 tidy_rules.go 的 containNone 分支),keep 按 §5.2-10 就是
|
||||
// 「什么都不做」。返回空切片而不是一条应用不了的候选 —— 后者只会污染待办。
|
||||
return nil
|
||||
}
|
||||
|
||||
absorbed, target := pr.a, *v.Target
|
||||
@@ -152,7 +166,7 @@ func buildCandidates(pr tidyPair, v tidyVerdict, now time.Time) []TidyCandidate
|
||||
re.TargetID = ""
|
||||
re.ProposedContent = ""
|
||||
re.MergedContent = ""
|
||||
re.Reason = "kind_mismatch"
|
||||
re.Reason = tidyReasonKindMismatch
|
||||
re.Evidence.ProposedKind = string(target.Kind)
|
||||
// KindA 在 reclassify 候选上**重新定义**为「被改那一行当前的 kind」:
|
||||
// merge 候选的 KindA/KindB 是一对各自的 kind(供 diff 预览),而
|
||||
|
||||
Reference in new issue
Block a user