feat(memory): 记忆整理调优面板读写(三态 + 热生效 + 面板表单)

主人 2026-10-07 两条口径:「.env 只做兜底用,后端自动创建独立的面板配置文件
之后就走面板配置文件,这些键优先从面板配置上取」+「频率必须调,可以调到 1 小时
或者三小时,或者动态分配(白天短、静默期长)」。

此前 $DATA_DIR/config/memory-tidy.json 只有读路径(装配期读一次 + 启动播种):
主人要改频率只能改文件 + 重启后端。本切片补上「读回来 → 改 → 落盘 → 当场生效」。

后端(读写端点,契约待塔登记 v0.92):
- GET  /api/v1/admin/memories/tidy/tuning  读盘上 7 键值 + 取值规则 specs +
  缺位时 .env 档的进程生效值(与 /admin/config-facts 同源)
- PUT  同路径:三态补丁(键缺位 = 不改动 / 合法值 = 采用 / 非法值 = 400 且盘上一
  个字节都不动)+ null 清除哨兵(删键 ⇒ 回到下一档)+ writeAuth + 审计
  memory.tidy.tuning;值一个都没变时不落盘,文件解不开时拒绝覆盖(409)
- 落盘走 runtimeconfig.PatchMemoryTidyTuning(原子写 temp+rename,与
  SeedMemoryTidy/SaveWebSearch 同族同手法);补丁语义保留下受管的其它键
  (t1Judge/t1PerWindow/autoApplyPerRound 原样保留)
- 热生效:保存后按「面板 > .env > 代码默认」重算并叠到运行期对象;整理档会
  重排心跳(否则新周期要等旧周期那一觉睡完),审查档当场改 ingestGate/tempTtlSec。
  未接线/失败时如实回 hot:false + hotError,绝不把「落盘了」说成「生效了」

前端(Miuix):
- 记忆管理页新增「记忆整理调优」卡:7 键表单(中文标签、单位、留空⇒跟随谁、
  env 档当前值、坏值就地报错并拦住保存、保存后回显从磁盘读回的真值)
- 试算卡的 recallMode 按契约 v0.91 如实渲染:embedding / lexical(已降级)/
  mixed(部分分区降级)/ ""(无候选可比)——空串不再渲染成空标签;并把
  error.code=tidy_recall_unavailable 单列一句(「尝试过但向量源不可用」,与
  「没有候选可比」显式区分)

验证:go test ./internal/{services/gateway,runtime/memory,config/runtimeconfig}
全绿;三态/清除/坏文件拒写/同值不重写/热生效接线各有用例,并逐条做过变异自证;
tsc --noEmit + vite build 通过;两个 esbuild 前端测试 39 + 34 + 41 checks 通过。
This commit is contained in:
AskaEth committed 2026-10-13 21:59:18 +08:00
1 parent 17c41265e8
commit eddf921229
18 files changed
+2604 -5

No files matched your search

@@ -8,6 +8,8 @@ import (
"fmt"
"strings"
"time"
"git.yeij.top/AskaEth/Cyrene_v2/internal/config/runtimeconfig"
)
// 本文件是入库前审查(design/23 §15.1 D3)的存储层:候选读取、软合并落定、
@@ -89,6 +91,24 @@ func (s *Store) ingestReviewer() *PreInsertReviewer {
return s.review
}
// ApplyIngestPanelTuning 把面板值**当场**叠到已接入的入库前审查上(切片 MEMPANEL / M4)。
//
// 网关握着的是记忆存储(`deps.Memories`),而不是审查服务本身,因此这条转调就是面板
// 写端点触达审查侧的**唯一**通道(不为此新开依赖面:没有新的 Deps 字段、没有新的
// Server 字段)。
//
// 未接入审查(`MEMORY_REVIEW_ENABLED=false`)时是**空操作 + nil**:那时两个键本来
// 就不生效,运行期没有任何旧值要改,返回错误只会让一次完全正常的保存报「热生效失败」。
// 指针的读写走既有的 `reviewMu`(与 AttachIngestReviewer 同一把锁),不会与并发接入
// 交手。
func (s *Store) ApplyIngestPanelTuning(panel runtimeconfig.MemoryTidyTuning) error {
reviewer := s.ingestReviewer()
if reviewer == nil {
return nil
}
return reviewer.ApplyPanelTuning(panel)
}
// enqueueReview 把刚落库的条目投入审查队列。非阻塞:队列满时审查方自己记
// WARN(它才是唯一知道队列状态的一侧),写入方绝不因此阻塞或失败。
func (s *Store) enqueueReview(item MemoryItem) {