Files
hermes-agent/tests/tools/test_bot_mode_dm_entry.py
kshitijk4poor be0a4147aa fix(bot-mode): only a live-admitted DM stays ambiguous when the runner cannot boot
Narrows the previous commit to the one case where the outcome is really
unknown. When the sender has already pinned a live intent (<dm>.live.json)
it may have admitted the DM and told the model not to resend; a runner
that then fails hermes_bootstrap now prints the same ambiguous payload
(same delivery id, "Do not resend") that _run_delivery prints for an
admission failure. Without an intent nothing was handed over, so the plain
repair hint stays the truth and a resend remains correct.

The payload is shared with _run_delivery via _live_outcome_unknown instead
of a second classifier, and the evidence-probing variants and the separate
test module are dropped in favour of one real -I -S subprocess test plus an
explicit negative assertion on the existing no-intent test.
2026-09-29 03:01:04 +05:30

156 lines
7.3 KiB
Python

"""The ``bot_mode_dm.py`` runner entry boots Hermes' dependency environment itself.
``message_agent`` spawns ``<sys.executable> tools/bot_mode_dm.py --run-delivery …`` and the relay
spawns ``… --wait-reply …`` (``bot_relay.waiter_command``). Under a PM-managed install
``sys.executable`` is the bare store interpreter: dependencies are activated in-process at boot,
and the terminal backend strips the Hermes-owned ``PYTHONPATH``, so a runner that does not boot
like every other entry point dies on its first third-party import. Live, every local Bot Chat DM
came back ``Live admission outcome unknown: No module named 'ruamel'. Do not resend.``
Each test launches the real script under ``-I -S`` (no site-packages, no ``PYTHON*`` env), with a
PM-committed dependency record in a temp ``HERMES_HOME`` as the only way to reach the packages.
"""
import json
import os
import shlex
import subprocess
import sys
from pathlib import Path
import pytest
from pm.environments import install_state_dir, runtime_facts_path, site_packages
from tools import bot_relay
from tools.bot_mode_dm import _dm_delivery_id
REPO = Path(__file__).resolve().parents[2]
RUNNER = REPO / "tools" / "bot_mode_dm.py"
# A bare interpreter: isolated (-I ignores PYTHONPATH/PYTHONHOME, user site) and no site
# module (-S), so no third-party package is importable unless the entry activates one.
BARE = [sys.executable, "-I", "-S"]
@pytest.fixture
def committed_home(tmp_path, monkeypatch):
"""A temp ``HERMES_HOME`` whose install state commits a dependency generation for REPO.
The generation's site-packages carries a ``.pth`` naming this interpreter's own package
directories, so activating it (and only activating it) makes the real dependencies importable.
"""
home = tmp_path / "hermes-home"
home.mkdir()
monkeypatch.setenv("HERMES_HOME", str(home))
venv = install_state_dir(REPO) / "environments" / "gen" / "venv"
venv.mkdir(parents=True)
(venv / "pyvenv.cfg").write_text(
"version = {}.{}.{}\n".format(*sys.version_info[:3]), encoding="utf-8")
packages = site_packages(venv)
packages.mkdir(parents=True)
dirs = [p for p in sys.path if p and Path(p).is_dir() and Path(p).resolve() != REPO]
(packages / "hermes-test-deps.pth").write_text("\n".join(dirs) + "\n", encoding="utf-8")
return home, _commit(home, venv)
@pytest.fixture
def uncommittable_home(tmp_path, monkeypatch):
"""A temp ``HERMES_HOME`` whose committed generation is gone, so activation cannot succeed."""
home = tmp_path / "hermes-home"
home.mkdir()
monkeypatch.setenv("HERMES_HOME", str(home))
install_state_dir(REPO).mkdir(parents=True)
return home, _commit(home, install_state_dir(REPO) / "environments" / "missing" / "venv")
def _commit(home, venv):
runtime_facts_path(REPO).write_text(
json.dumps({"packages": {"venv": {"environment": str(venv)}}}), encoding="utf-8")
env = {k: v for k, v in os.environ.items() if k not in ("PYTHONPATH", "PYTHONHOME", "VIRTUAL_ENV")}
env["HERMES_HOME"] = str(home)
return env
def _run(argv, env):
return subprocess.run(argv, capture_output=True, text=True, encoding="utf-8", errors="replace",
env=env, cwd=str(REPO.parent), timeout=120)
def test_delivery_runner_admits_through_dependencies_it_activates(tmp_path, committed_home):
"""Live admission (``_admit_live_dm``) imports Hermes' third-party graph. Launched bare, the
runner must activate the committed environment, find no live owner, and hand the DM to the
transport, which consumes the file — never report an ``ambiguous`` import failure."""
_home, env = committed_home
profile_home = tmp_path / "profile"
profile_home.mkdir()
dm_file = tmp_path / "dm.txt"
dm_file.write_text("hello teammate", encoding="utf-8")
# The transport echoes the DM file it receives via --query-file.
transport = [sys.executable, "-I", "-S", "-c", "import sys; print(open(sys.argv[2], encoding='utf-8').read())"]
result = _run([*BARE, str(RUNNER), "--run-delivery", "query-file", str(dm_file),
"--profile-home", str(profile_home), *transport], env)
assert "No module named" not in result.stdout + result.stderr, result
assert result.returncode == 0, result
assert result.stdout.strip() == "hello teammate"
assert not dm_file.exists()
def test_delivery_runner_that_cannot_activate_names_the_repair_remedy(tmp_path, uncommittable_home):
"""When the committed environment cannot be activated, the runner exits with PM's
``hermes pm repair`` remedy before touching the DM, instead of an ambiguous
``No module named …`` outcome the sender is told not to resend."""
_home, env = uncommittable_home
dm_file = tmp_path / "dm.txt"
dm_file.write_text("hello teammate", encoding="utf-8")
result = _run([*BARE, str(RUNNER), "--run-delivery", "query-file", str(dm_file),
sys.executable, "-c", "pass"], env)
assert result.returncode == 1, result
assert "hermes pm repair" in result.stderr
assert "No module named" not in result.stdout + result.stderr
assert result.stdout == "" # nothing was handed over, so no "do not resend" outcome
assert dm_file.read_text(encoding="utf-8") == "hello teammate"
def test_delivery_runner_that_cannot_activate_after_live_admission_stays_ambiguous(tmp_path, uncommittable_home):
"""The sender may already have admitted this DM to the live owner (its pinned intent exists)
and was told not to resend. A runner that then cannot activate must keep that outcome
unknown, with the same delivery id, instead of a bare repair hint that reads as a failure."""
_home, env = uncommittable_home
dm_file = tmp_path / "dm.txt"
dm_file.write_text("hello teammate", encoding="utf-8")
intent = Path(str(dm_file) + ".live.json")
intent.write_text("{}", encoding="utf-8")
result = _run([*BARE, str(RUNNER), "--run-delivery", "query-file", str(dm_file),
"--profile-home", str(tmp_path), sys.executable, "-c", "pass"], env)
assert result.returncode == 1, result
assert "hermes pm repair" in result.stderr
payload = json.loads(result.stdout)
assert payload["status"] == "ambiguous"
assert payload["delivery_id"] == _dm_delivery_id(str(dm_file))
assert "Do not resend" in payload["error"]
assert dm_file.exists() and intent.exists()
@pytest.mark.parametrize("home", ["committed_home", "uncommittable_home"])
def test_reply_waiter_prints_the_reply_whatever_the_dependency_state(tmp_path, request, home):
"""The relay's reply waiter shares the entry but is stdlib only, and its stdout is the
sender's wake-up: its argv, exactly as ``waiter_command`` builds it, must print the reply
whether or not the committed environment can be activated."""
_home, env = request.getfixturevalue(home)
root = tmp_path / "root"
envelope = {"id": "e" * 32, "target_handle": "researcher", "target_connection": "ssh-vps"}
reply_path = bot_relay.relay_root(root) / bot_relay.REPLIES_DIR / f"{envelope['id']}.json"
reply_path.parent.mkdir(parents=True)
reply_path.write_text(json.dumps({"reply": "pong"}), encoding="utf-8")
argv = shlex.split(bot_relay.waiter_command(root, envelope))
result = _run([*BARE, *argv[1:]], env)
assert result.returncode == 0, result
assert result.stdout.splitlines() == ["Reply from @researcher on ssh-vps:", "pong"]