mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-24 16:54:43 +00:00
fix(gateway): key Slack capability gate into the prompt pin; defer positive note to tool schemas
Follow-up to the #68627 cherry-pick (cluster C15 — Slack platform capability-note accuracy; earliest report/fix: #6545 by @daikeren): 1. Session/prompt stability: the pinned session-context render (_pinned_session_context_prompt) is keyed by _ephemeral_change_key, whose contract requires every rendered input to appear in the key. The new _slack_tools_loaded() gate reads config + the live MCP registration map, so its state is now hashed into the key exactly like the existing Discord gate — a gate flip re-renders ONCE (a legitimate bust); within a session the note stays byte-stable for the life of the conversation (A/B: the new parity test fails with this key change reverted, passes with it). 2. Derived, non-overpromising positive note: rather than hardcoding a capability list that can drift stale again (the original bug class), the tools-present note tells the agent to consult the actual loaded Slack tool schemas for supported operations — the schemas ARE the source of truth, so the note cannot overclaim ops a given Slack toolset/MCP server doesn't expose (e.g. a read-only history server). 3. Tests: parity test proving a gate flip changes both render and key; byte-stability test proving three consecutive turns in one Slack session return the identical pinned object (sha256-equal); autouse fixture pins the new gate so key<->render parity is env-independent.
This commit is contained in:
parent
96f21e8a54
commit
e027f43f70
3 changed files with 58 additions and 4 deletions
|
|
@ -18641,6 +18641,17 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
"1" if src.message_id else "0",
|
||||
)
|
||||
|
||||
# Slack renders a capability-aware platform note gated on
|
||||
# _slack_tools_loaded() — the gate state must appear in the key
|
||||
# (same parity contract as the Discord gate above) so a config /
|
||||
# MCP-registration flip re-renders once instead of serving a
|
||||
# stale pinned note for the rest of the session.
|
||||
slack_tools = ""
|
||||
if src.platform == Platform.SLACK:
|
||||
from gateway.session import _slack_tools_loaded
|
||||
|
||||
slack_tools = "1" if _slack_tools_loaded() else "0"
|
||||
|
||||
try:
|
||||
from hermes_constants import display_hermes_home
|
||||
|
||||
|
|
@ -18661,6 +18672,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
bool(context.shared_multi_user_session),
|
||||
discord_ids,
|
||||
discord_tools,
|
||||
slack_tools,
|
||||
tuple(p.value for p in context.connected_platforms),
|
||||
tuple(
|
||||
(
|
||||
|
|
|
|||
|
|
@ -572,9 +572,11 @@ def build_session_context_prompt(
|
|||
lines.append("")
|
||||
lines.append(
|
||||
"**Platform notes:** You are running inside Slack and have access "
|
||||
"to Slack-specific tools. You CAN search channel history, post "
|
||||
"messages, react to messages, and perform other Slack operations "
|
||||
"via the available Slack toolset."
|
||||
"to Slack-specific tools this session. Consult the available Slack "
|
||||
"tool schemas for the exact operations supported (e.g. channel "
|
||||
"history and thread lookups, posting, reactions) — use those tools "
|
||||
"for Slack-specific requests, and do not promise Slack actions "
|
||||
"beyond what the loaded tools actually expose."
|
||||
)
|
||||
else:
|
||||
lines.append("")
|
||||
|
|
|
|||
|
|
@ -99,9 +99,10 @@ def _make_context(
|
|||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _stable_discord_tools(monkeypatch):
|
||||
"""Pin the config/env-dependent renderer gate so key<->render parity is
|
||||
"""Pin the config/env-dependent renderer gates so key<->render parity is
|
||||
evaluated on the same footing in every environment."""
|
||||
monkeypatch.setattr("gateway.session._discord_tools_loaded", lambda: True)
|
||||
monkeypatch.setattr("gateway.session._slack_tools_loaded", lambda: False)
|
||||
|
||||
|
||||
def _key(runner, context, redact_pii=False):
|
||||
|
|
@ -192,6 +193,45 @@ class TestEphemeralChangeKeyParity:
|
|||
assert _render(ctx) != render_on
|
||||
assert _key(runner, ctx) != key_on
|
||||
|
||||
def test_slack_tools_gate_flip_changes_key(self, monkeypatch):
|
||||
"""The Slack capability note is gated on _slack_tools_loaded(); the
|
||||
gate state must be part of the change key (same parity contract as
|
||||
the Discord gate) or a config/MCP flip would serve a stale pinned
|
||||
note forever."""
|
||||
runner = _make_runner()
|
||||
ctx = _make_context(
|
||||
platform=Platform.SLACK,
|
||||
chat_id="C123",
|
||||
thread_id=None,
|
||||
parent_chat_id=None,
|
||||
guild_id=None,
|
||||
)
|
||||
render_off, key_off = _render(ctx), _key(runner, ctx)
|
||||
monkeypatch.setattr("gateway.session._slack_tools_loaded", lambda: True)
|
||||
assert _render(ctx) != render_off
|
||||
assert _key(runner, ctx) != key_off
|
||||
|
||||
def test_slack_note_byte_stable_across_turns_in_one_session(self):
|
||||
"""Within one session (gate state constant), the Slack platform note
|
||||
must be byte-stable turn over turn — the pin returns the identical
|
||||
object, so the composed system prompt cannot drift mid-conversation."""
|
||||
runner = _make_runner()
|
||||
|
||||
def _slack_ctx():
|
||||
return _make_context(
|
||||
platform=Platform.SLACK,
|
||||
chat_id="C123",
|
||||
thread_id=None,
|
||||
parent_chat_id=None,
|
||||
guild_id=None,
|
||||
)
|
||||
|
||||
t1 = runner._pinned_session_context_prompt(_slack_ctx(), False, "sk-slack") # noqa: SLF001
|
||||
t2 = runner._pinned_session_context_prompt(_slack_ctx(), False, "sk-slack") # noqa: SLF001
|
||||
t3 = runner._pinned_session_context_prompt(_slack_ctx(), False, "sk-slack") # noqa: SLF001
|
||||
assert t2 is t1 and t3 is t1
|
||||
assert hashlib.sha256(t1.encode()).hexdigest() == hashlib.sha256(t3.encode()).hexdigest()
|
||||
|
||||
def test_message_id_value_change_is_not_a_bust(self):
|
||||
"""Only message-id PRESENCE renders (the id itself rides the user
|
||||
message) — a new id every turn must not re-render."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue