fix(mcp): round-3 polish — generation capture adjacency + gateway contract note

Third review pass (Hermes subagent) declared convergence: no BLOCKING, the
round-2 generation-aware publish / context-engine staging / CLI reload / ACP
routing all verified correct by hand and by test.

- agent_init: capture _tool_snapshot_generation immediately before the tool
  snapshot (was ~425 lines earlier); removes a harmless skew window so the
  recorded generation always matches the snapshot it describes.
- gateway/run.py _execute_mcp_reload: keep preserving each cached agent's
  build-time enabled_toolsets EXACTLY (do NOT merge newly-connected servers like
  CLI/TUI do) and document WHY — gateway sessions can be deliberately locked
  down, and test_reload_mcp_preserves_per_agent_toolset_overrides asserts this.
  A reviewer suggested "parity" here; it would have violated that contract.
This commit is contained in:
alt-glitch 2026-06-19 23:01:01 +05:30 committed by Teknium
parent 88d523220f
commit f3e967aae5
2 changed files with 19 additions and 7 deletions

View file

@ -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,

View file

@ -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(