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
@@ -27,6 +27,9 @@ import (
|
||||
// (design/23 §9 的失败隔离同口径);
|
||||
// 5. **动作闭集**:link / keep 是**只读建议**(design/23 §14:link 的存储形态未定),
|
||||
// 因此不可应用 —— 应用它只会得到 skipped/action_not_applyable,绝不假装做过。
|
||||
// 🔴 2026-10-07 起规则层**不再产出** link 候选(判 keep,见 tidy_rules.go);
|
||||
// 这条分支留着是给**库里既有的** link 行用:它们仍要能被如实判为「不可应用」,
|
||||
// 并由 RejectTidyCandidatesByAction 批量拒绝(tidy_reject.go)。
|
||||
//
|
||||
// 作用域:应用只在候选自带的分区(persona_id + scope + project_id)内改行;
|
||||
// 候选与目标行的分区不一致时拒做(纵深防御 design/23 §5.2-7/8)。
|
||||
@@ -54,6 +57,11 @@ const (
|
||||
tidySkipActionNotApplyable = "action_not_applyable"
|
||||
// tidySkipSourceNotActive 表示候选指向的记忆行已不在册(已被别的动作改过)。
|
||||
tidySkipSourceNotActive = "source_not_active"
|
||||
// tidySkipSourceRestricted 表示候选指向的记忆行来自群聊/陌生人等**只读**来源
|
||||
// (design/23 §5.2-9 + §6)。规则层本就把这类条目排除在扫描外,这里是纵深防御:
|
||||
// 候选行是落库数据,可能来自更早的规则版本或手工改动,而「整理不得改写这类来源」
|
||||
// 是红线而不是权衡 —— 宁可如实报 skipped,也不动那一行。
|
||||
tidySkipSourceRestricted = "restricted_source"
|
||||
// tidySkipSourceMissing 表示候选指向的记忆行不存在。
|
||||
tidySkipSourceMissing = "source_missing"
|
||||
// tidySkipPartitionMismatch 表示候选的分区与它指向的行不一致。
|
||||
@@ -154,7 +162,8 @@ func (s *Store) applyCandidate(ctx context.Context, cand TidyCandidate, opts Tid
|
||||
AffectedIDs: []string{},
|
||||
}
|
||||
if !cand.Action.applyable() {
|
||||
// link / keep 没有执行语义(design/23 §14):如实说「不可应用」,不写任何行。
|
||||
// link / keep 没有执行语义(design/23 §14;2026-10-07 起 link 也不再产出,
|
||||
// 这里兜的是库里既有的 link 行):如实说「不可应用」,不写任何行。
|
||||
return skipTidyApply(base, tidySkipActionNotApplyable), nil
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
@@ -170,7 +179,9 @@ func (s *Store) applyCandidate(ctx context.Context, cand TidyCandidate, opts Tid
|
||||
}
|
||||
}
|
||||
|
||||
// applyable 报告一个动作是否有执行语义。
|
||||
// applyable 报告一个动作是否有执行语义。link/keep **永远**返回 false:
|
||||
// 2026-10-07 起规则层已不产 link(无落点,见 tidy_rules.go),这条判据只服务
|
||||
// 库里既有的历史行与「不许假装做过」的诚实口径。
|
||||
func (a TidyAction) applyable() bool {
|
||||
switch a {
|
||||
case TidyMerge, TidyArchive, TidyReclassify:
|
||||
@@ -223,6 +234,11 @@ func (s *Store) applyTidyArchive(ctx context.Context, cand TidyCandidate, opts T
|
||||
if skip := tidyPartitionMismatch(cand, item); skip != "" {
|
||||
return skipTidyApply(base, skip), nil
|
||||
}
|
||||
if nonOwnerSource(item.SourceConversation) {
|
||||
// 只读来源(群聊/陌生人)绝不归档(design/23 §5.2-9/§6):纵深防御,
|
||||
// 规则层本就把这类条目排除在扫描范围外。
|
||||
return skipTidyApply(base, tidySkipSourceRestricted), nil
|
||||
}
|
||||
err = s.tidyApplyTx(ctx, cand, opts, now, func(ctx context.Context, tx *sql.Tx) error {
|
||||
return archiveTidyItem(ctx, tx, item.ID, now)
|
||||
})
|
||||
@@ -286,6 +302,11 @@ func (s *Store) loadTidyMergeRows(ctx context.Context, cand TidyCandidate) (Memo
|
||||
if skip := tidyPartitionMismatch(cand, survivor); skip != "" {
|
||||
return MemoryItem{}, MemoryItem{}, skip, nil
|
||||
}
|
||||
// 只读来源硬门(design/23 §5.2-9/§6):两侧任一来自群聊/陌生人都不合。
|
||||
// 规则层已把这类条目排除在扫描外,这里是纵深防御。
|
||||
if nonOwnerSource(absorbed.SourceConversation) || nonOwnerSource(survivor.SourceConversation) {
|
||||
return MemoryItem{}, MemoryItem{}, tidySkipSourceRestricted, nil
|
||||
}
|
||||
if absorbed.ID == survivor.ID {
|
||||
return MemoryItem{}, MemoryItem{}, tidySkipInvalidCandidate, nil
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user