perf(agent): reduce memory footprint by storing minimal MCP dependencies

Replace full *config.Config reference with config.MCPConfig value type
in AgentLoop to allow garbage collection of unused configuration data.

Changes:
- AgentLoop now stores only MCPConfig and workspacePath (minimal deps)
- Add mcp.Manager.LoadFromMCPConfig() for minimal dependency version
- Keep LoadFromConfig() for backward compatibility
- Full Config object can be GC'd after NewAgentLoop() returns

This optimization reduces memory usage by not holding references to
unused channel, provider, gateway, and device configurations.
This commit is contained in:
yuchou87 2026-02-17 10:39:39 +08:00
parent 0f6fadb445
commit 6892d006d6
2 changed files with 16 additions and 11 deletions

View file

@ -43,7 +43,8 @@ type AgentLoop struct {
contextBuilder *ContextBuilder contextBuilder *ContextBuilder
tools *tools.ToolRegistry tools *tools.ToolRegistry
mcpManager *mcp.Manager // MCP server manager for resource cleanup mcpManager *mcp.Manager // MCP server manager for resource cleanup
mcpConfig *config.Config // Config for lazy MCP initialization mcpConfig config.MCPConfig // MCP config for lazy initialization (minimal dependency)
workspacePath string // Workspace path for resolving relative envFile paths
mcpInitOnce sync.Once // Ensures MCP is initialized only once mcpInitOnce sync.Once // Ensures MCP is initialized only once
subagentManager *tools.SubagentManager // Subagent manager for MCP tool registration subagentManager *tools.SubagentManager // Subagent manager for MCP tool registration
running atomic.Bool running atomic.Bool
@ -160,7 +161,8 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
contextBuilder: contextBuilder, contextBuilder: contextBuilder,
tools: toolsRegistry, tools: toolsRegistry,
mcpManager: mcpManager, mcpManager: mcpManager,
mcpConfig: cfg, // Store config for lazy initialization in Run() mcpConfig: cfg.Tools.MCP, // Store only MCP config (minimal dependency)
workspacePath: workspace, // Store workspace path for envFile resolution
subagentManager: subagentManager, subagentManager: subagentManager,
summarizing: sync.Map{}, summarizing: sync.Map{},
} }
@ -172,7 +174,7 @@ func (al *AgentLoop) Run(ctx context.Context) error {
// Initialize MCP servers using the agent's lifecycle context // Initialize MCP servers using the agent's lifecycle context
// This ensures MCP connections are cancelled when the agent stops // This ensures MCP connections are cancelled when the agent stops
al.mcpInitOnce.Do(func() { al.mcpInitOnce.Do(func() {
if err := al.mcpManager.LoadFromConfig(ctx, al.mcpConfig); err != nil { if err := al.mcpManager.LoadFromMCPConfig(ctx, al.mcpConfig, al.workspacePath); err != nil {
logger.WarnCF("agent", "Failed to load MCP servers, MCP tools will not be available", logger.WarnCF("agent", "Failed to load MCP servers, MCP tools will not be available",
map[string]interface{}{ map[string]interface{}{
"error": err.Error(), "error": err.Error(),

View file

@ -114,29 +114,32 @@ func NewManager() *Manager {
// LoadFromConfig loads MCP servers from configuration // LoadFromConfig loads MCP servers from configuration
func (m *Manager) LoadFromConfig(ctx context.Context, cfg *config.Config) error { func (m *Manager) LoadFromConfig(ctx context.Context, cfg *config.Config) error {
if !cfg.Tools.MCP.Enabled { return m.LoadFromMCPConfig(ctx, cfg.Tools.MCP, cfg.WorkspacePath())
}
// LoadFromMCPConfig loads MCP servers from MCP configuration and workspace path.
// This is the minimal dependency version that doesn't require the full Config object.
func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig, workspacePath string) error {
if !mcpCfg.Enabled {
logger.InfoCF("mcp", "MCP integration is disabled", nil) logger.InfoCF("mcp", "MCP integration is disabled", nil)
return nil return nil
} }
if len(cfg.Tools.MCP.Servers) == 0 { if len(mcpCfg.Servers) == 0 {
logger.InfoCF("mcp", "No MCP servers configured", nil) logger.InfoCF("mcp", "No MCP servers configured", nil)
return nil return nil
} }
logger.InfoCF("mcp", "Initializing MCP servers", logger.InfoCF("mcp", "Initializing MCP servers",
map[string]interface{}{ map[string]interface{}{
"count": len(cfg.Tools.MCP.Servers), "count": len(mcpCfg.Servers),
}) })
// Get workspace path for resolving relative envFile paths
workspacePath := cfg.WorkspacePath()
var wg sync.WaitGroup var wg sync.WaitGroup
errs := make(chan error, len(cfg.Tools.MCP.Servers)) errs := make(chan error, len(mcpCfg.Servers))
enabledCount := 0 enabledCount := 0
for name, serverCfg := range cfg.Tools.MCP.Servers { for name, serverCfg := range mcpCfg.Servers {
if !serverCfg.Enabled { if !serverCfg.Enabled {
logger.DebugCF("mcp", "Skipping disabled server", logger.DebugCF("mcp", "Skipping disabled server",
map[string]interface{}{ map[string]interface{}{