package config import ( "fmt" "reflect" ) // MemoryReviewConfig 是 D3「入库前审查」(design/23-记忆整理与去重 §15.1 D3) // 在配置层的旋钮:新记忆落库后,由独立的记忆管理会话异步判定它是 // merge / modify / create / skip。 // // 为什么用独立加载器而不是挂进 Config 根结构:Config 由 Load 统一解码,而 // 本切片刻意**不碰** Config / 记忆整理那组键(并行切片正在改它们)。这里复用 // 与 LoadLLMDefaults 同一手法(parseDotEnv + 同一份 `env:"…"` 解码器), // 因此优先级与 Load 逐字一致:进程环境变量 > `.env` > 标签默认值。 // envPath 为空表示不看 `.env`(与 Load 的约定一致)。 type MemoryReviewConfig struct { // Enabled 是入库前审查总开关,默认 true。依据:主人 2026-10-05 的裁定 // 就是「入库之前先走记忆管理会话审查,而不是反复新建」,而 T0 判定是 // **纯规则、零模型调用**,默认开启是安全的增量。置 false 时组合根不建、 // 不接、不跑 —— 写入路径行为逐字回到本次改动之前。 Enabled bool `env:"MEMORY_REVIEW_ENABLED" default:"true"` // QueueSize 是有界审查队列的长度。队列满时该条**不做审查**:记忆本身 // 早已落库(绝不因队列满而丢记忆),只记一条带来源的 WARN。 QueueSize int `env:"MEMORY_REVIEW_QUEUE_SIZE" default:"256"` // ScanLimit 是每次审查在同一分区内最多看多少条既有记忆(top-K 上限, // design/23 §15 D3-4「每条待写记忆的审查有界」)。 ScanLimit int `env:"MEMORY_REVIEW_SCAN_LIMIT" default:"200"` // TimeoutSec 是单条审查的墙钟上限(秒)。超时 ⇒ **不做任何改写**, // 记忆照旧保留 + WARN(design/23 §15 D3-5 的 fail-safe)。 TimeoutSec int `env:"MEMORY_REVIEW_TIMEOUT_SEC" default:"20"` } // LoadMemoryReviewConfig 读取入库前审查的旋钮。非法(负数)值不在这里报错, // 而是与项目其它旋钮同一口径 —— **≤0 视为「用默认」**,由 memory 包 normalize // 收敛,避免一个笔误让进程起不来。 func LoadMemoryReviewConfig(envPath string) (MemoryReviewConfig, error) { fromEnv := map[string]string{} if envPath != "" { m, err := parseDotEnv(envPath) if err != nil { return MemoryReviewConfig{}, fmt.Errorf("config: read %s: %w", envPath, err) } fromEnv = m } var cfg MemoryReviewConfig if err := decodeValue(reflect.ValueOf(&cfg), fromEnv); err != nil { return MemoryReviewConfig{}, err } return cfg, nil }