fix(personality): stop writing personality into agent.system_prompt
Persist display.personality only; apply rendered text as an in-session overlay across CLI, TUI config.set, and gateway /personality. Co-authored-by: kyssta-exe <kyssta-exe@users.noreply.github.com> Co-authored-by: EMT5320 <1908937833@qq.com>
This commit is contained in:
@@ -4502,10 +4502,13 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
# AGENTS.md/SOUL.md/.cursorrules and persistent memory are not loaded.
|
||||
self.ignore_rules = ignore_rules or os.environ.get("HERMES_IGNORE_RULES") == "1"
|
||||
|
||||
# Ephemeral system prompt: env var takes precedence, then config
|
||||
# Ephemeral system prompt: env var takes precedence, then
|
||||
# display.personality / agent.system_prompt from config.
|
||||
from hermes_cli.config import resolve_ephemeral_system_prompt_from_config
|
||||
|
||||
self.system_prompt = (
|
||||
os.getenv("HERMES_EPHEMERAL_SYSTEM_PROMPT", "")
|
||||
or CLI_CONFIG["agent"].get("system_prompt", "")
|
||||
or resolve_ephemeral_system_prompt_from_config(CLI_CONFIG)
|
||||
)
|
||||
self.personalities = CLI_CONFIG["agent"].get("personalities", {})
|
||||
|
||||
|
||||
+6
-4
@@ -8170,15 +8170,17 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||
@staticmethod
|
||||
def _load_ephemeral_system_prompt() -> str:
|
||||
"""Load ephemeral system prompt from config or env var.
|
||||
|
||||
Checks HERMES_EPHEMERAL_SYSTEM_PROMPT env var first, then falls back to
|
||||
agent.system_prompt in ~/.hermes/config.yaml.
|
||||
|
||||
Checks HERMES_EPHEMERAL_SYSTEM_PROMPT env var first, then
|
||||
``display.personality`` / ``agent.system_prompt`` in config.yaml.
|
||||
"""
|
||||
from hermes_cli.config import resolve_ephemeral_system_prompt_from_config
|
||||
|
||||
prompt = os.getenv("HERMES_EPHEMERAL_SYSTEM_PROMPT", "")
|
||||
if prompt:
|
||||
return prompt
|
||||
cfg = _load_gateway_runtime_config()
|
||||
return str(cfg_get(cfg, "agent", "system_prompt", default="") or "").strip()
|
||||
return resolve_ephemeral_system_prompt_from_config(cfg)
|
||||
|
||||
def _resolve_model_for_channel(
|
||||
self,
|
||||
|
||||
+18
-18
@@ -2519,34 +2519,34 @@ class GatewaySlashCommandsMixin:
|
||||
lines.append(t("gateway.personality.usage"))
|
||||
return "\n".join(lines)
|
||||
|
||||
def _resolve_prompt(value):
|
||||
if isinstance(value, dict):
|
||||
parts = [value.get("system_prompt", "")]
|
||||
if value.get("tone"):
|
||||
parts.append(f'Tone: {value["tone"]}')
|
||||
if value.get("style"):
|
||||
parts.append(f'Style: {value["style"]}')
|
||||
return "\n".join(p for p in parts if p)
|
||||
return str(value)
|
||||
from hermes_cli.config import (
|
||||
_prompt_text,
|
||||
render_personality_prompt,
|
||||
)
|
||||
|
||||
if args in {"none", "default", "neutral"}:
|
||||
# Persist the selection only. Never clear agent.system_prompt —
|
||||
# that field is the user-owned manual overlay.
|
||||
try:
|
||||
if "agent" not in config or not isinstance(config.get("agent"), dict):
|
||||
config["agent"] = {}
|
||||
config["agent"]["system_prompt"] = ""
|
||||
if "display" not in config or not isinstance(config.get("display"), dict):
|
||||
config["display"] = {}
|
||||
config["display"]["personality"] = ""
|
||||
atomic_config_write(config_path, config)
|
||||
except Exception as e:
|
||||
return t("gateway.personality.save_failed", error=str(e))
|
||||
self._ephemeral_system_prompt = ""
|
||||
self._ephemeral_system_prompt = _prompt_text(
|
||||
cfg_get(config, "agent", "system_prompt", default="")
|
||||
)
|
||||
return t("gateway.personality.cleared")
|
||||
elif args in personalities:
|
||||
new_prompt = _resolve_prompt(personalities[args])
|
||||
new_prompt = render_personality_prompt(personalities[args])
|
||||
|
||||
# Write to config.yaml, same pattern as CLI save_config_value.
|
||||
# Persist the personality name only — never write personality text
|
||||
# into agent.system_prompt (user-owned manual overlay).
|
||||
try:
|
||||
if "agent" not in config or not isinstance(config.get("agent"), dict):
|
||||
config["agent"] = {}
|
||||
config["agent"]["system_prompt"] = new_prompt
|
||||
if "display" not in config or not isinstance(config.get("display"), dict):
|
||||
config["display"] = {}
|
||||
config["display"]["personality"] = args
|
||||
atomic_config_write(config_path, config)
|
||||
except Exception as e:
|
||||
return t("gateway.personality.save_failed", error=str(e))
|
||||
|
||||
@@ -1340,21 +1340,34 @@ class CLICommandsMixin:
|
||||
personality_name = parts[1].strip().lower()
|
||||
|
||||
if personality_name in {"none", "default", "neutral"}:
|
||||
self.system_prompt = ""
|
||||
# Persist the selection only. Never clear agent.system_prompt —
|
||||
# that field is the user-owned manual overlay.
|
||||
saved = save_config_value("display.personality", "")
|
||||
try:
|
||||
from hermes_cli.config import cfg_get, read_raw_config, _prompt_text
|
||||
|
||||
self.system_prompt = _prompt_text(
|
||||
cfg_get(read_raw_config(), "agent", "system_prompt", default="")
|
||||
)
|
||||
except Exception:
|
||||
self.system_prompt = ""
|
||||
self.agent = None # Force re-init
|
||||
if save_config_value("agent.system_prompt", ""):
|
||||
if saved:
|
||||
print("(^_^)b Personality cleared (saved to config)")
|
||||
else:
|
||||
print("(^_^) Personality cleared (session only)")
|
||||
print(" No personality overlay — using base agent behavior.")
|
||||
elif personality_name in self.personalities:
|
||||
self.system_prompt = self._resolve_personality_prompt(self.personalities[personality_name])
|
||||
personality_prompt = self._resolve_personality_prompt(
|
||||
self.personalities[personality_name]
|
||||
)
|
||||
self.system_prompt = personality_prompt
|
||||
self.agent = None # Force re-init
|
||||
if save_config_value("agent.system_prompt", self.system_prompt):
|
||||
if save_config_value("display.personality", personality_name):
|
||||
print(f"(^_^)b Personality set to '{personality_name}' (saved to config)")
|
||||
else:
|
||||
print(f"(^_^) Personality set to '{personality_name}' (session only)")
|
||||
print(f" \"{self.system_prompt[:60]}{'...' if len(self.system_prompt) > 60 else ''}\"")
|
||||
print(f" \"{personality_prompt[:60]}{'...' if len(personality_prompt) > 60 else ''}\"")
|
||||
else:
|
||||
print(f"(._.) Unknown personality: {personality_name}")
|
||||
print(f" Available: none, {', '.join(self.personalities.keys())}")
|
||||
|
||||
@@ -6471,8 +6471,9 @@ def _make_agent(
|
||||
pass
|
||||
|
||||
cfg = _load_cfg()
|
||||
agent_cfg = cfg.get("agent") or {}
|
||||
system_prompt = _prompt_text(agent_cfg.get("system_prompt", ""))
|
||||
from hermes_cli.config import resolve_ephemeral_system_prompt_from_config
|
||||
|
||||
system_prompt = resolve_ephemeral_system_prompt_from_config(cfg)
|
||||
startup_skills = _parse_tui_skills_env()
|
||||
if startup_skills:
|
||||
from agent.skill_commands import build_preloaded_skills_prompt
|
||||
@@ -11336,8 +11337,10 @@ def _(rid, params: dict) -> dict:
|
||||
elif key == "personality":
|
||||
sid_key = params.get("session_id", "")
|
||||
pname, new_prompt = _validate_personality(str(value or ""), cfg)
|
||||
# Personality text is an in-session overlay. Keep the
|
||||
# user-owned global system prompt intact so changing a
|
||||
# personality cannot destroy manual configuration.
|
||||
_write_config_key("display.personality", pname)
|
||||
_write_config_key("agent.system_prompt", new_prompt)
|
||||
nv = str(value or "none")
|
||||
history_reset, info = _apply_personality_to_session(
|
||||
sid_key, session, new_prompt, pname
|
||||
|
||||
Reference in New Issue
Block a user