feat(memory): 记忆整理 v1(design/23)——低峰定期扫描 + 事实包含判定 + 规则否决 + 只产建议队列(不自动应用)
This commit is contained in:
1 parent
012e9f3ad4
commit
91a3209c9b
25 files changed
+3418
-13
No files matched your search
@@ -53,6 +53,7 @@ type Config struct {
|
||||
Channels ChannelsConfig
|
||||
Context ContextConfig
|
||||
Guardrail GuardrailConfig
|
||||
MemoryTidy MemoryTidyConfig
|
||||
}
|
||||
|
||||
// ServerConfig holds the single gateway listener settings.
|
||||
@@ -228,6 +229,33 @@ type BackgroundConfig struct {
|
||||
KindWindowSec int `env:"THINK_KIND_WINDOW_SEC" default:"7200"`
|
||||
}
|
||||
|
||||
// MemoryTidyConfig 是记忆整理(design/23-记忆整理与去重)的开关与配额旋钮。
|
||||
// 它映射进 memory.TidyConfig;未暴露为 env 的项(更低频的调参)由该包取
|
||||
// 设计稿默认值。所有键的默认值都取设计稿的默认/建议值(design/23 §13)。
|
||||
type MemoryTidyConfig struct {
|
||||
// Enabled 是总开关,默认 true(design/23 §4:可用 MEMORY_TIDY_ENABLED=false
|
||||
// 关闭)。整理**只产建议、绝不自动改记忆**,因此默认开启是安全的;
|
||||
// 它带来的是每轮一次的记忆向量化(召回用)与一批候选行。
|
||||
Enabled bool `env:"MEMORY_TIDY_ENABLED" default:"true"`
|
||||
// IntervalSec 是整理周期(秒),默认 86400 = 每天 1 次(design/23 §13
|
||||
// 开放点 8 的建议值)。它是下限:实际在安静时段起点跑,因此间隔取
|
||||
// 「安静时段每天一次」与它之中较长者。
|
||||
IntervalSec int `env:"MEMORY_TIDY_INTERVAL_SEC" default:"86400"`
|
||||
// QuietStartHour / QuietEndHour 是低峰窗口(本地钟点),默认复用
|
||||
// backthink 的 23:00-07:00 语义(窗口可跨午夜;两端相等且非 0 则关闭窗口,
|
||||
// 改为纯周期)。整理只在低峰跑,且**从不推送**(design/23 §4)。
|
||||
QuietStartHour int `env:"MEMORY_TIDY_QUIET_START_HOUR" default:"23"`
|
||||
QuietEndHour int `env:"MEMORY_TIDY_QUIET_END_HOUR" default:"7"`
|
||||
// RecallThreshold 是候选召回的余弦**召回门**,默认 0.80(design/23 §5.1
|
||||
// T0-2 与 §13 开放点 2)。它只决定「哪些对值得看一眼」,能否合并由包含
|
||||
// 判定 + 规则否决决定 —— 绝不是「超过阈值就合」。
|
||||
RecallThreshold float64 `env:"MEMORY_TIDY_RECALL_THRESHOLD" default:"0.80"`
|
||||
// ScanLimit 是每分区每轮的扫描条数上限,默认 5000(design/23 §9)。
|
||||
ScanLimit int `env:"MEMORY_TIDY_SCAN_LIMIT" default:"5000"`
|
||||
// CandidateLimit 是每分区每轮的候选条数上限,默认 20(design/23 §9)。
|
||||
CandidateLimit int `env:"MEMORY_TIDY_CANDIDATE_LIMIT" default:"20"`
|
||||
}
|
||||
|
||||
// SubagentConfig holds the sub-agent orchestration settings (design/05 §7).
|
||||
type SubagentConfig struct {
|
||||
// TimeoutSec bounds one sub-agent's whole run. It feeds the dispatch tool's
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestLoad_MemoryTidyDefaults 锁定记忆整理(design/23 §4/§9/§13)的 env 契约默认值。
|
||||
// 它们必须与 design/23 的默认/建议值一致,也必须与 `memory` 包的兜底默认值一致
|
||||
// (两处不一致会让「没配这个键」与「配了但解析失败」走向不同的行为)。
|
||||
func TestLoad_MemoryTidyDefaults(t *testing.T) {
|
||||
t.Setenv("JWT_SECRET", strings.Repeat("s", 40))
|
||||
|
||||
cfg, err := Load("")
|
||||
if err != nil {
|
||||
t.Fatalf("Load() error: %v", err)
|
||||
}
|
||||
got := cfg.MemoryTidy
|
||||
if !got.Enabled {
|
||||
t.Error("MEMORY_TIDY_ENABLED 默认应为 true(design §4:可用 false 关闭)")
|
||||
}
|
||||
if got.IntervalSec != 86400 {
|
||||
t.Errorf("MEMORY_TIDY_INTERVAL_SEC = %d, want 86400(每天 1 次)", got.IntervalSec)
|
||||
}
|
||||
if got.QuietStartHour != 23 || got.QuietEndHour != 7 {
|
||||
t.Errorf("低峰窗口 = %d-%d, want 23-7(复用 backthink 语义)", got.QuietStartHour, got.QuietEndHour)
|
||||
}
|
||||
if got.RecallThreshold != 0.80 {
|
||||
t.Errorf("MEMORY_TIDY_RECALL_THRESHOLD = %v, want 0.80(design §13 开放点 2 的建议值)", got.RecallThreshold)
|
||||
}
|
||||
if got.ScanLimit != 5000 || got.CandidateLimit != 20 {
|
||||
t.Errorf("配额 = %d/%d, want 5000/20(design §9)", got.ScanLimit, got.CandidateLimit)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoad_MemoryTidyEnvOverrides 锁定键名与类型:写进 .env 的旋钮真的生效
|
||||
// (键名写错会静默沿用默认值,这类错误只能靠这条用例抓住)。
|
||||
func TestLoad_MemoryTidyEnvOverrides(t *testing.T) {
|
||||
t.Setenv("JWT_SECRET", strings.Repeat("s", 40))
|
||||
t.Setenv("MEMORY_TIDY_ENABLED", "false")
|
||||
t.Setenv("MEMORY_TIDY_INTERVAL_SEC", "3600")
|
||||
t.Setenv("MEMORY_TIDY_QUIET_START_HOUR", "1")
|
||||
t.Setenv("MEMORY_TIDY_QUIET_END_HOUR", "5")
|
||||
t.Setenv("MEMORY_TIDY_RECALL_THRESHOLD", "0.7")
|
||||
t.Setenv("MEMORY_TIDY_SCAN_LIMIT", "10")
|
||||
t.Setenv("MEMORY_TIDY_CANDIDATE_LIMIT", "3")
|
||||
|
||||
cfg, err := Load("")
|
||||
if err != nil {
|
||||
t.Fatalf("Load() error: %v", err)
|
||||
}
|
||||
got := cfg.MemoryTidy
|
||||
if got.Enabled || got.IntervalSec != 3600 || got.QuietStartHour != 1 || got.QuietEndHour != 5 ||
|
||||
got.RecallThreshold != 0.7 || got.ScanLimit != 10 || got.CandidateLimit != 3 {
|
||||
t.Fatalf("MEMORY_TIDY_* 未生效:%+v", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user