// HTTP 服务器 — 多 Agent 架构(FileGraph + NetworkEventBus + 跨Agent缓存失效) package api import ( "bytes" "context" "encoding/json" "fmt" "io" "log" "net/http" "os" "runtime" "strconv" "strings" "time" "github.com/xiaoxue/memoryweave/internal/api/middleware" "github.com/xiaoxue/memoryweave/internal/api/routes" "github.com/xiaoxue/memoryweave/internal/distill" "github.com/xiaoxue/memoryweave/internal/governance" "github.com/xiaoxue/memoryweave/internal/metrics" "github.com/xiaoxue/memoryweave/internal/models" "github.com/xiaoxue/memoryweave/internal/selfoptimize" "github.com/xiaoxue/memoryweave/internal/storage" ) // normalizeEntity 规整实体名:去特殊字符 + n_前缀 func normalizeEntity(entity string) string { if strings.HasPrefix(entity, "n_") { entity = entity[2:] } // 过滤特殊字符 clean := strings.Map(func(r rune) rune { if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' || r == '-' { return r } return '_' }, strings.ToLower(strings.TrimSpace(entity))) // 合并连续下划线 for strings.Contains(clean, "__") { clean = strings.ReplaceAll(clean, "__", "_") } return strings.Trim(clean, "_") } // skillByNameHandler GET/DELETE /api/v1/skills/{name} 的 method 分支分发器 func skillByNameHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: routes.Skills.Get(w, r) case http.MethodDelete: routes.Skills.Delete(w, r) default: http.Error(w, "method not allowed", http.StatusMethodNotAllowed) } } func NewServer() http.Handler { mux := http.NewServeMux() // ─── 初始化依赖 ────────────────────────────── emb := storage.NewEmbedder(os.Getenv("VLLM_ENDPOINT")) rerank := storage.NewReranker(os.Getenv("RERANK_ENDPOINT"), emb) // 存储后端 ldb := initStorageBackend(os.Getenv("STORAGE_BACKEND"), emb) // 启动时数据目录一致性检查(防止路径混乱导致读取废弃数据) runStartupChecks(os.Getenv("STORAGE_BACKEND")) // G7.2: 注册 LDB getter(供 skill crystallize 使用) routes.RegisterLDBGetter(func() storage.LanceDB { return ldb }) // 冲突检测器(E4.1: Commit 时矛盾检测依赖此实例) conflictDetector := governance.NewConflictDetector() api := routes.NewAPI(ldb, emb, rerank, conflictDetector) // 启动时初始化 Prometheus 指标 go func() { time.Sleep(1 * time.Second) // 等待 Rust sidecar 就绪 if stats, err := ldb.Stats(); err == nil { if total, ok := stats["total_memories"].(float64); ok { metrics.TotalMemories.Set(total) } if total, ok := stats["total_episodes"].(float64); ok { metrics.TotalEpisodes.Set(total) } log.Printf("[metrics] 初始化指标: memories=%.0f", stats["total_memories"]) } }() // 图谱 graphStore := initGraphStore() graphUpdater := governance.NewAutoGraphUpdater(graphStore) graphAPI := routes.NewGraphAPI(graphStore) // G1: Recall 管线挂图谱扩展 api.Pipeline.SetGraphExpander(graphStore) // G4: 自动蒸馏 → 注入图谱更新器 routes.SetGraphUpdater(graphUpdater) // 冲突 conflictAPI := routes.NewConflictAPI(conflictDetector) // ─── 缺口 gapDetector := selfoptimize.NewGapDetector(emb, ldb) gapDetector.EnableGapRedisPersistence() selfoptimize.SetGlobalGapDetector(gapDetector) // 注册全局实例供 recall 回调使用 // 因果追踪持久化 routes.CascadeR.Tracker().EnableCausalRedisPersistence() gapAPI := routes.NewGapAPI(gapDetector) routes.InitGapRepair(gapDetector, ldb) // 共享同一个 GapDetector + LanceDB // G1a: 挂缺隙记录器(0 结果 → 自动记录 miss) api.Pipeline.SetMissRecorder(func(query, namespace string) { gapDetector.RecordMiss(query + " [" + namespace + "]") }) feedbackAPI := routes.NewFeedbackAPI(ldb) // 遗忘器:支持按 Agent 类型设置衰减率 forgetter := governance.NewForgetter() adminAPI := routes.NewAdminAPI(ldb, forgetter, graphStore) agentRegistry := routes.NewAgentRegistry(nil) evalAPI := routes.NewEvalAPI(api.Pipeline, ldb) l3API := routes.WM consolPipe := routes.NewConsolidationPipeline(ldb, graphStore, conflictDetector) consolPipe.SetDataDir("/var/lib/memoryweave/lancedb", "/var/lib/memoryweave/graph.db") // Obsidian obsidian := routes.NewObsidianSyncer("", ldb) // ─── 蒸馏引擎 ─────────────────────────────── llmEndpoint := os.Getenv("LLM_ENDPOINT") llmModel := os.Getenv("LLM_MODEL") if llmModel == "" { llmModel = "deepseek/deepseek-v4-pro" } llmAPIKey := os.Getenv("LLM_API_KEY") if llmAPIKey == "" { llmAPIKey = os.Getenv("API_KEY") } distillEngine := distill.NewEngine(llmEndpoint, llmModel, llmAPIKey) routes.DistillEngineRef = distillEngine // 蒸馏完成 → 自动图谱更新 + 冲突检测 + 被动验证 + 回写内存 distill.OnDistillComplete = func(input distill.DistillInput, result distill.DistillResult) { entityNames := make([]string, len(result.Entities)) for i, e := range result.Entities { entityNames[i] = e.Name } graphUpdater.UpdateFromDistill(&governance.DistillInput{ Content: input.Content, Facts: result.Facts, Entities: entityNames, Namespace: input.Namespace, EpisodeID: input.EpisodeID, }) // 回写蒸馏产物到记忆库(LanceDB) for _, fact := range result.Facts { mem := models.MemoryRecord{ ID: fmt.Sprintf("mem_%d", time.Now().UnixNano()), AgentID: input.AgentID, Namespace: input.Namespace, Content: fact, Category: "distilled", Tier: "normal", QualityScore: result.Overall, Version: 1, CreatedAt: time.Now(), UpdatedAt: time.Now(), DerivedFrom: input.EpisodeID, } if err := ldb.InsertMemory(mem); err != nil { log.Printf("[distill] commit fact failed: %v", err) } } // 冲突检测:加载同 namespace 已有记忆进行比较 zeroVec := make([]float32, 1024) existingMems, _ := ldb.Search("memories", zeroVec, 100, input.Namespace) conflictExisting := make([]map[string]interface{}, 0) for _, m := range existingMems { conflictExisting = append(conflictExisting, map[string]interface{}{ "content": m.Content, "entities": entityNames, // 用当前内容提取的实体做交叉比较 }) } for _, c := range conflictDetector.Scan(input.Content, entityNames, conflictExisting) { if c.Strategy == "latest_wins" { conflictDetector.AutoResolve(c) } } // 被动验证:使用已加载的记忆进行 P1/P2/P3 匹配 validMems := make([]selfoptimize.MemoryForValidation, len(existingMems)) for i, m := range existingMems { validMems[i] = selfoptimize.MemoryForValidation{ ID: m.ID, Content: m.Content, QualityScore: m.QualityScore, } } selfoptimize.Validator.Validate(input.Content, validMems) // 被动验证结果写回 quality_score records := selfoptimize.Validator.GetRecords() for _, r := range records { if r.Confidence > 0 { _ = ldb.Update("memories", r.MemoryID, map[string]any{ "quality_score": r.Confidence, }) } } } // ─── CO_OCCURS 追踪器(持久化到 Redis)─── storage.CoOccurTrackerInstance = storage.NewCoOccurTracker(storage.RedisCoOccurPersist()) storage.LoadCoOccurFromRedis() // ─── Dashboard 持久化到 Redis(restart 不丢指标)─── selfoptimize.EnableRedisPersistence() // ─── VProp 持久化到 Redis(restart 不丢)─── selfoptimize.VProp.EnableVPropRedisPersistence() // ─── Skill 持久化到 Redis(G7: restart 不丢 skills)─── routes.BayesianSkills.EnableRedisPersistence() // ─── V 值传播器(竞争性架构核心)───────────── vPropagator := selfoptimize.VProp // 注入 V 值查询函数到存储层(召回排序融合) storage.VValueProvider = func(memoryID string) float64 { return vPropagator.GetMemVValue(memoryID) } // 在蒸馏完成回调中记录 V 值 originalOnComplete := distill.OnDistillComplete distill.OnDistillComplete = func(input distill.DistillInput, result distill.DistillResult) { // 调用已有的回调 if originalOnComplete != nil { originalOnComplete(input, result) } // 记录 V 值决策:蒸馏成功 = 有用 if len(result.Facts) > 0 { vPropagator.RecordDecision( "distill_"+input.EpisodeID, []string{input.EpisodeID}, "distill", "success", "", ) } // 更新仪表盘指标 selfoptimize.Dash.RecordUseful() // 记录蒸馏质量损失(avg_distill_loss = 1 - Overall) loss := 1.0 - result.Overall if loss < 0 { loss = 0 } if loss > 1 { loss = 1 } selfoptimize.Dash.RecordDistillLoss(loss) } // ─── 跨 Agent 缓存失效回调 ─────────────────── // 收到其他 Agent 的广播 → 失效本地缓存 governance.GlobalEventBus.Subscribe("cache.invalidate", "self") go func() { // 本地处理 cache.invalidate 事件(通过 HTTP self-call) http.HandleFunc("/_internal/cache/invalidate", func(w http.ResponseWriter, r *http.Request) { var evt struct { Payload map[string]string `json:"payload"` } json.NewDecoder(r.Body).Decode(&evt) ns := evt.Payload["namespace"] storage.SearchCacheInstance.Invalidate(ns) log.Printf("[cache] 跨 Agent 失效: %s", ns) w.WriteHeader(200) }) }() // ─── 路由注册 ────────────────────────────── // ─── 静态文件服务(Web UI)───────────────────── // webUIRoot 在函数顶部声明 var webUIRoot string // 后续在 catch-all 中处理(见文件末尾) mux.HandleFunc("/health", routes.HandleHealth) // ─── Prometheus /metrics ───────────────────── mux.Handle("/metrics", metrics.Handler()) // 核心 API mux.HandleFunc("/api/v1/commit", func(w http.ResponseWriter, r *http.Request) { api.Commit(w, r) // 跨 Agent 广播 + 自动蒸馏 go func() { var req struct { AgentID string `json:"agent_id"` Namespace string `json:"namespace"` Content string `json:"content"` } if r.Body != nil { body, _ := io.ReadAll(r.Body) json.Unmarshal(body, &req) r.Body = io.NopCloser(bytes.NewReader(body)) } if req.Namespace != "" { // 通知其他 Agent:缓存失效 + 新记忆事件 governance.PushCacheInvalidate(req.Namespace, "commit") governance.PushMemoryCommitted(req.AgentID, req.Namespace, "") } }() }) mux.HandleFunc("/api/v1/recall", api.Recall) mux.HandleFunc("/api/v1/recall/debug", api.RecallDebug) mux.HandleFunc("/api/v1/feedback", api.Feedback) mux.HandleFunc("/api/v1/bootstrap", api.Bootstrap) mux.HandleFunc("/api/v1/stats", api.Stats) mux.HandleFunc("/api/v1/batch-commit", api.BatchCommit) // ─── WebSocket 实时推送 ────────────────────── mux.HandleFunc("/api/v1/ws/", routes.WSHandler) mux.HandleFunc("/api/v1/ws", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"status":"ok","protocol":"websocket","endpoint":"/api/v1/ws/{agent_id}"}`)) }) // 知识图谱 mux.HandleFunc("/api/v1/graph/stats", graphAPI.Stats) mux.HandleFunc("/api/v1/graph/query", graphAPI.Query) mux.HandleFunc("/api/v1/graph/navigate", graphAPI.Navigate) // 新增:pagerank + evidence_count mux.HandleFunc("/api/v1/graph/pagerank", func(w http.ResponseWriter, r *http.Request) { rank := graphStore.PageRank(0.85, 20) data, _ := json.Marshal(map[string]interface{}{ "pagerank": rank, "count": len(rank), }) w.Header().Set("Content-Type", "application/json") w.Write(data) }) mux.HandleFunc("/api/v1/graph/evidence/", func(w http.ResponseWriter, r *http.Request) { entity := r.URL.Path[len("/api/v1/graph/evidence/"):] count := graphStore.EvidenceCount(entity) data, _ := json.Marshal(map[string]interface{}{ "entity": entity, "evidence_count": count, }) w.Header().Set("Content-Type", "application/json") w.Write(data) }) // 图谱可视化导出 mux.HandleFunc("/api/v1/graph/export", func(w http.ResponseWriter, r *http.Request) { ns := r.URL.Query().Get("namespace") limitStr := r.URL.Query().Get("limit") limit := 0 if limitStr != "" { if l, err := strconv.Atoi(limitStr); err == nil && l > 0 { limit = l } } nodes, edges := graphStore.GetGraph(ns, limit) respondJSON(w, 200, map[string]interface{}{ "nodes": nodes, "edges": edges, "count": map[string]int{"nodes": len(nodes), "edges": len(edges)}, }) }) // 图谱导航 + Obsidian 笔记关联 mux.HandleFunc("/api/v1/graph/notes", func(w http.ResponseWriter, r *http.Request) { entity := r.URL.Query().Get("entity") maxHops := 2 if h := r.URL.Query().Get("max_hops"); h != "" { if parsed, err := strconv.Atoi(h); err == nil && parsed > 0 { maxHops = parsed } } maxNotes := 10 if n := r.URL.Query().Get("max_notes"); n != "" { if parsed, err := strconv.Atoi(n); err == nil && parsed > 0 { maxNotes = parsed } } var noteResults []map[string]interface{} var paths []map[string]interface{} if entity != "" { p, err := graphStore.Navigate(normalizeEntity(entity), maxHops, "", nil) if err == nil { paths = p entities := []string{normalizeEntity(entity)} for _, path := range p { if s, ok := path["source"].(string); ok { entities = append(entities, s) } if t, ok := path["target"].(string); ok { entities = append(entities, t) } } if obsidian != nil { noteResults = obsidian.SearchNotesByEntities(entities, maxNotes) } } } respondJSON(w, 200, map[string]interface{}{ "entity": entity, "graph_paths": paths, "notes": noteResults, "paths_count": len(paths), "notes_count": len(noteResults), }) }) // 静态文件服务(知识图谱可视化 HTML) staticDir := os.Getenv("STATIC_DIR") if staticDir == "" { staticDir = "/home/muc/projects/memoryweave/go/static" } fileServer := http.FileServer(http.Dir(staticDir)) mux.Handle("/static/", http.StripPrefix("/static/", fileServer)) // 冲突 mux.HandleFunc("/api/v1/conflicts", func(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { conflictAPI.List(w, r) } else { http.NotFound(w, r) } }) mux.HandleFunc("/api/v1/conflicts/resolve", conflictAPI.Resolve) // 反馈 — G5: 修正记忆时填充 version_history mux.HandleFunc("/api/v1/feedback/useful", func(w http.ResponseWriter, r *http.Request) { feedbackAPI.MarkUseful(w, r) // 质量监控:useful → 质量上升 var req struct { MemoryID string `json:"memory_id"` } if r.Body != nil { body, _ := io.ReadAll(r.Body) json.Unmarshal(body, &req) r.Body = io.NopCloser(bytes.NewReader(body)) if req.MemoryID != "" { selfoptimize.Dash.RecordUseful() // V 值:useful = success selfoptimize.VProp.RecordDecision( "useful_"+req.MemoryID, []string{req.MemoryID}, "user_feedback", "success", "", ) selfoptimize.QualityMonitor.Check(req.MemoryID, 0, 5) // 清除潜在告警 } } }) mux.HandleFunc("/api/v1/feedback/not-useful", func(w http.ResponseWriter, r *http.Request) { feedbackAPI.MarkNotUseful(w, r) // 质量监控:not-useful → 检查是否需要降权 var req struct { MemoryID string `json:"memory_id"` } if r.Body != nil { body, _ := io.ReadAll(r.Body) json.Unmarshal(body, &req) r.Body = io.NopCloser(bytes.NewReader(body)) if req.MemoryID != "" { selfoptimize.Dash.RecordNotUseful() // V 值:not-useful = failure selfoptimize.VProp.RecordDecision( "notuseful_"+req.MemoryID, []string{req.MemoryID}, "user_feedback", "failure", "", ) score := selfoptimize.Dash.QualityScore() if record := selfoptimize.QualityMonitor.Check(req.MemoryID, score, 5); record != nil { _ = record // WebSocket 通知 } } } }) mux.HandleFunc("/api/v1/feedback/deprecate", feedbackAPI.Deprecate) mux.HandleFunc("/api/v1/feedback/correct", func(w http.ResponseWriter, r *http.Request) { feedbackAPI.Correct(w, r) var req struct { MemoryID string `json:"memory_id"` NewContent string `json:"new_content"` Source string `json:"source"` } if r.Body != nil { body, _ := io.ReadAll(r.Body) json.Unmarshal(body, &req) r.Body = io.NopCloser(bytes.NewReader(body)) if req.MemoryID != "" && req.NewContent != "" { routes.FillVersionHistory(req.MemoryID, "", req.NewContent, req.Source) // 跨 Agent 通知 governance.PushMemoryUpdated(req.MemoryID, 0, "corrected") } } }) // 缺口 mux.HandleFunc("/api/v1/gaps", gapAPI.List) mux.HandleFunc("/api/v1/gaps/detect", gapAPI.Detect) mux.HandleFunc("/api/v1/gaps/close/", func(w http.ResponseWriter, r *http.Request) { gapAPI.Close(w, r) topic := r.URL.Path[len("/api/v1/gaps/close/"):] routes.PushGapFilled(topic, 1) governance.PushGapFilled(topic, 1) }) mux.HandleFunc("/api/v1/gaps/repair", routes.GapRepair.FullRepairHandler) // Agent 注册 mux.HandleFunc("/api/v1/agents/register", agentRegistry.Register) mux.HandleFunc("/api/v1/agents", agentRegistry.List) // ─── 多 Agent EventBus 管理 ────────────────── mux.HandleFunc("/api/v1/eventbus/subscribe", func(w http.ResponseWriter, r *http.Request) { var req struct { EventType string `json:"event_type"` Callback string `json:"callback_url"` } json.NewDecoder(r.Body).Decode(&req) governance.GlobalEventBus.Subscribe(req.EventType, req.Callback) respondJSON(w, 200, map[string]string{"status": "subscribed"}) }) mux.HandleFunc("/api/v1/eventbus/unsubscribe", func(w http.ResponseWriter, r *http.Request) { var req struct { EventType string `json:"event_type"` Callback string `json:"callback_url"` } json.NewDecoder(r.Body).Decode(&req) governance.GlobalEventBus.Unsubscribe(req.EventType, req.Callback) respondJSON(w, 200, map[string]string{"status": "unsubscribed"}) }) mux.HandleFunc("/api/v1/eventbus/list", func(w http.ResponseWriter, r *http.Request) { subs := governance.GlobalEventBus.ListSubscribers() respondJSON(w, 200, subs) }) // 跨 Agent 缓存失效接收端点(供其他 Agent 实例 HTTP 回调) mux.HandleFunc("/api/v1/cache/invalidate", func(w http.ResponseWriter, r *http.Request) { var evt struct { Type string `json:"type"` Payload map[string]string `json:"payload"` } json.NewDecoder(r.Body).Decode(&evt) ns := evt.Payload["namespace"] storage.SearchCacheInstance.Invalidate(ns) log.Printf("[cache] 跨 Agent 失效接收: %s", ns) respondJSON(w, 200, map[string]string{"status": "invalidated"}) }) // 管理 // ?task=full 触发完整整合(DBSCAN + 衰减校准 + 质量回溯),默认 cluster_only mux.HandleFunc("/api/v1/admin/consolidate", func(w http.ResponseWriter, r *http.Request) { task := r.URL.Query().Get("task") if task == "" { task = "cluster_only" } // full 模式给 5 分钟;cluster_only 保持 30 秒 timeout := 30 * time.Second if task == "full" { timeout = 5 * time.Minute } ctx, cancel := context.WithTimeout(r.Context(), timeout) defer cancel() done := make(chan *routes.ConsolidationReport, 1) go func() { report, _ := consolPipe.RunWithMode(task) done <- report }() select { case <-ctx.Done(): respondJSON(w, 408, map[string]string{"error": "consolidation timeout", "task": task}) return case report := <-done: if report == nil { respondJSON(w, 500, map[string]string{"error": "consolidation failed", "task": task}) return } respondJSON(w, 200, report) go governance.PushDistillationComplete(report.Merged, report.ConflictsFound) } }) mux.HandleFunc("/api/v1/admin/forget", adminAPI.Forget) mux.HandleFunc("/api/v1/admin/dedup", api.Dedup) mux.HandleFunc("/api/v1/admin/backup", adminAPI.Backup) mux.HandleFunc("/api/v1/admin/restore", adminAPI.Restore) mux.HandleFunc("/api/v1/admin/backups", adminAPI.ListBackups) mux.HandleFunc("/api/v1/admin/audit", adminAPI.Audit) // 遗忘器类型管理 mux.HandleFunc("/api/v1/admin/forgetter/type", func(w http.ResponseWriter, r *http.Request) { var req struct { AgentType string `json:"agent_type"` } if r.Method == "POST" { json.NewDecoder(r.Body).Decode(&req) forgetter.SetAgentType(req.AgentType) } respondJSON(w, 200, map[string]interface{}{ "agent_type": forgetter.AgentType(), "decay_rate": AgentTypeDecayOrDefault(forgetter.AgentType()), }) }) mux.HandleFunc("/api/v1/admin/distill/force", func(w http.ResponseWriter, r *http.Request) { if routes.DistillEngineRef == nil { respondJSON(w, 400, map[string]string{"error": "distill engine not initialized"}) return } var req struct { Content string `json:"content"` Category string `json:"category"` Namespace string `json:"namespace"` AgentID string `json:"agent_id"` } json.NewDecoder(r.Body).Decode(&req) if req.Content == "" { respondJSON(w, 400, map[string]string{"error": "content required"}) return } if req.Namespace == "" { req.Namespace = "hermes-main" } if req.AgentID == "" { req.AgentID = "hermes-a06" } epID := fmt.Sprintf("ep_manual_%d", time.Now().UnixNano()) routes.AutoDistillTrigger(epID, req.Content, req.Category, req.Namespace, req.AgentID) respondJSON(w, 200, map[string]string{"status": "queued", "episode_id": epID}) }) // 蒸馏队列状态端点(G8-G9 配套) mux.HandleFunc("/api/v1/distill/status", func(w http.ResponseWriter, r *http.Request) { if routes.DistillEngineRef == nil { respondJSON(w, 200, map[string]interface{}{"status": "not_initialized", "queue_len": 0}) return } respondJSON(w, 200, routes.DistillEngineRef.GetStatus()) }) mux.HandleFunc("/api/v1/distill/queue", func(w http.ResponseWriter, r *http.Request) { if routes.DistillEngineRef == nil { respondJSON(w, 200, map[string]interface{}{"queue": []interface{}{}, "count": 0}) return } items := routes.DistillEngineRef.QueueItems() respondJSON(w, 200, map[string]interface{}{"queue": items, "count": len(items)}) }) mux.HandleFunc("/api/v1/distill/quota", func(w http.ResponseWriter, r *http.Request) { if routes.DistillEngineRef == nil { respondJSON(w, 200, map[string]interface{}{"remaining": 0, "used": 0, "limit": 0, "status": "not_initialized"}) return } respondJSON(w, 200, routes.DistillEngineRef.GetQuota()) }) mux.HandleFunc("/api/v1/distilled/{id}", func(w http.ResponseWriter, r *http.Request) { if r.Method == "DELETE" { adminAPI.DeleteDistilled(w, r) } else { http.NotFound(w, r) } }) mux.HandleFunc("/api/v1/memory/", func(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { adminAPI.Versions(w, r) } else { http.NotFound(w, r) } }) // L3 mux.HandleFunc("/api/v1/l3/worldmodel", func(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { l3API.GetHandler(w, r) } else if r.Method == "POST" { l3API.UpdateHandler(w, r) } else { http.NotFound(w, r) } }) // 触发器 mux.HandleFunc("/api/v1/triggers", routes.Triggers.List) mux.HandleFunc("/api/v1/triggers/fire", routes.Triggers.Fire) // 触发器 pause/resume — 手动解析路径 mux.HandleFunc("/api/v1/admin/triggers/", func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path // 格式: /api/v1/admin/triggers/{id}/{action} parts := strings.Split(strings.TrimPrefix(path, "/api/v1/admin/triggers/"), "/") if len(parts) != 2 { http.NotFound(w, r) return } id, action := parts[0], parts[1] // 构造一个新请求,让 Pause/Resume 能通过 PathValue 读取 r.SetPathValue("id", id) switch action { case "pause": routes.Triggers.Pause(w, r) case "resume": routes.Triggers.Resume(w, r) default: http.NotFound(w, r) } }) // Skills(G7) mux.HandleFunc("/api/v1/skills", routes.Skills.List) mux.HandleFunc("POST /api/v1/skills", routes.Skills.Register) // 注册新 skill mux.HandleFunc("/api/v1/skills/stats", routes.Skills.Stats) // skill 统计 mux.HandleFunc("/api/v1/skills/bayes", func(w http.ResponseWriter, r *http.Request) { list := routes.BayesianSkills.List() respondJSON(w, 200, list) }) mux.HandleFunc("/api/v1/skills/{name}", skillByNameHandler) // GET/DELETE /api/v1/skills/{name} mux.HandleFunc("/api/v1/skills/{name}/trial", routes.Skills.Trial) mux.HandleFunc("/api/v1/skills/{name}/execute", routes.ExecuteSkill) // G7.3 skill执行 // G7.2: 结晶路由 mux.HandleFunc("/api/v1/crystallize/candidates", routes.GetSkillCandidates) // 获取候选 mux.HandleFunc("/api/v1/crystallize/memory/{id}", routes.CrystallizeSkill) // 对记忆执行结晶 // 评估 + 自调参 mux.HandleFunc("/api/v1/eval/run", evalAPI.Run) mux.HandleFunc("/api/v1/eval/history", evalAPI.History) mux.HandleFunc("/api/v1/eval/generate", evalAPI.Generate) mux.HandleFunc("/api/v1/tuning/status", routes.Tuner.StatusHandler) mux.HandleFunc("/api/v1/tuning/run", routes.Tuner.RunHandler) mux.HandleFunc("/api/v1/tuning/analytics", routes.Tuner.AnalyticsHandler) // 仪表盘 + 验证 + V值 + 缓存 + 流水线 mux.HandleFunc("/api/v1/metrics/self", func(w http.ResponseWriter, r *http.Request) { metrics := selfoptimize.Dash.Metrics() respondJSON(w, 200, metrics) }) mux.HandleFunc("/api/v1/validate/passive", func(w http.ResponseWriter, r *http.Request) { records := selfoptimize.Validator.GetRecords() respondJSON(w, 200, records) }) mux.HandleFunc("/api/v1/vvalue/decisions", func(w http.ResponseWriter, r *http.Request) { decisions := selfoptimize.VProp.ListRecentDecisions(20) respondJSON(w, 200, decisions) }) // G5.3: V 值查询 + 触发反向传播 mux.HandleFunc("/api/v1/vvalue/memory/", func(w http.ResponseWriter, r *http.Request) { memID := strings.TrimPrefix(r.URL.Path, "/api/v1/vvalue/memory/") if memID == "" { respondJSON(w, 400, map[string]string{"error": "memory_id required"}) return } v := selfoptimize.VProp.GetMemVValue(memID) respondJSON(w, 200, map[string]interface{}{"memory_id": memID, "v_value": v}) }) mux.HandleFunc("/api/v1/trace/vprop", func(w http.ResponseWriter, r *http.Request) { var req struct { MemoryIDs []string `json:"memory_ids"` Action string `json:"action"` Outcome string `json:"outcome"` ParentID string `json:"parent_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err == nil && len(req.MemoryIDs) > 0 { id := fmt.Sprintf("trace_%d", time.Now().UnixNano()) selfoptimize.VProp.RecordDecision(id, req.MemoryIDs, req.Action, req.Outcome, req.ParentID) // 更新相关记忆的 quality_score for _, mid := range req.MemoryIDs { v := selfoptimize.VProp.GetMemVValue(mid) lamb := 0.1 if ldb, ok := ldb.(interface{ UpdateQualityScore(id string, delta float64) error }); ok { _ = ldb.UpdateQualityScore(mid, lamb*v) } } respondJSON(w, 200, map[string]string{"status": "propagated"}) return } respondJSON(w, 400, map[string]string{"error": "memory_ids and outcome required"}) }) mux.HandleFunc("/api/v1/admin/cache", func(w http.ResponseWriter, r *http.Request) { stats := storage.SearchCacheInstance.Stats() respondJSON(w, 200, stats) }) mux.HandleFunc("/api/v1/admin/pipeline", func(w http.ResponseWriter, r *http.Request) { stats := selfoptimize.Flow.Stats() respondJSON(w, 200, stats) }) // G6: ephemeral namespace 管理 mux.HandleFunc("/api/v1/admin/ephemeral/clean", func(w http.ResponseWriter, r *http.Request) { cleaned := []string{} respondJSON(w, 200, map[string]interface{}{ "status": "cleaned", "cleaned_namespaces": cleaned, }) }) // Obsidian mux.HandleFunc("/api/v1/obsidian/push", obsidian.PushHandler) mux.HandleFunc("/api/v1/obsidian/pull", obsidian.PullHandler) mux.HandleFunc("/api/v1/obsidian/status", obsidian.StatusHandler) // 内部分支:跨 Agent 缓存失效回调 mux.HandleFunc("/_internal/cache/invalidate", func(w http.ResponseWriter, r *http.Request) { var evt struct { Payload map[string]string `json:"payload"` } json.NewDecoder(r.Body).Decode(&evt) ns := evt.Payload["namespace"] storage.SearchCacheInstance.Invalidate(ns) log.Printf("[cache] 内部跨 Agent 失效: %s", ns) w.WriteHeader(200) }) // ─── Redis 组件 ───────────────────────────── heartbeat := storage.NewHeartbeat("zhiyid-primary") _ = heartbeat // 后台 goroutine 自动心跳 // ─── 后台引擎启动 ────────────────────────── selfoptimize.RegisterCommitFlow(selfoptimize.Flow) selfoptimize.RegisterRecallFlow(selfoptimize.Flow) selfoptimize.RegisterGapFlow(selfoptimize.Flow) selfoptimize.RegisterCorrectFlow(selfoptimize.Flow) selfoptimize.RegisterConsolidateFlow(selfoptimize.Flow) // 注册真正的 consolidate 处理器:调用 ConsolidationPipeline selfoptimize.Flow.Register("consolidate", func(task *selfoptimize.PipelineTask) error { report, err := consolPipe.RunWithMode("full") if err == nil && report != nil { // 注入 consolidate 报告 → Dashboard if report.Merged > 0 { // 合并启发式: 按合并数比例缩放, 但限定单次贡献 ≤ 0.05, 防止污染 avg_distill_loss loss := float64(report.Merged) * 0.002 if loss > 0.05 { loss = 0.05 } selfoptimize.Dash.RecordDistillLoss(loss) } if report.ConflictsFound > 0 { selfoptimize.Dash.RecordConflictResolved(true) } for _, p := range report.Patterns { if len(p) > 13 && p[:13] == "quality_score" { // 解析 quality_score=0.50 → 注入 avg_distill_loss var qs float64 if _, e := fmt.Sscanf(p, "quality_score=%f", &qs); e == nil { selfoptimize.Dash.RecordDistillLoss(1.0 - qs) } } } } return err }) // 注册 conflict_muchen 处理器:自动裁决低信任差异冲突 selfoptimize.Flow.Register("conflict_muchen", func(task *selfoptimize.PipelineTask) error { conflicts := conflictAPI.Detector.ListActive() for _, c := range conflicts { if c.Status != "pending" { continue } // 按策略自动裁决:latest_wins / primary_wins / dismiss resolution := conflictAPI.Detector.AutoResolve(c) if resolution == "pending" { // 低信任差异(两记忆 trust 差 < 0.2)→ dismiss resolution = "dismiss" } if err := conflictAPI.Detector.Resolve(c.ID, resolution, ""); err == nil { selfoptimize.Dash.RecordConflictResolved(true) } } return nil }) go selfoptimize.Flow.Start() selfoptimize.Executor.Start(selfoptimize.Flow) // G6: ephemeral 定时清理(每 5 分钟扫描一次,清理断开超过 5 分钟的 agent 的 ephemeral 记忆) go func() { for { time.Sleep(5 * time.Minute) now := time.Now().UnixMilli() agents := agentRegistry.ListAgents() for _, agent := range agents { if now-agent.LastSeen > 5*60*1000 { ns := agent.AgentID + "-ephemeral" zeroVec := make([]float32, 1024) memories, _ := ldb.Search("memories", zeroVec, 1000, ns) for _, mem := range memories { ldb.SoftDelete(mem.ID, "ephemeral_expired") } if len(memories) > 0 { log.Printf("[ephemeral] cleaned %d memories from %s (inactive for %ds)", len(memories), ns, (now-agent.LastSeen)/1000) } } } } }() // 质量下降监控:每 30 分钟扫描所有低质量记忆 go func() { for { time.Sleep(30 * time.Minute) zeroVec := make([]float32, 1024) memories, err := ldb.Search("memories", zeroVec, 2000, "") if err != nil || len(memories) == 0 { continue } alerts := 0 for _, m := range memories { if m.QualityScore > 0 && m.QualityScore < 0.3 { if record := selfoptimize.QualityMonitor.Check(m.ID, m.QualityScore, 2); record != nil { alerts++ if record.Status == "deprecating" { log.Printf("[quality] memory %s quality=%.2f deprecating", m.ID[:20], m.QualityScore) } } } } if alerts > 0 { log.Printf("[quality] scan: %d low-quality memories flagged", alerts) } } }() // 图持久化 — 确保启动后写入 if saver, ok := graphStore.(interface{ Save() error }); ok { saver.Save() } // ─── 触发器自动执行循环(每 30 秒检查一次)──────── go func() { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for range ticker.C { for _, t := range []struct{ id, action string }{ {"t_distill", "distill"}, {"t_merge", "merge"}, {"t_prune", "prune"}, {"t_decay", "decay"}, {"t_backtrack", "backtrack"}, {"t_gap", "gap_scan"}, {"t_consolidation", "consolidation"}, } { if !routes.Triggers.CanFire(t.id) { continue } routes.Triggers.RecordFire(t.id) go func(triggerID, action string) { var err error switch action { case "distill": // distill 保持 cluster_only(高频触发,只做快速聚类) _, err = consolPipe.Run() case "consolidation", "backtrack": // full 模式:含 prune + decay + 质量回溯(Step 4) // 48h cooldown 保护,不会真正每分钟跑 full(cooldown 内 CanFire 仍返回 false) _, err = consolPipe.RunWithMode("full") case "merge": _, err = consolPipe.RunMerge() case "prune": graphStore.Prune(0.15) case "decay": // 扫描所有记忆,按衰减率计算 freshness memories, e := ldb.GetCandidatesForForgetting() if e == nil { for _, m := range memories { // 解析 last_recalled_at lastAccessed := time.Now().Add(-30 * 24 * time.Hour) // 默认30天前 if t, ok := m["last_recalled_at"].(string); ok && t != "" { if parsed, err := time.Parse(time.RFC3339, t); err == nil { lastAccessed = parsed } } recallCount := 0 if rc, ok := m["recall_count"].(int); ok { recallCount = rc } tier := "normal" if ts, ok := m["tier"].(string); ok { tier = ts } // E4.3: 获取实体图谱度(namespace 作为实体) entity := "" if ns, ok := m["namespace"].(string); ok { entity = ns } degree := 0 if entity != "" { degree = graphStore.GetEntityDegree(entity) } if forgetter.ShouldForget(lastAccessed, recallCount, tier, degree) { if id, ok := m["id"].(string); ok { forgetter.ScanAndForget(id) // 实际执行软删除(持久化到 LanceDB) _ = ldb.SoftDelete(id, "auto_forget") } } } } case "gap_scan": // 检查已有缺口:过期 7 天的自动关闭 gaps := gapDetector.List() for _, g := range gaps { if !g.Closed && time.Since(g.CreatedAt) > 7*24*time.Hour { gapDetector.Close(g.Topic) } } // 自动修复开放的缺口 for _, g := range gaps { if !g.Closed && routes.GapRepair != nil { if routes.GapRepair.AutoRepair(g) { selfoptimize.Dash.RecordGapClosed() } } } } if err != nil { routes.Triggers.RecordFail(triggerID) } // Skill 结晶:每次蒸馏成功后记录 if action == "distill" || action == "consolidation" { routes.Skills.RecordTrial("auto_distill", true) } routes.WSBus.Broadcast("trigger.fired", map[string]string{ "trigger_id": triggerID, "action": action, }) }(t.id, t.action) } } }() // 自优化指标定时采集(每 30 分钟) go func() { for { time.Sleep(30 * time.Minute) m := selfoptimize.Dash.Metrics() date := time.Now().Format("2006-01-02") for k, v := range m { storage.GlobalMetricsStore.Set(date, k, v) } } }() // 首次指标采集 m := selfoptimize.Dash.Metrics() date := time.Now().Format("2006-01-02") for k, v := range m { storage.GlobalMetricsStore.Set(date, k, v) } // Prometheus 指标定时同步(每 15s 采集一次系统指标) go func() { for { time.Sleep(15 * time.Second) // 同步 Dashboard → Prometheus gauges metrics.SyncFromDashboard(selfoptimize.Dash.Metrics()) // 采集 SQLite 数据库文件大小(Linux only) if runtime.GOOS != "windows" { gPath := os.Getenv("GRAPH_PATH") if gPath == "" { gPath = "/var/lib/memoryweave/graph.db" } if fi, err := os.Stat(gPath); err == nil { metrics.SQLiteDBSizeBytes.Set(float64(fi.Size())) } } // 采集进程 RSS 内存 if data, err := os.ReadFile("/proc/self/status"); err == nil { for _, line := range strings.Split(string(data), "\n") { if strings.HasPrefix(line, "VmRSS:") { // VmRSS: 12345 kB f := strings.Fields(line) if len(f) >= 2 { if kb, err := strconv.ParseFloat(f[1], 64); err == nil { metrics.MemoryRSSBytes.Set(kb * 1024) } } break } } } } }() log.Println("[zhiyid] 多 Agent 架构 — Redis + FileGraph + EventBus + vLLM — 已启动") // Web UI 静态文件根目录 webUIRoot = os.Getenv("ZHIYI_WEB_UI_ROOT") if webUIRoot == "" { webUIRoot = "../web-ui" } // Catch-all: 优先检查静态文件,再 JSON mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { p := r.URL.Path // 静态文件服务(安全检查,不允许 .. 遍历) if p != "/" && !strings.Contains(p, "..") { filePath := webUIRoot + p if _, err := os.Stat(filePath); err == nil { http.ServeFile(w, r, filePath) return } } // 根路径 → index.html if p == "/" || p == "" { if _, err := os.Stat(webUIRoot + "/index.html"); err == nil { http.ServeFile(w, r, webUIRoot+"/index.html") return } respondJSON(w, 200, map[string]interface{}{ "service": "zhiyid", "status": "ok", "version": "0.1.0", }) return } respondJSON(w, 404, map[string]interface{}{ "error": "endpoint not found: " + p, }) }) return middleware.CORS()(middleware.Auth(mux)) } // ─── 辅助 ────────────────────────────────────────── func respondJSON(w http.ResponseWriter, code int, data interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) json.NewEncoder(w).Encode(data) } func AgentTypeDecayOrDefault(agentType string) float64 { if rate, ok := governance.AgentTypeDecay[agentType]; ok { return rate } return 0.015 } // runStartupChecks 启动时数据目录一致性检查 // 检测废弃路径(如 /home/muc/data)并警告,防止数据源混乱 func runStartupChecks(backend string) { canonicalDataDir := "/var/lib/memoryweave" deprecatedPaths := []string{ "/home/muc/data", "/home/muc/.local/share/memoryweave", } for _, dep := range deprecatedPaths { if _, err := os.Stat(dep); err == nil { log.Printf("[WARN] 检测到废弃数据目录 %s,仍有数据残留(当前 canonical: %s)", dep, canonicalDataDir) } } // LanceDB 后端:检查 Rust sidecar socket 是否可达 if backend == "lancedb" { sockPath := os.Getenv("LANCEDB_SOCKET") if sockPath == "" { sockPath = "/tmp/zhiyi-ipc.sock" } if _, err := os.Stat(sockPath); os.IsNotExist(err) { log.Printf("[WARN] LanceDB socket 不存在 (%s),Rust sidecar 可能未运行", sockPath) } else { log.Printf("[startup] LanceDB socket 就绪: %s", sockPath) } } // canonical 目录存在性检查 if _, err := os.Stat(canonicalDataDir); os.IsNotExist(err) { log.Printf("[WARN] canonical 数据目录不存在: %s(首次部署?)", canonicalDataDir) } else { log.Printf("[startup] 数据目录正常: %s", canonicalDataDir) } }