Files
Cyrene_v2/internal/runtime/memory/tidy_config.go
T
AskaEth 91a3209c9b
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(memory): 记忆整理 v1(design/23)——低峰定期扫描 + 事实包含判定 + 规则否决 + 只产建议队列(不自动应用)
2026-10-05 11:29:36 +08:00

164 lines
6.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package memory
import (
"time"
"git.yeij.top/AskaEth/Cyrene_v2/internal/config"
)
// 本文件收拢记忆整理(design/23-记忆整理与去重)全部的标识、默认值与旋钮,
// 使运行路径上不出现裸魔法数字(AGENTS §10),并让「设计稿默认值」
// 只有一处定义、可被复核。
//
// 形态(design/23 §4 方案 A/C):整理是一件**低峰定期后台任务** —— 跑在
// 专属隔离会话下、零上下文启动、只产候选**不写记忆**,绝不是会话内派
// subagent。
const (
// TidySessionID 是记忆整理运行的专属隔离会话标识(design/23 §4.1 B)。
// 它与主会话 history.MainSessionID("web")不同,因此整理不改主人的心情、
// 不进主会话泳道(cmd/cyrene 的 isConversationLane 对它恒为 false),
// 默认也不进 flowPub 的图源白名单(design/23 §4.1 F 的可选小开关默认关)。
TidySessionID = "sess-memorytidy"
// TidyRuleVersion 是 T0 规则集的版本号,进候选的幂等键(design/23 §9):
// 规则不变时重复运行绝不重复产候选;升版后同一数据重新产候选,
// 因此被 reject 的对可以重评。**改判定规则必须同时升它**。
TidyRuleVersion = "t0.1"
// defaultTidyIntervalSec 是整理周期秒数,默认 86400 = 每天 1 次
// (design/23 §13 开放点 8 的建议值)。
defaultTidyIntervalSec = 86400
// defaultTidyQuietStartHour / defaultTidyQuietEndHour 复用 backthink 的
// 安静时段语义(默认 23:00-07:00,窗口跨午夜;两端相等且非 0 则关闭窗口)
// —— 整理只在低峰跑(design/23 §4)。
defaultTidyQuietStartHour = 23
defaultTidyQuietEndHour = 7
// defaultTidyRecallThreshold 是候选召回的余弦**召回门**,0.80
// (design/23 §5.1 T0-2 与 §13 开放点 2 的建议值)。它只决定「哪些对值得
// 看一眼」:能否合并由包含判定 + 规则否决决定,绝不退化成阈值判断
// (design/23 §1.2:「必合」0.8330 与「必不合」0.7100 只差 0.12)。
defaultTidyRecallThreshold = 0.80
// defaultTidyScanLimit 是每分区每轮的扫描上限(design/23 §9)。
defaultTidyScanLimit = 5000
// maxTidyCompareItems 是每分区每轮**真正参与两两比对**的条目上限。它是
// 本实现的性能护栏:两两相似度是 O(n²·dim),5000 条会到十亿级乘加,
// 低峰任务也不该跑成那样。超限时按「最新优先」截断并记一条 WARN
// (绝不静默),见 tidy_recall.go。
maxTidyCompareItems = 400
// defaultTidyCandidateLimit 是每分区每轮的候选上限(design/23 §9)。
defaultTidyCandidateLimit = 20
// tidyLowConfidenceThreshold 是置信度否决门(design/23 §5.2-6):
// confidence 不能当合并门([2]0.7 与 [5]0.8 是同一事实),但必须当否决门。
tidyLowConfidenceThreshold = 0.7
// tidyEmbedBatch 是单次 Embed 调用的文本条数上限,避免把整个分区的文本
// 一次性交给远端 embedding API。
tidyEmbedBatch = 64
// minTidyContentRunes 是参与比对的最小内容长度(按 rune)。过短的内容
// (如单字)没有可判定的事实要素,进比对只会产出噪声,直接跳过。
minTidyContentRunes = 3
)
// TidyConfig 携带记忆整理的调优旋钮。零值经 normalize 选取安全默认值,
// 因此半填的 Config 仍可用。
type TidyConfig struct {
// Enabled 是总开关。关闭时组合根不建服务、不跑任何整理
// (design/23 §4 的 MEMORY_TIDY_ENABLED)。
Enabled bool
// Interval 是整理周期。为零时由 IntervalSec(或其默认值)推导。
Interval time.Duration
// IntervalSec 是整理周期秒数,映射自 config.MemoryTidyConfig.IntervalSec。
// 设置了 Interval 时忽略。它是**下限**:安静时段每天只有一次,
// 因此实际间隔取两者中较长者。
IntervalSec int
// QuietStartHour / QuietEndHour 以本地钟点约束低峰窗口
// (复用 backthink 语义:窗口可跨午夜,两端相等且非 0 则关闭窗口)。
QuietStartHour int
QuietEndHour int
// RecallThreshold 是候选召回的余弦门(见 defaultTidyRecallThreshold)。
// 非正用默认值。
RecallThreshold float64
// ScanLimit 是每分区每轮扫描条数上限。非正用默认值。
ScanLimit int
// CandidateLimit 是每分区每轮候选条数上限。非正用默认值。
CandidateLimit int
}
// TidyConfigFromConfig 把 config.MemoryTidyConfig(env 侧旋钮)映射成
// TidyConfig,env 未暴露的项保持零值,交给 normalize 填默认值。
func TidyConfigFromConfig(cfg config.MemoryTidyConfig) TidyConfig {
return TidyConfig{
Enabled: cfg.Enabled,
IntervalSec: cfg.IntervalSec,
QuietStartHour: cfg.QuietStartHour,
QuietEndHour: cfg.QuietEndHour,
RecallThreshold: cfg.RecallThreshold,
ScanLimit: cfg.ScanLimit,
CandidateLimit: cfg.CandidateLimit,
}
}
// normalize 用设计稿默认值填充 TidyConfig 的零值字段。
func normalizeTidyConfig(cfg TidyConfig) TidyConfig {
if cfg.IntervalSec <= 0 {
cfg.IntervalSec = defaultTidyIntervalSec
}
if cfg.Interval <= 0 {
cfg.Interval = time.Duration(cfg.IntervalSec) * time.Second
}
if cfg.QuietStartHour == 0 && cfg.QuietEndHour == 0 {
cfg.QuietStartHour = defaultTidyQuietStartHour
cfg.QuietEndHour = defaultTidyQuietEndHour
}
if cfg.RecallThreshold <= 0 {
cfg.RecallThreshold = defaultTidyRecallThreshold
}
if cfg.ScanLimit <= 0 {
cfg.ScanLimit = defaultTidyScanLimit
}
if cfg.CandidateLimit <= 0 {
cfg.CandidateLimit = defaultTidyCandidateLimit
}
return cfg
}
// quietWindowDisabled 报告低峰窗口是否被显式关闭(两端相等且非 0)。
func (c TidyConfig) quietWindowDisabled() bool {
return c.QuietStartHour != 0 && c.QuietStartHour == c.QuietEndHour
}
// inQuietHours 报告 at 是否落在低峰窗口内。窗口可跨午夜(23:00-07:00);
// 与 backthink.inQuietHours 同语义。
func (c TidyConfig) inQuietHours(at time.Time) bool {
if c.quietWindowDisabled() {
return true
}
h := at.Hour()
if c.QuietStartHour <= c.QuietEndHour {
return h >= c.QuietStartHour && h < c.QuietEndHour
}
return h >= c.QuietStartHour || h < c.QuietEndHour
}
// nextQuietStart 返回 at 之后(严格晚于 at)的下一个低峰窗口起点:
// 今天/明天的 QuietStartHour 整点。窗口关闭时返回零值时间,调用方据此
// 改走纯周期路径。
func (c TidyConfig) nextQuietStart(at time.Time) time.Time {
if c.quietWindowDisabled() {
return time.Time{}
}
start := time.Date(at.Year(), at.Month(), at.Day(), c.QuietStartHour, 0, 0, 0, at.Location())
if !start.After(at) {
start = start.AddDate(0, 0, 1)
}
return start
}