Skip to content
Open
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
16 changes: 16 additions & 0 deletions agent/prompt_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1905,6 +1905,18 @@ def _load_agents_md(cwd_path: Path, context_length: Optional[int] = None) -> str
return ""


def _load_hermes_home_agents_md(cwd_path: Path, context_length: Optional[int] = None) -> str:
"""Load AGENTS.md from HERMES_HOME when gateway cwd has no project context."""
try:
hermes_home = get_hermes_home().expanduser().resolve()
if hermes_home == cwd_path.resolve():
return ""
except Exception as e:
logger.debug("Could not resolve HERMES_HOME for AGENTS.md fallback: %s", e)
return ""
return _load_agents_md(hermes_home, context_length)


def _load_claude_md(cwd_path: Path, context_length: Optional[int] = None) -> str:
"""CLAUDE.md / claude.md — cwd only."""
for name in ["CLAUDE.md", "claude.md"]:
Expand Down Expand Up @@ -1962,6 +1974,7 @@ def build_context_files_prompt(
skip_soul: bool = False,
context_length: Optional[int] = None,
allow_install_tree_fallback: bool = False,
use_hermes_home_agents_fallback: bool = False,
) -> str:
"""Discover and load context files for the system prompt.

Expand All @@ -1970,6 +1983,7 @@ def build_context_files_prompt(
2. AGENTS.md / agents.md (cwd only)
3. CLAUDE.md / claude.md (cwd only)
4. .cursorrules / .cursor/rules/*.mdc (cwd only)
5. AGENTS.md / agents.md from HERMES_HOME when the gateway fallback is enabled

SOUL.md from HERMES_HOME is independent and always included when present.

Expand Down Expand Up @@ -2020,6 +2034,8 @@ def build_context_files_prompt(
or _load_claude_md(cwd_path, context_length)
or _load_cursorrules(cwd_path, context_length)
)
if not project_context and use_hermes_home_agents_fallback:
project_context = _load_hermes_home_agents_md(cwd_path, context_length)
if project_context:
sections.append(project_context)

Expand Down
3 changes: 2 additions & 1 deletion agent/system_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,8 @@ def build_system_prompt_parts(agent: Any, system_message: Optional[str] = None)
context_files_prompt = _r.build_context_files_prompt(
cwd=resolve_context_cwd(), skip_soul=_soul_loaded,
context_length=_ctx_len,
allow_install_tree_fallback=agent.platform in ("cli", "tui"))
allow_install_tree_fallback=agent.platform in ("cli", "tui"),
use_hermes_home_agents_fallback=platform_key not in ("", "cli", "tui"))
if context_files_prompt:
context_parts.append(context_files_prompt)

Expand Down
46 changes: 46 additions & 0 deletions tests/agent/test_prompt_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,52 @@ def test_loads_agents_md(self, tmp_path):
assert "Ruff for linting" in result
assert "Project Context" in result

def test_gateway_agents_md_falls_back_to_hermes_home(self, tmp_path, monkeypatch):
gateway_cwd = tmp_path / "gateway-home"
gateway_cwd.mkdir()
hermes_home = tmp_path / "hermes-home"
hermes_home.mkdir()
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
(hermes_home / "AGENTS.md").write_text("Gateway project instructions.", encoding="utf-8")

result = build_context_files_prompt(
cwd=str(gateway_cwd),
skip_soul=True,
use_hermes_home_agents_fallback=True,
)

assert "Gateway project instructions." in result
assert "## AGENTS.md" in result

def test_cli_default_does_not_use_hermes_home_agents_fallback(self, tmp_path, monkeypatch):
project = tmp_path / "project"
project.mkdir()
hermes_home = tmp_path / "hermes-home"
hermes_home.mkdir()
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
(project / "CLAUDE.md").write_text("Project-local Claude rules.", encoding="utf-8")
(hermes_home / "AGENTS.md").write_text("Global gateway rules.", encoding="utf-8")

result = build_context_files_prompt(cwd=str(project), skip_soul=True)

assert "Project-local Claude rules." in result
assert "Global gateway rules." not in result

def test_missing_hermes_home_agents_fallback_is_silent(self, tmp_path, monkeypatch):
gateway_cwd = tmp_path / "gateway-home"
gateway_cwd.mkdir()
hermes_home = tmp_path / "hermes-home"
hermes_home.mkdir()
monkeypatch.setenv("HERMES_HOME", str(hermes_home))

result = build_context_files_prompt(
cwd=str(gateway_cwd),
skip_soul=True,
use_hermes_home_agents_fallback=True,
)

assert result == ""

def test_skips_agents_md_in_install_tree_on_fallback(self, monkeypatch, tmp_path):
# A backend that FALLS BACK into the install tree (cwd=None → getcwd,
# the desktop default) must not load that tree's contributor AGENTS.md
Expand Down
46 changes: 46 additions & 0 deletions tests/agent/test_system_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ def _captured_context_cwd(agent):
def fake_context_files(
cwd=None, skip_soul=False, context_length=None,
allow_install_tree_fallback=False,
use_hermes_home_agents_fallback=False,
):
captured["cwd"] = cwd
captured["use_hermes_home_agents_fallback"] = use_hermes_home_agents_fallback
return ""

with (
Expand All @@ -59,6 +61,50 @@ def test_configured_dir_when_terminal_cwd_set(self, monkeypatch, tmp_path):
monkeypatch.setenv("TERMINAL_CWD", str(tmp_path))
assert _captured_context_cwd(_make_agent()) == tmp_path

def test_gateway_platform_enables_hermes_home_agents_fallback(self, monkeypatch):
monkeypatch.delenv("TERMINAL_CWD", raising=False)
captured = {}

def fake_context_files(
cwd=None, skip_soul=False, context_length=None,
allow_install_tree_fallback=False,
use_hermes_home_agents_fallback=False,
):
captured["use_hermes_home_agents_fallback"] = use_hermes_home_agents_fallback
return ""

with (
patch("run_agent.load_soul_md", return_value=""),
patch("run_agent.build_nous_subscription_prompt", return_value=""),
patch("run_agent.build_environment_hints", return_value=""),
patch("run_agent.build_context_files_prompt", side_effect=fake_context_files),
):
build_system_prompt_parts(_make_agent(platform="telegram"))

assert captured["use_hermes_home_agents_fallback"] is True

def test_cli_platform_keeps_hermes_home_agents_fallback_off(self, monkeypatch):
monkeypatch.delenv("TERMINAL_CWD", raising=False)
captured = {}

def fake_context_files(
cwd=None, skip_soul=False, context_length=None,
allow_install_tree_fallback=False,
use_hermes_home_agents_fallback=False,
):
captured["use_hermes_home_agents_fallback"] = use_hermes_home_agents_fallback
return ""

with (
patch("run_agent.load_soul_md", return_value=""),
patch("run_agent.build_nous_subscription_prompt", return_value=""),
patch("run_agent.build_environment_hints", return_value=""),
patch("run_agent.build_context_files_prompt", side_effect=fake_context_files),
):
build_system_prompt_parts(_make_agent(platform="cli"))

assert captured["use_hermes_home_agents_fallback"] is False


def _stable_prompt(agent):
with (
Expand Down