diff --git a/agent/agent_init.py b/agent/agent_init.py index 90e363c7f2b..2d443241367 100644 --- a/agent/agent_init.py +++ b/agent/agent_init.py @@ -537,12 +537,8 @@ def init_agent( agent._skip_mcp_refresh = False # Registry generation the current tool snapshot was derived from. Lets a # late/concurrent refresh reject a stale (older-generation) rebuild instead - # of clobbering a newer one. See tools.mcp_tool.refresh_agent_mcp_tools. - try: - from tools.registry import registry as _registry - agent._tool_snapshot_generation = _registry._generation - except Exception: - agent._tool_snapshot_generation = 0 + # of clobbering a newer one. Set adjacent to the tool snapshot below. + agent._tool_snapshot_generation = 0 # Rate limit tracking — updated from x-ratelimit-* response headers # after each API call. Accessed by /usage slash command. agent._rate_limit_state: Optional["RateLimitState"] = None @@ -964,7 +960,14 @@ def init_agent( print(f"🔄 Fallback chain ({len(agent._fallback_chain)} providers): " + " → ".join(f"{f['model']} ({f['provider']})" for f in agent._fallback_chain)) - # Get available tools with filtering + # Get available tools with filtering. Capture the registry generation this + # snapshot is derived from FIRST, so a later concurrent refresh can tell + # whether it holds a newer or staler view (see refresh_agent_mcp_tools). + try: + from tools.registry import registry as _snapshot_registry + agent._tool_snapshot_generation = _snapshot_registry._generation + except Exception: + agent._tool_snapshot_generation = 0 agent.tools = _ra().get_tool_definitions( enabled_toolsets=enabled_toolsets, disabled_toolsets=disabled_toolsets, diff --git a/gateway/run.py b/gateway/run.py index 4a65501b6d2..2672ab43e95 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -11668,6 +11668,15 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew continue if _agent is None: continue + # Preserve each cached agent's build-time toolset + # selection EXACTLY: a gateway session built with a + # restricted enabled_toolsets (e.g. ["safe"]) must + # NOT silently gain tools after a reload. This is the + # opposite of the interactive CLI/TUI /reload-mcp, + # which is a single user re-applying their own config + # edit; gateway agents are per-session and may be + # deliberately locked down. (Contract is asserted by + # test_reload_mcp_preserves_per_agent_toolset_overrides.) refresh_agent_mcp_tools(_agent, quiet_mode=True) except Exception as _exc: logger.debug(