Files
hermes-agent/hermes_cli/default_soul.py
teknium1 d10bb2ab6f test: make tests/ mirror the source tree; drop issue numbers from filenames
`scripts/run_tests.sh tests/<dir>/` is how a change gets its regression
coverage run, so a test filed under the wrong directory is a test nobody
runs when that code changes. Two kinds of drift had accumulated.

Parallel directories for one source package, folded into the mirror:
  tests/acp        -> tests/acp_adapter   (its __init__/conftest move with it)
  tests/cli        -> tests/hermes_cli    (prompt_toolkit fixture merged into
                                           hermes_cli/conftest.py)
  tests/run_agent  -> tests/agent         (backoff fixture becomes
                                           agent/conftest.py)
  tests/relay      -> tests/gateway/relay
  tests/state      -> tests/hermes_state

246 loose files at tests/ root, routed by the package they import/patch:
hermes_cli, hermes_state, agent, gateway, tools, plugins, tui_gateway, cron.
Installer and desktop-update script tests go to tests/scripts/{install,
desktop_update}/. 43 tests of root-level modules (batch_runner, utils,
hermes_constants, packaging) stay at the root.

Filenames drop their issue numbers (95 files: test_89315_x.py -> test_x.py);
the number stays in the module docstring where it has context.

Collisions: test_cli_skin_integration.py existed in both tests/ and tests/cli
with different subsets — merged into one (10 tests, all kept);
run_agent/test_pre_compress_memory_context.py -> agent/..._handoff.py;
tests/test_account_usage.py -> agent/test_account_usage_fetch.py;
tests/test_web_server.py -> hermes_cli/test_web_server_ws_ping.py.
Deleted: test_minisweagent_path.py (empty since PR #2804),
test_model_picker_scroll.py (tested a private copy of the logic, imported
nothing), test_process_loop_event_loop_warning.py (asserted asyncio behaviour,
imported nothing from Hermes).

Repo-root path arithmetic (Path(__file__).parents[N], dirname chains) is
bumped for the 202 files that changed depth and verified by evaluating every
such expression against the new location. classify_changes' desktop-updater
lane prefix, tests-os.yml's ignore glob and every in-tree path comment follow
the moves. tests/test_tests_tree_layout.py keeps the tree from drifting back.
2026-09-13 09:18:02 -07:00

73 lines
4.4 KiB
Python

"""Default SOUL.md template seeded into HERMES_HOME on first run."""
# Kept identical to agent/prompt_builder.py's DEFAULT_AGENT_IDENTITY: _ensure_default_soul_md()
# seeds this into SOUL.md on first run, so it is the text virtually every real user gets. The old
# "targeted and efficient exploration" line is deliberately absent (see DEFAULT_AGENT_IDENTITY) --
# never re-add it here either.
# DEFAULT_AGENT_IDENTITY only serves sessions with no SOUL.md at all (e.g. skip_context_files), which is not
# the common case. See #95681.
DEFAULT_SOUL_MD = (
"You are Hermes Agent, built by Nous Research. Be direct: match the length of your reply to the weight of "
"the ask — a one-line question gets a one-line answer, and finished work gets a short report of what "
"changed, what's verified, and what's left, never a replay of the process. No filler (\"Great question,\" "
"\"I'd be happy to\"), no restating the request back, no re-summarizing what you already said, no narrating "
"tool calls the user can see. Plain claims over adjectives; when unsure, say so plainly. Agree because it's "
"right, not because the user said it. Depth is earned — give it when the user asks for detail, teaches, or "
"the stakes demand it, not by default."
)
_SCAFFOLD_HEAD = (
"# Hermes Agent Persona\n\n<!--\nThis file defines the agent's personality and tone.\n"
"The agent will embody whatever you write here.\nEdit this to customize how Hermes communicates with you.\n\n"
)
_SCAFFOLD_TAIL = (
"This file is loaded fresh each message -- no restart needed.\n"
"Delete the contents (or this file) to use the default personality.\n-->"
)
# Auto-seeded SOUL.md content that carries zero user intent, so a matching file is safe to upgrade
# to DEFAULT_SOUL_MD in place: comment-only scaffolds older installers (install.sh / install.ps1 /
# docker/SOUL.md) wrote, plus earlier generations of the auto-seeded default text. Compared on
# normalized content (stripped, line endings unified). NEVER add anything here a user might have
# intentionally written -- that is the whole safety guarantee.
_LEGACY_TEMPLATE_SOULS = (
_SCAFFOLD_HEAD + (
"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'
) + _SCAFFOLD_TAIL,
# Bare scaffold without the "Examples" block, shipped briefly.
_SCAFFOLD_HEAD + _SCAFFOLD_TAIL,
# The previous generation of DEFAULT_SOUL_MD (same auto-seed mechanism, older string).
(
"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."
),
# ASCII-dashed variant seeded by scripts/install.ps1 (must stay pure ASCII, see
# tests/scripts/install/test_install_ps1_ascii_only.py); upgrading converges Windows installs on the em-dash text.
DEFAULT_SOUL_MD.replace("\u2014", "--"),
)
def _normalize_soul(text: str) -> str:
"""Unify line endings, strip a leading UTF-8 BOM, trim 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 a non-customized, auto-seeded SOUL.md (see ``_LEGACY_TEMPLATE_SOULS``).
Covers two generations of non-user-authored content: older installers' comment-only scaffold (which
shadowed the runtime default and left users with no persona), and the pre-#95681 generation of
DEFAULT_SOUL_MD itself (auto-seeded, never edited). A file matching one of those known strings 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)