hermes-agent/hermes_cli/default_soul.py
Teknium 411faf08bd
fix(soul): installers seed the real default persona, upgrade legacy empty templates (#52246)
The desktop bootstrap (and curl/PowerShell/docker installs) seeded
~/.hermes/SOUL.md with a comment-only scaffold that contained no persona
text. That shadowed the runtime default (_ensure_default_soul_md ->
DEFAULT_SOUL_MD), since seeding is guarded by 'if SOUL.md doesn't exist'.
Result: every fresh installer install got the empty template instead of
the documented Hermes persona; desktop just made it visible in onboarding.

- install.sh / install.ps1 / docker/SOUL.md now write DEFAULT_SOUL_MD.
- _ensure_default_soul_md() upgrades a SOUL.md still matching the known
  legacy scaffold in place; customized files (any deviation, incl. a
  persona appended below the comment) are never touched.
- Detection normalizes CRLF/BOM so Windows-installer drift still matches.
2026-06-24 18:56:26 -07:00

76 lines
3.6 KiB
Python

"""Default SOUL.md template seeded into HERMES_HOME on first run."""
DEFAULT_SOUL_MD = (
"You are Hermes Agent, an intelligent AI assistant created by Nous Research. "
"You are helpful, knowledgeable, and direct. You assist users with a wide "
"range of tasks including answering questions, writing and editing code, "
"analyzing information, creative work, and executing actions via your tools. "
"You communicate clearly, admit uncertainty when appropriate, and prioritize "
"being genuinely useful over being verbose unless otherwise directed below. "
"Be targeted and efficient in your exploration and investigations."
)
# Legacy SOUL.md boilerplate that older installers (install.sh / install.ps1 /
# docker/SOUL.md) seeded before they were switched to write DEFAULT_SOUL_MD.
# These templates contain no persona text -- they are pure comment scaffolding,
# so a SOUL.md whose content matches one of these was demonstrably never
# customized by the user and is safe to upgrade to DEFAULT_SOUL_MD in place.
#
# Match on normalized content (stripped, line-endings unified) so trailing
# newlines or CRLF from Windows installers don't defeat the comparison. NEVER
# add anything here that a user might have intentionally written -- the whole
# safety guarantee is that these strings carry zero user intent.
_LEGACY_TEMPLATE_SOULS = (
(
"# Hermes Agent Persona\n"
"\n"
"<!--\n"
"This file defines the agent's personality and tone.\n"
"The agent will embody whatever you write here.\n"
"Edit this to customize how Hermes communicates with you.\n"
"\n"
"Examples:\n"
' - "You are a warm, playful assistant who uses kaomoji occasionally."\n'
' - "You are a concise technical expert. No fluff, just facts."\n'
' - "You speak like a friendly coworker who happens to know everything."\n'
"\n"
"This file is loaded fresh each message -- no restart needed.\n"
"Delete the contents (or this file) to use the default personality.\n"
"-->"
),
# docker/SOUL.md and the install.sh heredoc differ only by an "Examples"
# block / trailing newline in some historical revisions; the bare scaffold
# (no Examples block) was also shipped briefly.
(
"# Hermes Agent Persona\n"
"\n"
"<!--\n"
"This file defines the agent's personality and tone.\n"
"The agent will embody whatever you write here.\n"
"Edit this to customize how Hermes communicates with you.\n"
"\n"
"This file is loaded fresh each message -- no restart needed.\n"
"Delete the contents (or this file) to use the default personality.\n"
"-->"
),
)
def _normalize_soul(text: str) -> str:
"""Normalize SOUL.md content for legacy-template comparison."""
# Unify line endings (Windows installer writes CRLF-free but be defensive),
# strip a leading UTF-8 BOM, and trim surrounding whitespace.
return text.replace("\r\n", "\n").replace("\r", "\n").lstrip("\ufeff").strip()
def is_legacy_template_soul(text: str) -> bool:
"""True if ``text`` is an old empty-template SOUL.md (no user persona).
Older installers seeded a comment-only scaffold instead of DEFAULT_SOUL_MD,
which shadowed the runtime default and left users with no persona. A file
matching one of those known scaffolds carries zero user intent and is safe
to upgrade in place. Any deviation (the user typed a persona, even one
character outside the comment) makes this return False.
"""
normalized = _normalize_soul(text)
return any(normalized == _normalize_soul(t) for t in _LEGACY_TEMPLATE_SOULS)