fix(memory): 记忆层启动接线/并发/落盘一致性整备

按设计方案整顿记忆系统,收敛一批"单测照不出、只在长跑生产里暴露"的缺陷:

- 启动接线:initMemoryStack 残留 `defer distiller.Stop()`,规则蒸馏
  10min 心跳启动即死。改为由调用点 cleanup 停机,并补 Stopped() 探针 +
  TestInitMemoryStackKeepsDistillerRunning / TestStartKeepsLoopRunningUntilStop。
- L0 相关性上下文:SetDenseSpace 注入稠密空间时回填已有事件的稠密向量,
  否则旧事件走稀疏余弦、新事件走稠密余弦,同一次 Prune 里两种尺度混排。
- 文档检索:QueryScored 访问计数从读锁内写移出(-race 竞争),更新后置脏,
  优雅关停可落盘、FindColdDocs 冷度判据跨重启不再失真。
- 蒸馏管线:只 flush 未落盘记录(persisted 标记)、蒸馏成功后从 raw 文件
  删除对应行、原子写文件,修重启重复蒸馏导致 mention_count 膨胀。
- 图库:全量 Recall 加实体上限(内部整备路径,防大图整表进内存);
  ClearSentenceID 补写锁;Commit/upsertEntity 计数语义注释澄清。
- 索引器:recalled 去重集加 FIFO 上限,防长跑进程自动注入越来越沉默。
- 文档/媒体注释修正;README 记忆层流程对齐跨模态召回。

验证:go build ./...、go vet、go test -race
./internal/memory/... ./internal/agent/core/... ./cmd/homed/... 全绿。
This commit is contained in:
JianFeeeee
2026-09-15 06:32:36 +08:00
parent fd03e8b905
commit c34e7cbb25
12 changed files with 413 additions and 61 deletions

View File

@ -185,9 +185,9 @@ func TestDistillOnceBatchLimit(t *testing.T) {
func TestExtractKeyTriples(t *testing.T) {
tests := []struct {
user string
user string
assistant string
check func([]memory.Triple) bool
check func([]memory.Triple) bool
}{
{
user: "我住在北京",
@ -201,7 +201,7 @@ func TestExtractKeyTriples(t *testing.T) {
},
},
{
user: "我在杭州读书",
user: "我在杭州读书",
assistant: "好的",
check: func(triples []memory.Triple) bool {
for _, tr := range triples {
@ -264,3 +264,25 @@ func TestTruncate(t *testing.T) {
t.Errorf("expected 'hi', got %q", truncate("hi", 10))
}
}
// TestStartKeepsLoopRunningUntilStop 锁死 Start/Stop 的接线契约:
// Start 之后蒸馏循环必须处于运行态,只有显式 Stop 才退出。
//
// 这条回归的直接来源:main() 拆分时 initMemoryStack 里残留一句
// defer distiller.Stop(),函数返回即 cancel,循环启动即死——而
// TestDistillOnce* 直接调 distillOnce,绕过了 Start/Stop,照不出这个洞。
func TestStartKeepsLoopRunningUntilStop(t *testing.T) {
d := NewDistiller(nil, t.TempDir(), DistillerConfig{
Interval: time.Hour, // 不依赖 tick,只验证循环存活
RetentionDays: 7,
BatchSize: 50,
})
d.Start()
if d.Stopped() {
t.Fatal("Start 之后蒸馏循环必须处于运行态(不可被 defer Stop 杀掉)")
}
d.Stop()
if !d.Stopped() {
t.Fatal("Stop 之后应处于已停止态")
}
}