Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions agent/conversation_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,15 @@ def _restore_or_build_system_prompt(agent, system_message, conversation_history)
)

if stored_prompt and _stored_prompt_matches_runtime(agent, stored_prompt):
# The user set an explicit personality via /personality or the
# caller passed an ephemeral override. Use it even when a stored
# prompt would otherwise match — the user's explicit intent wins
# over prefix-cache reuse. Caching will miss for this turn, but
# that is the expected trade-off for a deliberate personality switch
# (#58774).
if getattr(agent, "ephemeral_system_prompt", None):
agent._cached_system_prompt = agent.ephemeral_system_prompt
return
# Continuing session — reuse the exact system prompt from the
# previous turn so the Anthropic cache prefix matches.
agent._cached_system_prompt = stored_prompt
Expand Down
1 change: 1 addition & 0 deletions scripts/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"huanshan5195@users.noreply.github.com": "huanshan5195", # PR #57601 salvage (custom-provider: emit reasoning_effort at the live CustomProfile path so GLM-5.2/ARK/vLLM/Ollama endpoints receive it; + "max" reasoning level)
"infinitycrew39@gmail.com": "infinitycrew39", # PR #56431 salvage (honor live vLLM context limits on local endpoints)
"jonathan.kovacs999@gmail.com": "CocaKova", # PR #57692 salvage (cron: run jobs under the profile secret scope so get_secret does not fail-close with UnscopedSecretError under profile isolation)
"ishengeqi@163.com": "isheng-eqi",
"hermes.wanderer@yahoo.com": "trismegistus-wanderer", # PR #31856 salvage (gateway: defer idle-TTL agent-cache eviction until the session store says the session actually expired, so the expiry watcher can still fire MemoryProvider.on_session_end with the live transcript; #11205)
"louis@letsfive.io": "Mibayy", # PR #3243 salvage (/compact alias + preview/aggressive flags for /compress)
"louis@letsfive.io": "Mibayy", # PR #3176 salvage (api-server: per-client model routing via model_routes)
Expand Down
47 changes: 47 additions & 0 deletions tests/agent/test_system_prompt_restore.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ def _make_agent(session_db=None, prebuilt_prompt: str = "BUILT_PROMPT"):
agent.platform = "cli"
agent._session_db = session_db
agent._build_system_prompt = MagicMock(return_value=prebuilt_prompt)
# Explicitly None — MagicMock auto-creates attributes on access, so
# getattr(agent, "ephemeral_system_prompt", None) would return a Mock.
agent.ephemeral_system_prompt = None
return agent


Expand Down Expand Up @@ -261,5 +264,49 @@ def test_restored_prompt_is_byte_identical_to_stored(self):
assert agent._cached_system_prompt.encode("utf-8") == stored.encode("utf-8")


class TestEphemeralSystemPromptOverride:
"""When the caller sets an ephemeral system prompt (e.g. /personality),
it must win over the session-DB stored prompt (#58774)."""

def test_ephemeral_overrides_stored_prompt(self):
"""ephemeral_system_prompt takes precedence over a matching stored prompt."""
stored = "Stored prompt from session DB"
db = MagicMock()
db.get_session.return_value = {"system_prompt": stored}
agent = _make_agent(session_db=db)
agent.ephemeral_system_prompt = "Personality: pirate"

_restore_or_build_system_prompt(agent, None, [{"role": "user", "content": "hi"}])

assert agent._cached_system_prompt == "Personality: pirate"
agent._build_system_prompt.assert_not_called()

def test_ephemeral_none_does_not_block_restore(self):
"""ephemeral_system_prompt=None (the default) should still restore from DB."""
stored = "Stored prompt from session DB"
db = MagicMock()
db.get_session.return_value = {"system_prompt": stored}
agent = _make_agent(session_db=db)
agent.ephemeral_system_prompt = None

_restore_or_build_system_prompt(agent, None, [{"role": "user", "content": "hi"}])

assert agent._cached_system_prompt == stored
agent._build_system_prompt.assert_not_called()

def test_ephemeral_empty_string_does_not_block_restore(self):
"""ephemeral_system_prompt='' should still restore from DB (empty is falsy)."""
stored = "Stored prompt from session DB"
db = MagicMock()
db.get_session.return_value = {"system_prompt": stored}
agent = _make_agent(session_db=db)
agent.ephemeral_system_prompt = ""

_restore_or_build_system_prompt(agent, None, [{"role": "user", "content": "hi"}])

assert agent._cached_system_prompt == stored
agent._build_system_prompt.assert_not_called()


if __name__ == "__main__":
pytest.main([__file__, "-v"])
Loading