fix(gateway): bind profile override in _persist_live_session_system_prompt
Fixes #50233 _persist_live_session_system_prompt rebuilds the system prompt after a live model switch (/model), but _start_agent_build's finally block has already reset set_hermes_home_override by then. load_soul_md() and build_skills_system_prompt() call get_hermes_home() which falls back to the root ~/.hermes, loading the wrong SOUL.md identity and skills for the session's profile. Fix: set_hermes_home_override(session["profile_home"]) before calling agent._build_system_prompt() in _persist_live_session_system_prompt, and reset it in a finally block. Also upgrade the failure log from DEBUG to WARNING so silent profile-wrong-prompt issues are visible. The first-prompt lazy-build path is unaffected — _run_prompt_submit already re-sets the override before run_conversation. The /model slash worker path does not, which is the gap this fixes. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -18045,3 +18045,89 @@ def test_prompt_submit_unconfirmed_truncation_refuses_before_target_resolution(
|
||||
assert len(sess["history"]) == 4
|
||||
finally:
|
||||
server._sessions.pop(sid, None)
|
||||
|
||||
|
||||
def test_persist_live_session_system_prompt_uses_profile_home(monkeypatch, tmp_path):
|
||||
"""Issue #50233: _persist_live_session_system_prompt must re-bind
|
||||
HERMES_HOME to the session's profile before rebuilding the system
|
||||
prompt. Without this, a /model switch rebuilds the prompt with the
|
||||
root profile's SOUL.md and skills instead of the session's profile.
|
||||
"""
|
||||
profile_home = tmp_path / "profile-work"
|
||||
profile_home.mkdir()
|
||||
(profile_home / "SOUL.md").write_text(
|
||||
"# Work persona\nYou are a work agent.", encoding="utf-8"
|
||||
)
|
||||
|
||||
built_homes = []
|
||||
|
||||
class FakeAgent:
|
||||
model = "test-model"
|
||||
provider = "test"
|
||||
_cached_system_prompt = None
|
||||
_session_db = None
|
||||
|
||||
def _build_system_prompt(self, system_message=None):
|
||||
from hermes_constants import get_hermes_home
|
||||
home = get_hermes_home()
|
||||
built_homes.append(str(home))
|
||||
soul = (
|
||||
(home / "SOUL.md").read_text(encoding="utf-8")
|
||||
if (home / "SOUL.md").exists()
|
||||
else ""
|
||||
)
|
||||
return f"System prompt from {home}\n{soul}"
|
||||
|
||||
class FakeDB:
|
||||
def update_system_prompt(self, session_id, prompt):
|
||||
pass
|
||||
|
||||
agent = FakeAgent()
|
||||
agent._session_db = FakeDB()
|
||||
session = {
|
||||
"agent": agent,
|
||||
"session_key": "test-key",
|
||||
"profile_home": str(profile_home),
|
||||
}
|
||||
|
||||
server._persist_live_session_system_prompt(session)
|
||||
|
||||
# The system prompt must have been built while the override pointed
|
||||
# to the profile home, not the root ~/.hermes.
|
||||
assert len(built_homes) == 1, f"expected 1 build, got {built_homes}"
|
||||
assert str(profile_home) in built_homes[0], (
|
||||
f"system prompt built with wrong home: {built_homes[0]}"
|
||||
)
|
||||
assert "Work persona" in agent._cached_system_prompt
|
||||
|
||||
# The override must have been reset after the call.
|
||||
from hermes_constants import get_hermes_home_override
|
||||
assert get_hermes_home_override() is None
|
||||
|
||||
|
||||
def test_persist_live_session_system_prompt_no_profile_is_unchanged(monkeypatch):
|
||||
"""Sessions without a profile_home must not set/clear any override —
|
||||
the function should behave identically to before the fix."""
|
||||
class FakeAgent:
|
||||
model = "test"
|
||||
_cached_system_prompt = None
|
||||
_session_db = None
|
||||
|
||||
def _build_system_prompt(self, system_message=None):
|
||||
return "plain prompt"
|
||||
|
||||
class FakeDB:
|
||||
def update_system_prompt(self, session_id, prompt):
|
||||
pass
|
||||
|
||||
agent = FakeAgent()
|
||||
agent._session_db = FakeDB()
|
||||
session = {
|
||||
"agent": agent,
|
||||
"session_key": "test-key",
|
||||
"profile_home": None,
|
||||
}
|
||||
|
||||
# Should not raise, should still build and cache.
|
||||
server._persist_live_session_system_prompt(session)
|
||||
assert agent._cached_system_prompt == "plain prompt"
|
||||
|
||||
+17
-1
@@ -4016,12 +4016,28 @@ def _persist_live_session_system_prompt(session: dict | None) -> None:
|
||||
if db is None or not hasattr(db, "update_system_prompt"):
|
||||
return
|
||||
|
||||
# Re-bind HERMES_HOME to the session's profile so load_soul_md() and
|
||||
# build_skills_system_prompt() resolve to the correct profile. Without
|
||||
# this, _start_agent_build's finally block has already reset the
|
||||
# override and the rebuilt prompt silently uses the root profile's
|
||||
# SOUL.md and skills. See issue #50233.
|
||||
profile_home = session.get("profile_home")
|
||||
home_token = (
|
||||
set_hermes_home_override(profile_home) if profile_home else None
|
||||
)
|
||||
try:
|
||||
prompt = agent._build_system_prompt(None)
|
||||
agent._cached_system_prompt = prompt
|
||||
db.update_system_prompt(getattr(agent, "session_id", None) or session_key, prompt)
|
||||
except Exception:
|
||||
logger.debug("failed to persist live session system prompt", exc_info=True)
|
||||
logger.warning(
|
||||
"failed to persist live session system prompt for session %s",
|
||||
session_key,
|
||||
exc_info=True,
|
||||
)
|
||||
finally:
|
||||
if home_token is not None:
|
||||
reset_hermes_home_override(home_token)
|
||||
|
||||
|
||||
# Stable leading text of the model-switch marker, shared by the builder and the
|
||||
|
||||
Reference in New Issue
Block a user