主人原话:「收窄提取器,确实要给他一个主动式的记忆留存规则,不能想到什么都 往记忆里写」「.env 只做兜底用 …这些键优先从面板配置上取」「临时记忆到期删除」。 按 design/23 §18(D3 第五态 defer)、§19(到期处置)、design/03 §9.6 (tempTtlSec 默认 7 天 + 坏值口径)、§10(提取留存规则)落地。 - IngestDecision 闭集加第五态 defer:照常 active 落库 + 写短 expires_at 落 临时层(不删行、不改正文,回滚 = 清空该列);IngestRuleVersion d3.2 → d3.3。 - 落点:ingestGatePlan 增 ExpiresAt + expiresAt()(走 expiryText/formatTime 的 定宽 30 字节 UTC,与读侧时效闸同一套文本口径),由 finishIngest 在 defer 一态 补写 expires_at(INSERT 与补写之间的短暂窗口如实登记,方向 = 宁可多留)。 非 defer 三态一笔都不多写。 - T0 提取留存规则闸(extract_retention.go):design/03 §10.5 的 N1~N5 纯规则, 接进 judgeIngest 的候选比对之前,命中即 defer + reason=t0_low_value,不进 T1; 两道护栏(显式「记住」/ 持久信号)一票放行,fail-safe 方向 = 宁可多留。 - T1 闭集扩为 merge/modify/keep/defer(同一个 prompt、同一次调用,绝不新增 第二次模型调用):只有关系判为真新时才问价值;defer 要求 target=0,否则判 非法回落 create(reason=t1_reply_invalid)。 - 提取器提示词加固(design/03 §10.3 B):正向清单 + 反例清单 + 「允许返回 []」。 - 临时记忆 TTL:代码默认 tempTtlSec = 604800(7 天);面板 $DATA_DIR/config/memory-tidy.json 的 tempTtlSec 优先(ApplyReviewTuning 逐键 解析,坏值回落默认 + WARN,绝不写进 cfg)。该键在 .env 里没有出处且不新增 (.env 已冻结)⇒ 实际生效链是「面板 > 代码默认」。启动播种同步该键(*int + omitempty,nil 省略、绝不写 null),保证播种文件与生效值逐字一致。 - preinsert_store.go 的 listReviewCandidatesSQL 原位注释:这条内部召回 SQL 刻意不过时效闸的裁定与理由(design/03 §11.2 第 3 条)。 - 测试:defer 落库带短 expires_at(闸 + T1 两条路径)、写入口径与时效闸同源、 TTL 坏值与回落、规则版本 d3.3、整理候选幂等键不含 IngestRuleVersion、 提取规则正例 11 条 / 反例 11 条。 验收:go build ./... + go vet ./... + go test ./... 全绿;windows/darwin 交叉 编译通过(GOOS=darwin GOARCH=arm64 / GOOS=windows GOARCH=amd64)。
71 lines
3.7 KiB
Go
71 lines
3.7 KiB
Go
package memory
|
||
|
||
import (
|
||
"log/slog"
|
||
"time"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/config/runtimeconfig"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
||
)
|
||
|
||
// 本文件是**入库前审查调优面**的解析点:把 `$DATA_DIR/config/memory-tidy.json`
|
||
// 的面板旋钮叠到 ReviewConfig 上 —— 与 tidy_tuning.go 对 TidyConfig 做的事同源、
|
||
// 同一条口径(主人 2026-10-07:「**`.env` 只做兜底用** —— 后端自动创建独立的面板
|
||
// 配置文件之后就走面板配置文件,这些键优先从面板配置上取」)。
|
||
//
|
||
// 为什么解析放在 memory 而不是 runtimeconfig:runtimeconfig 只负责**文件形状与
|
||
// 读取**(它的头注释把这条边界写死了),而「缺位/坏值怎么回落」属于 ReviewConfig
|
||
// 的归一化语义 —— 与 normalizeReviewConfig 同一层,因此两者可以一起被单测覆盖。
|
||
//
|
||
// 为什么不是「读盘时逐键容错」:整份文件解不开(含某键类型写错)时**全部键一起**
|
||
// 回落,这是本文件家族既有的粒度(见 runtimeconfig.LoadMemoryTidyTuning 与它的
|
||
// 用例③);本文件处理的是**类型正确但值不可用**(非正数)那一档。
|
||
|
||
// ApplyReviewTuning 把面板调优旋钮叠到 ReviewConfig 上,返回叠加后的配置
|
||
// (**未归一化**:调用方随后交给 NewPreInsertReviewer / normalizeReviewConfig
|
||
// 填代码默认值,与组合根对 TidyConfig 的既有顺序一致)。
|
||
//
|
||
// 目前只有一键,逐键独立,三档优先级是:
|
||
//
|
||
// 键 面板(memory-tidy.json) 下一档(.env) 代码默认
|
||
// tempTtlSec tempTtlSec **无此键**(结构性为空) 604800(7 天)
|
||
//
|
||
// 🔴 **中档(`.env`)在结构上是空的**,这是**刻意**的、不是漏配:`.env` 已冻结
|
||
// (design/13 §2.1「不加、不改、不删」),而「多久算临时」是本次新增的能力,
|
||
// 它在 `.env` 里**从来就没有键**。因此实际生效链是「面板 > 代码默认」——报回报时
|
||
// 不许把它写成「面板 > .env > 代码默认」而让读者以为 `.env` 那档还有东西可调。
|
||
//
|
||
// 坏值(非正秒数)**绝不写进 cfg**:回落代码默认 + 记一条 WARN。一个 ≤0 的 TTL
|
||
// 会让每条 defer 记忆**落库即过期**(读侧立刻隐藏、后台清扫下一轮就删)——那是
|
||
// 坏配置而不是「不过期」,但也不许因此让启动失败(design/03 §9.6)。
|
||
func ApplyReviewTuning(cfg ReviewConfig, panel runtimeconfig.MemoryTidyTuning, log *slog.Logger) ReviewConfig {
|
||
log = logger.WithSource(baseTidyLog(log), "memory/review")
|
||
|
||
if sec := panelPositiveTTLSec(panel.TempTtlSec, "tempTtlSec", log); sec > 0 {
|
||
cfg.TempTTL = time.Duration(sec) * time.Second
|
||
}
|
||
return cfg
|
||
}
|
||
|
||
// panelPositiveTTLSec 解析**临时记忆 TTL** 旋钮,返回 0 表示「不覆盖」(调用方原样
|
||
// 留着上一档的值,最终由 normalizeReviewConfig 落到代码默认 7 天)。
|
||
//
|
||
// 三种情形分得很清,因为它们该有不同的观测形状(与 tidy_tuning.go 的
|
||
// panelPositiveSec 逐字同构):
|
||
//
|
||
// - `nil`(键没写)⇒ 0,**静默**:没配不是错,正是「代码默认兜底」那一档;
|
||
// - 非正数(写了 0 或负数)⇒ 0 + **WARN**:它会让每条临时记忆落库即过期
|
||
// (与「不过期」恰好相反),属于坏配置,必须留下痕迹;
|
||
// - 正数 ⇒ 原值。
|
||
func panelPositiveTTLSec(v *int, key string, log *slog.Logger) int {
|
||
if v == nil {
|
||
return 0
|
||
}
|
||
if *v <= 0 {
|
||
log.Warn("memory-tidy.json 的临时记忆 TTL 不可用,回落代码默认(非正 TTL 会让临时记忆落库即过期)",
|
||
"key", key, "value", *v, "fallback_sec", tempTtlSec)
|
||
return 0
|
||
}
|
||
return *v
|
||
}
|