diff --git a/agent/evolution/executor.py b/agent/evolution/executor.py index 63380343..5c418f3c 100644 --- a/agent/evolution/executor.py +++ b/agent/evolution/executor.py @@ -424,6 +424,11 @@ def run_evolution_for_session( enable_skills=True, runtime_info=getattr(agent, "runtime_info", None), ) + # Mark this as a restricted review agent so runtime MCP reconciliation + # (ToolManager.sync_mcp_into_agent) will NOT silently re-inject MCP tools + # that _select_tools()/_guard_tools() intentionally withheld. Without this + # flag the review boundary would be re-opened on the first LLM turn. + review_agent._evolution_restricted = True # Reuse the live model so it follows the user's configured model. review_agent.model = agent.model # Inject the evolution task brief AFTER the full system prompt: the agent diff --git a/agent/tools/tool_manager.py b/agent/tools/tool_manager.py index 73b76943..823e8b8e 100644 --- a/agent/tools/tool_manager.py +++ b/agent/tools/tool_manager.py @@ -523,6 +523,16 @@ class ToolManager: if agent is None or not hasattr(agent, "tools"): return ([], []) + # Never re-inject MCP tools into a restricted Self-Evolution review agent. + # The review agent is created with a deliberately reduced, workspace-guarded + # toolset; silently re-adding configured MCP tools here would bypass that + # policy boundary (see agent/evolution/executor.py). The flag may live on + # the agent itself (Agent) or on the wrapping stream executor's .agent. + if getattr(agent, "_evolution_restricted", False) or getattr( + getattr(agent, "agent", None), "_evolution_restricted", False + ): + return ([], []) + from agent.tools.mcp.mcp_tool import McpTool current = self._mcp_tool_instances registry_names = set(current.keys())