From e78b35869c9245159e33492cebf026689bf39a73 Mon Sep 17 00:00:00 2001 From: Autumn-27 <53844290+Autumn-27@users.noreply.github.com> Date: Thu, 17 Sep 2026 09:07:40 -0400 Subject: [PATCH] =?UTF-8?q?chore(server):=20=E5=88=A0=E9=99=A4=E5=B7=B2?= =?UTF-8?q?=E6=AD=BB=E7=9A=84=E5=85=A8=E5=B1=80=20mainAgent=20=E4=B8=8E=20?= =?UTF-8?q?agentsForProfile=20=E7=AD=89=E5=86=97=E4=BD=99=E6=9E=84?= =?UTF-8?q?=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit per-task 路由(agentsForTask)落地后,这些全局副本已无人使用: - 全局 mainAgent(字段 + applyLLM 里的构造 + mainAgentRef getter):真正在跑的是 agentsForTask(t).main,全局那份从不被读取,mainAgentRef 无调用者。 - agentsForProfile:旧的任务 pin 路径,已被 agentsForTask 取代,无调用者。 - 随之失活的 profBundle 类型与 profAgents 缓存(仅初始化/清空、再无存取)一并移除。 均为编译器可确认的死代码,不改变任何执行路径;chat 的全局兜底(chatAgent/ chatAgentForProfile)、provider 缓存(provByProfile)等仍在用,保留不动。 全局 planner/worker 空壳与 Ready()/Mode() 改造留待第二步。 --- server/server.go | 84 +++++++++--------------------------------------- 1 file changed, 15 insertions(+), 69 deletions(-) diff --git a/server/server.go b/server/server.go index 92c0ed6..a9e9ff2 100644 --- a/server/server.go +++ b/server/server.go @@ -51,7 +51,6 @@ type Server struct { concMu sync.Mutex cfgMu sync.Mutex - mainAgent *agent.MainAgent // nil when no LLM provider is configured chatAgent *agent.ChatAgent // conversational runner for the chat page; nil w/o LLM // llmProv is the fully-decorated global provider (recorder + failover chain) // installed by applyLLM. Task routers use it after an explicit chain is cleared. @@ -87,21 +86,17 @@ type Server struct { triggerActive map[string]int triggerCfg map[string]triggerBehavior - // profAgents caches the dedicated planner/worker built for a specific (non-active) - // LLM profile, keyed by profile id, so tasks pinned to that profile share one - // provider (and its rate limiter). Built lazily on first use; invalidated when any - // profile is saved/activated/deleted so edits take effect. The active-profile path - // stays on the global engine planner/worker (applyLLM). + // profChatAgents caches a per-profile ChatAgent (chat page), keyed by profile id. + // Built lazily on first use; invalidated when any profile is saved/activated/deleted + // so edits take effect. profMu sync.Mutex - profAgents map[int64]*profBundle profChatAgents map[int64]*agent.ChatAgent // per-profile ChatAgent cache (chat page) // provByProfile caches ONE provider per LLM profile id so every agent bound or // pinned to the same profile shares a single provider instance — hence one rate - // limiter. Separate from profAgents (whole planner/worker pairs). applyLLM also - // resolves the persisted global-active profile through this cache, so global - // fallback and task chains do not accidentally double the configured request rate. - // Cleared alongside profAgents on profile edits, then repopulated by active reapply. + // limiter. applyLLM also resolves the persisted global-active profile through this + // cache, so global fallback and task chains do not accidentally double the + // configured request rate. Cleared on profile edits, then repopulated by active reapply. provCacheMu sync.Mutex provByProfile map[int64]*provEntry provCacheGen uint64 @@ -125,12 +120,6 @@ type Server struct { side *sideQuestionState } -// profBundle is a planner/worker pair built from one LLM profile. -type profBundle struct { - pl *agent.Planner - wk *agent.Worker -} - // provEntry is a cached provider + its config for one LLM profile id. type provEntry struct { prov llm.Provider @@ -158,7 +147,7 @@ func New(ctx context.Context, m *Manager, skillDir string, dataDir string, keyDi s := &Server{m: m, engine: NewEngine(m), ctx: ctx, skillDir: skillDir, jwtKey: key, chatBusy: map[string]bool{}, chatCancel: map[string]context.CancelCauseFunc{}, triggerQ: map[string][]triggeredRun{}, triggerActive: map[string]int{}, triggerCfg: map[string]triggerBehavior{}, - profAgents: map[int64]*profBundle{}, profChatAgents: map[int64]*agent.ChatAgent{}, + profChatAgents: map[int64]*agent.ChatAgent{}, provByProfile: map[int64]*provEntry{}, llmHealth: newLLMHealthRegistry(m.pg), taskAgents: map[string]*taskAgentBundle{}, archiveWake: make(chan struct{}, 1)} s.initSideQuestions() @@ -414,7 +403,7 @@ func (s *Server) webSearchFor(key string) agent.WebSearchOpts { // and worker can run on different models (e.g. a stronger planner, a cheaper worker). // pinID is the task's pinned profile (nil on the global active path). With no agent // binding and no pin, both fall back to gProv/gCfg — identical to the previous single- -// provider behavior. Shared by applyLLM (global) and agentsForProfile (per-task pin). +// provider behavior. Called by applyLLM (global active pair). func (s *Server) buildPlannerWorker(pinID *int64, gProv llm.Provider, gCfg agent.Config) (*agent.Planner, *agent.Worker) { tx := transcript.NewStore(filepath.Join(s.m.dir, "transcripts")) // raw LLM conversation logs // traffic host tools flow through ToolAugment for every agent and are filtered by @@ -499,19 +488,11 @@ func (s *Server) applyLLM(cfg agent.Config) error { tx := transcript.NewStore(filepath.Join(s.m.dir, "transcripts")) win := cfg.CompactionWindow() - // mainagent resolves its own binding (→ global fallback); chat stays the GLOBAL - // fallback since one ChatAgent serves many agent keys — its per-agent binding is - // resolved at Chat time (runConversationSync → chatAgentForProfile). - mProv, mCfg := s.providerForAgent("mainagent", nil, prov, cfg) + // chat stays the GLOBAL fallback since one ChatAgent serves many agent keys — its + // per-agent binding is resolved at Chat time (runConversationSync → + // chatAgentForProfile). The main-agent has no global instance: each task builds its + // own via agentsForTask (task-routed), so nothing is constructed for it here. s.cfgMu.Lock() - s.mainAgent = agent.NewMainAgent(mProv, mCfg.Model, s.m.dir, tx, mCfg.CompactionWindow(), s.agentMaxTurns("mainagent")) - s.mainAgent.SetFindingRecorder(s.evidenceStore()) - s.mainAgent.SetProxy(s.m.ProxyAddr(), s.m.ProxyCACert()) // WebFetch through the recording proxy - s.mainAgent.SetWebSearch(s.webSearchFor("mainagent")) - s.mainAgent.SetSteerWork(s.engine.SteerWork) // steer_work:人对运行中 work 实时纠偏 - s.mainAgent.SetNonStreaming(nonStreamingResolver(mCfg)) - s.mainAgent.SetMaxTokens(maxTokensResolver(mCfg)) - s.mainAgent.SetNoaEnabled(s.m.NoaCompactionEnabled) // 实验功能:noa 上下文压缩(每 run 读) // chat agent serves MANY custom agents by key → it holds the GLOBAL opts // (backend/key) and gates Enabled per-conversation-agent at Chat time. 对话始终用激活配置。 s.chatAgent = agent.NewChatAgent(prov, cfg.Model, s.m.dir, tx, win) // chat page runner @@ -656,34 +637,6 @@ func (s *Server) providerForAgent(agentKey string, pinID *int64, gProv llm.Provi return gProv, gCfg } -// agentsForProfile returns the dedicated planner/worker for a task pinned to a specific -// LLM profile, built + cached on first use (tasks on the same pin share one pair). Each -// agent still honors its own binding first (via buildPlannerWorker), falling back to this -// pinned profile. nil,nil when the profile is invalid → caller uses the global active pair. -func (s *Server) agentsForProfile(id int64) (*agent.Planner, *agent.Worker) { - s.profMu.Lock() - b := s.profAgents[id] - s.profMu.Unlock() - if b != nil { - return b.pl, b.wk - } - prov, cfg, ok := s.providerForProfile(id) - if !ok { - return nil, nil - } - // The pin is the fallback for agents without their own binding — same - // exclusive-by-default rule, so route it through poolForBinding too. - pl, wk := s.buildPlannerWorker(&id, s.poolForBinding(id, prov, cfg), cfg) - s.profMu.Lock() - if ex := s.profAgents[id]; ex != nil { // lost the race → keep the winner - pl, wk = ex.pl, ex.wk - } else { - s.profAgents[id] = &profBundle{pl: pl, wk: wk} - } - s.profMu.Unlock() - return pl, wk -} - // chatAgentForProfile returns a ChatAgent built from a specific LLM profile, cached // per profile id. Returns nil if the profile is missing or has no API key. func (s *Server) chatAgentForProfile(id int64) *agent.ChatAgent { @@ -715,12 +668,11 @@ func (s *Server) chatAgentForProfile(id int64) *agent.ChatAgent { return ca } -// invalidateProfileAgents drops the per-profile agent + provider caches so a profile -// save/activate/delete — or an agent's binding change — rebuilds pinned tasks' planner/ -// worker (and re-resolves each agent's bound model) on their next round. +// invalidateProfileAgents drops the per-profile ChatAgent + provider caches so a profile +// save/activate/delete — or an agent's binding change — rebuilds pinned tasks' agents +// (and re-resolves each agent's bound model) on their next round. func (s *Server) invalidateProfileAgents() { s.profMu.Lock() - s.profAgents = map[int64]*profBundle{} s.profChatAgents = map[int64]*agent.ChatAgent{} s.profMu.Unlock() s.provCacheMu.Lock() @@ -730,12 +682,6 @@ func (s *Server) invalidateProfileAgents() { s.invalidateTaskAgents() } -func (s *Server) mainAgentRef() *agent.MainAgent { - s.cfgMu.Lock() - defer s.cfgMu.Unlock() - return s.mainAgent -} - func (s *Server) chatAgentRef() *agent.ChatAgent { s.cfgMu.Lock() defer s.cfgMu.Unlock()