From d8cd7ac2e7cb0d7617666f3af1abdfa2bf275e22 Mon Sep 17 00:00:00 2001 From: Ben Barclay Date: Thu, 23 Jul 2026 13:57:57 +1000 Subject: [PATCH] fix(relay): declare explicit relevance policy when require_mention is configured false (#69816) Companion to gateway-gateway#160 (connector absent-row default flips to mention-gated / requireAddress=true). relay_relevance_policy() previously returned None whenever the projected policy was all-falsy, on the premise that 'the connector's quiet default already matches'. Once the connector defaults requireAddress to true, that premise inverts for one case: an agent EXPLICITLY configured with require_mention: false would stay silent and get mention-gated anyway, with no way out. The nothing-to-declare check now keys on 'require_mention is unset' rather than 'require_mention is falsy': an explicit false IS a configured knob and is declared to the connector; a genuinely unconfigured platform still declares nothing and inherits the connector's default. No wire/contract change. --- gateway/relay/__init__.py | 19 +++++++---- tests/gateway/relay/test_relay_policy_send.py | 34 ++++++++++++++++++- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/gateway/relay/__init__.py b/gateway/relay/__init__.py index 87326c4e4a01..909b21125caf 100644 --- a/gateway/relay/__init__.py +++ b/gateway/relay/__init__.py @@ -309,7 +309,9 @@ def relay_relevance_policy(platform: Optional[str] = None) -> Optional[dict]: the ``{PLATFORM}_*`` env. ``platform`` defaults to the PRIMARY fronted platform (back-compat). Returns the generic dict, or None when relay isn't configured or the platform exposes no relevance knobs (⇒ the connector's - quiet default already matches, so there's nothing to declare). + default — mention-gated — applies unchallenged; an EXPLICIT + ``require_mention: false`` IS a knob and is declared so the connector + doesn't mention-gate an agent configured to free-respond). """ if platform is None: platform, _bot_id = relay_platform_identity() @@ -351,13 +353,18 @@ def relay_relevance_policy(platform: Optional[str] = None) -> Optional[dict]: allow_bots_env = os.environ.get(f"{platform.upper()}_ALLOW_BOTS", "").lower().strip() allow_other_bots = allow_bots_env in {"mentions", "all"} - require_address = bool(require_mention) if require_mention is not None else False - - # Nothing non-default to declare ⇒ let the connector keep its quiet default - # (matches absence-of-row semantics on the connector side). - if not require_address and not free_response and not allow_other_bots: + # Nothing CONFIGURED to declare ⇒ let the connector keep its default policy + # (mention-gated with agent-thread continuation — matches absence-of-row + # semantics on the connector side). NOTE the condition is "require_mention + # is unset", NOT "require_mention is falsy": the connector's default is now + # requireAddress=true, so an EXPLICIT `require_mention: false` is a + # non-default choice that MUST be declared or the connector would + # mention-gate an agent configured to free-respond. + if require_mention is None and not free_response and not allow_other_bots: return None + require_address = bool(require_mention) if require_mention is not None else False + return { "platform": platform, "requireAddress": require_address, diff --git a/tests/gateway/relay/test_relay_policy_send.py b/tests/gateway/relay/test_relay_policy_send.py index 3996eec19564..3ad5134ce2d1 100644 --- a/tests/gateway/relay/test_relay_policy_send.py +++ b/tests/gateway/relay/test_relay_policy_send.py @@ -87,12 +87,44 @@ def test_projection_falls_back_to_top_level_require_mention(monkeypatch): def test_projection_none_when_all_default(monkeypatch): # No require_mention, no free-response, no allow-bots ⇒ nothing to declare - # (the connector's quiet default already matches). + # (the connector's default — mention-gated — applies). monkeypatch.setenv("GATEWAY_RELAY_PLATFORMS", "discord") monkeypatch.setattr("gateway.run._load_gateway_config", lambda: {"discord": {}}, raising=False) assert relay.relay_relevance_policy() is None +def test_projection_declares_explicit_require_mention_false(monkeypatch): + # An EXPLICIT `require_mention: false` is a configured (non-default) choice + # and MUST be declared: the connector's absent-row default is now + # requireAddress=true, so staying silent would mention-gate an agent the + # operator configured to free-respond. + monkeypatch.setenv("GATEWAY_RELAY_PLATFORMS", "discord") + monkeypatch.setattr( + "gateway.run._load_gateway_config", + lambda: {"discord": {"require_mention": False}}, + raising=False, + ) + pol = relay.relay_relevance_policy() + assert pol == { + "platform": "discord", + "requireAddress": False, + "freeResponseScopes": [], + "allowOtherBots": False, + } + + +def test_projection_declares_explicit_top_level_require_mention_false(monkeypatch): + # Same as above via the bridged top-level key. + monkeypatch.setenv("GATEWAY_RELAY_PLATFORMS", "discord") + monkeypatch.setattr( + "gateway.run._load_gateway_config", + lambda: {"require_mention": False}, + raising=False, + ) + pol = relay.relay_relevance_policy() + assert pol is not None and pol["requireAddress"] is False + + def test_projection_none_when_platform_unresolved(monkeypatch): # Default platform "relay" ⇒ no concrete fronted platform ⇒ nothing to project. monkeypatch.setattr(