package main import ( "fmt" "time" "git.yeij.top/AskaEth/Cyrene_v2/internal/config" "git.yeij.top/AskaEth/Cyrene_v2/internal/logger" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory" ) // 本文件把 D3「入库前审查」(DevDocs/design/23-记忆整理与去重 §15.1 D3)接进 // 组合根。三件事只在这里定: // // - **形态**:记忆管理会话(复用既有的 sess-memorytidy 装配,见 // memory.TidySessionID)的有界后台队列,不是会话内派 subagent。它与整理 // 心跳并列启动、在 close 里先于关库停止; // - **依赖面**:只给它记忆存储与 embedder —— 服务侧拿不到任何会话/历史读数, // 因此「独立会话、零上下文、不碰主会话上下文」(§15 D3)是结构性成立的, // 而不是靠自觉; // - **审计**:接到网关的 AuditEvent(口径 memory.review.ingest)。出口在调用 // 时才解析 a.gw —— 网关比本服务晚组装(wireFrontend),与 memoryTidy 同 // 一手法。未就绪时只记日志,绝不假装写过审计。 // // 关闭(MEMORY_REVIEW_ENABLED=false)时**不建、不接**:store 的写入路径因此 // 逐字回到本次改动之前(纯增量,无破坏性变更)。 func (a *app) wireMemoryReview() error { cfg, err := config.LoadMemoryReviewConfig(envPath) if err != nil { return fmt.Errorf("memory review config: %w", err) } if !cfg.Enabled { logger.WithSource(a.log, "memory/review").Info( "memory ingest review disabled by configuration", "env", "MEMORY_REVIEW_ENABLED") return nil } reviewer, err := memory.NewPreInsertReviewer(memory.ReviewConfig{ Enabled: cfg.Enabled, QueueSize: cfg.QueueSize, ScanLimit: cfg.ScanLimit, Timeout: time.Duration(cfg.TimeoutSec) * time.Second, // 召回门复用记忆整理的既有阈值(design/23 §13 开放点 2 = 0.80): // 入库前审查与夜间整理必须用同一把尺子,否则同一对条目会有两个结论。 Threshold: a.cfg.MemoryTidy.RecallThreshold, }, memory.ReviewDeps{ Store: a.memStore, Embedder: a.embedder, Audit: a.auditMemoryReview, Log: a.log, }) if err != nil { return fmt.Errorf("memory ingest review: %w", err) } a.memStore.AttachIngestReviewer(reviewer) a.memoryReview = reviewer return nil } // auditMemoryReview 把一条审查结论写进网关审计链。它**只带决定、依据与 id**, // 绝不带任何记忆正文(design §15 D3-8「记忆正文不进日志」)。 func (a *app) auditMemoryReview(who, what, result string) { if a.gw != nil { a.gw.AuditEvent(who, what, result) return } logger.WithSource(a.log, "memory/review").Info( "memory ingest review audit dropped: gateway not ready", "who", who, "what", what, "result", result) }