feat(memory): 整理频率走面板配置(面板 > env > 默认)+ 分时段动态间隔
主人 2026-10-07:「.env 只做兜底…优先从面板配置上取」+「频率必须调…或者动态
分配,比如白天间隔短一点 晚上到了静默时期稍微长一点」。
- memory-tidy.json 新增四键:intervalSec / quietIntervalSec / quietStartHour /
quietEndHour(*int + 指针判定 => 显式 0 是合法值、缺位才回落)
- 优先级逐键三档:面板 > env(既有键,未增删)> 代码默认;坏值不覆盖 + WARN,
且不启动失败
- 动态间隔由纯函数 intervalAt(at) 决定,调度侧每一拍现算 => 跑完按当时时段重算
- 🔴 语义变更(有意,代码注释已登记,待补设计修订):安静时段从「唯一可跑时段」
变成「降频时段」—— 旧的「安静窗外一律 continue」被删,否则 intervalSec 永远
是空壳(白天唤醒必被跳过);窗口仍决定档位与补跑判据
- 6 个变异(含「加回旧的那道门」=> 端到端用例红)
This commit is contained in:
1 parent
6deab2e836
commit
d680390300
11 files changed
+832
-54
No files matched your search
@@ -10,8 +10,8 @@ import (
|
||||
// 使运行路径上不出现裸魔法数字(AGENTS §10),并让「设计稿默认值」
|
||||
// 只有一处定义、可被复核。
|
||||
//
|
||||
// 形态(design/23 §4 方案 A/C):整理是一件**低峰定期后台任务** —— 跑在
|
||||
// 专属隔离会话下、零上下文启动、只产候选**不写记忆**,绝不是会话内派
|
||||
// 形态(design/23 §4 方案 A/C):整理是一件**定期后台任务**(安静时段降频)——
|
||||
// 跑在专属隔离会话下、零上下文启动、只产候选**不写记忆**,绝不是会话内派
|
||||
// subagent。
|
||||
|
||||
const (
|
||||
@@ -37,8 +37,11 @@ const (
|
||||
defaultTidyIntervalSec = 86400
|
||||
|
||||
// defaultTidyQuietStartHour / defaultTidyQuietEndHour 复用 backthink 的
|
||||
// 安静时段语义(默认 23:00-07:00,窗口跨午夜;两端相等且非 0 则关闭窗口)
|
||||
// —— 整理只在低峰跑(design/23 §4)。
|
||||
// 安静时段语义(默认 23:00-07:00,窗口跨午夜;两端相等且非 0 则关闭窗口)。
|
||||
//
|
||||
// 🔴 2026-10-07 起窗口不再等于「唯一可跑时段」:它是**降频档位**的判据
|
||||
// (安静时段用 defaultTidyIntervalSec 之外的另一档,见 intervalAt),并且
|
||||
// 仍然决定「首次/未跑过的窗口要补跑一轮」(dueNow 判据 1/3)。
|
||||
defaultTidyQuietStartHour = 23
|
||||
defaultTidyQuietEndHour = 7
|
||||
|
||||
@@ -53,7 +56,7 @@ const (
|
||||
|
||||
// maxTidyCompareItems 是每分区每轮**真正参与两两比对**的条目上限的兜底默认
|
||||
// 值(可直接从 MEMORY_TIDY_COMPARE_LIMIT 调,见 TidyConfig.CompareLimit)。
|
||||
// 两两相似度是 O(n²·dim),5000 条会到十亿级乘加,低峰任务也不该跑成那样。
|
||||
// 两两相似度是 O(n²·dim),5000 条会到十亿级乘加,后台任务也不该跑成那样。
|
||||
// 2026-10-06 由 400 抬到 2000:首轮真跑 459 条的分区被旧值截到 400,
|
||||
// 于是 compared(705) < scanned(784) —— 截断确实如实登记了,但护栏成为结
|
||||
// 果的主因本身就是缺陷。超限仍按「最新优先」截断并记一条 WARN(绝不静默),
|
||||
@@ -129,14 +132,28 @@ type TidyConfig struct {
|
||||
// Enabled 是总开关。关闭时组合根不建服务、不跑任何整理
|
||||
// (design/23 §4 的 MEMORY_TIDY_ENABLED)。
|
||||
Enabled bool
|
||||
// Interval 是整理周期。为零时由 IntervalSec(或其默认值)推导。
|
||||
// Interval 是整理周期(**非安静时段**)。为零时由 IntervalSec(或其默认值)推导。
|
||||
Interval time.Duration
|
||||
// IntervalSec 是整理周期秒数,映射自 config.MemoryTidyConfig.IntervalSec。
|
||||
// 设置了 Interval 时忽略。它是**下限**:安静时段每天只有一次,
|
||||
// 因此实际间隔取两者中较长者。
|
||||
// IntervalSec 是整理周期秒数(**非安静时段**),映射自
|
||||
// config.MemoryTidyConfig.IntervalSec(env),并可由
|
||||
// `$DATA_DIR/config/memory-tidy.json` 的 `intervalSec` 覆盖(面板优先、`.env`
|
||||
// 兜底,唯一实现点是 ApplyTidyScheduleTuning)。设置了 Interval 时忽略。
|
||||
IntervalSec int
|
||||
// QuietIntervalSec 是**安静时段**的整理周期秒数(2026-10-07 主人「或者动态
|
||||
// 分配,比如白天间隔短一点,晚上到了静默时期稍微长一点」)。
|
||||
//
|
||||
// 非正 ⇒ 等于 IntervalSec:**缺省 = 不因时段换档**,而不是「夜里不跑」
|
||||
// (normalize 里那一条是唯一落点)。它没有 env 键(`.env` 已冻结),只由
|
||||
// `$DATA_DIR/config/memory-tidy.json` 的 `quietIntervalSec` 提供。
|
||||
QuietIntervalSec int
|
||||
// QuietInterval 是安静时段的整理周期。为零时由 QuietIntervalSec 推导。
|
||||
QuietInterval time.Duration
|
||||
// QuietStartHour / QuietEndHour 以本地钟点约束低峰窗口
|
||||
// (复用 backthink 语义:窗口可跨午夜,两端相等且非 0 则关闭窗口)。
|
||||
//
|
||||
// 🔴 2026-10-07 起窗口的**语义一字未改**(inQuietHours 逐字不动),但它的
|
||||
// **作用面**收窄了一格:从「整理唯一可跑的时段」变成「整理降频的时段」
|
||||
// (见 intervalAt)。理由是主人的频率口径要求白天也跑,且白天跑得比夜里勤。
|
||||
QuietStartHour int
|
||||
QuietEndHour int
|
||||
// RecallThreshold 是候选召回的余弦门(见 defaultTidyRecallThreshold)。
|
||||
@@ -165,6 +182,10 @@ type TidyConfig struct {
|
||||
|
||||
// TidyConfigFromConfig 把 config.MemoryTidyConfig(env 侧旋钮)映射成
|
||||
// TidyConfig,env 未暴露的项保持零值,交给 normalize 填默认值。
|
||||
//
|
||||
// 它是**兜底层**:组合根随后用 ApplyTidyScheduleTuning 把
|
||||
// `$DATA_DIR/config/memory-tidy.json` 的调度旋钮叠上来(面板优先、env 兜底);
|
||||
// env 没有的项(如 quietIntervalSec)在这里保持零值,由 normalize 取默认。
|
||||
func TidyConfigFromConfig(cfg config.MemoryTidyConfig) TidyConfig {
|
||||
return TidyConfig{
|
||||
Enabled: cfg.Enabled,
|
||||
@@ -186,6 +207,14 @@ func normalizeTidyConfig(cfg TidyConfig) TidyConfig {
|
||||
if cfg.Interval <= 0 {
|
||||
cfg.Interval = time.Duration(cfg.IntervalSec) * time.Second
|
||||
}
|
||||
if cfg.QuietIntervalSec <= 0 {
|
||||
// 缺省 = 与普通间隔同档。放在 IntervalSec 归一之后,因此「面板/env 都没给
|
||||
// quietIntervalSec」时它取的是**已解析的** intervalSec,而不是魔法数字。
|
||||
cfg.QuietIntervalSec = cfg.IntervalSec
|
||||
}
|
||||
if cfg.QuietInterval <= 0 {
|
||||
cfg.QuietInterval = time.Duration(cfg.QuietIntervalSec) * time.Second
|
||||
}
|
||||
if cfg.QuietStartHour == 0 && cfg.QuietEndHour == 0 {
|
||||
cfg.QuietStartHour = defaultTidyQuietStartHour
|
||||
cfg.QuietEndHour = defaultTidyQuietEndHour
|
||||
@@ -230,6 +259,32 @@ func (c TidyConfig) inQuietHours(at time.Time) bool {
|
||||
return h >= c.QuietStartHour || h < c.QuietEndHour
|
||||
}
|
||||
|
||||
// intervalAt 按 **at 所在时段** 选整理周期:安静时段用 QuietInterval,其余用
|
||||
// Interval(= design/12 §N A1 给后台思考心跳定下的「窗口内降频」口径,搬进整理)。
|
||||
//
|
||||
// 主人 2026-10-07 的原话是「**频率必须调**,可以调到 1 小时或者三小时,**或者动态
|
||||
// 分配**,比如白天间隔短一点,晚上到了静默时期稍微长一点」——这个函数就是「动态
|
||||
// 分配」的唯一判据。
|
||||
//
|
||||
// 它是**纯函数**(只读 cfg 与入参),因此心跳可以在**每一拍之前**重算它:跨过
|
||||
// 安静时段边界之后,下一拍自然换档,而不是启动时定死一次(调度侧的用法见
|
||||
// TidyService.nextDelay)。
|
||||
//
|
||||
// 两条口径:
|
||||
// - 安静判定**直接复用** inQuietHours,跨午夜口径与其余各处以字节为单位一致,
|
||||
// 这里不另写一份「越走越偏」的窗口算法;
|
||||
// - 窗口被显式关闭(两端相等且非 0)时不换档 —— 关闭窗口的意思是「没有低峰
|
||||
// 这回事」,而不是「全天都算低峰」。
|
||||
func (c TidyConfig) intervalAt(at time.Time) time.Duration {
|
||||
if c.quietWindowDisabled() {
|
||||
return c.Interval
|
||||
}
|
||||
if c.QuietInterval > 0 && c.inQuietHours(at) {
|
||||
return c.QuietInterval
|
||||
}
|
||||
return c.Interval
|
||||
}
|
||||
|
||||
// nextQuietStart 返回 at 之后(严格晚于 at)的下一个低峰窗口起点:
|
||||
// 今天/明天的 QuietStartHour 整点。窗口关闭时返回零值时间,调用方据此
|
||||
// 改走纯周期路径。
|
||||
|
||||
Reference in new issue
Block a user