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
+5 -3
View File
@@ -43,7 +43,8 @@ type AgentLoop struct {
contextBuilder *ContextBuilder
tools *tools.ToolRegistry
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
subagentManager *tools.SubagentManager // Subagent manager for MCP tool registration
running atomic.Bool
@@ -160,7 +161,8 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
contextBuilder: contextBuilder,
tools: toolsRegistry,
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,
summarizing: sync.Map{},
}
@@ -172,7 +174,7 @@ func (al *AgentLoop) Run(ctx context.Context) error {
// Initialize MCP servers using the agent's lifecycle context
// This ensures MCP connections are cancelled when the agent stops
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",
map[string]interface{}{
"error": err.Error(),
+11 -8
View File
@@ -114,29 +114,32 @@ func NewManager() *Manager {
// LoadFromConfig loads MCP servers from configuration
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)
return nil
}
if len(cfg.Tools.MCP.Servers) == 0 {
if len(mcpCfg.Servers) == 0 {
logger.InfoCF("mcp", "No MCP servers configured", nil)
return nil
}
logger.InfoCF("mcp", "Initializing MCP servers",
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
errs := make(chan error, len(cfg.Tools.MCP.Servers))
errs := make(chan error, len(mcpCfg.Servers))
enabledCount := 0
for name, serverCfg := range cfg.Tools.MCP.Servers {
for name, serverCfg := range mcpCfg.Servers {
if !serverCfg.Enabled {
logger.DebugCF("mcp", "Skipping disabled server",
map[string]interface{}{