Files
hermes-agent/tests/agent/test_memory_migration_notifications.py
teknium1 a5144c71e9 fix(memory): show every memory-provider migration outcome, ask once per update
Agent-start recovery (recover_at_startup) reported installed / refused / failed /
not-in-catalog only to agent.log, and so did a selected provider that reports unavailable,
so a user with security.allow_lazy_installs: false, offline, or who declined the dependency
prompt silently ran without external memory (#119769).

- Startup outcomes go through _emit_startup_warning: printed in the CLI; other drivers get
  a sticky warn AgentNotice (Desktop toasts notices but drops warn-kind status.update), and
  the messaging gateway, which wires notice_callback per turn, receives it on turn 1.
- hermes update labels each line with the profile it is about and gives the exact
  `hermes -p <profile> plugins install <name>` command for non-active homes.
- Profiles sharing one environment get one set of dependency answers per provider
  (shared_dependency_answers): the first install asks the normal Python/Node questions,
  later homes reuse them; a decline or failure names the remaining profiles once instead of
  failing (or re-prompting) one by one; Ctrl-C ends the migration with a message (#125794).
- The success line no longer names memory.<name> (#124038); docs follow.

Co-authored-by: chelsealong <chelsealong@126.com>
2026-10-02 04:09:57 -07:00

48 lines
2.0 KiB
Python

"""A configured memory provider that cannot be recovered at agent start is shown to the user,
never only logged (#119769): the CLI prints it; other drivers get a warn notice, which a
messaging-gateway agent (callbacks wired per turn) replays on its first turn."""
from agent.agent_init import _init_memory
from agent.status_output import StatusOutputMixin
from hermes_cli import memory_provider_migration as mig
class StartupAgent(StatusOutputMixin):
enabled_toolsets = disabled_toolsets = tools = []
valid_tool_names = set()
log_prefix = ""
quiet_mode = False
status_callback = notice_callback = tool_progress_callback = None
def __init__(self, platform):
self.platform = platform
self.printed = []
def _vprint(self, message, **kwargs):
self.printed.append(message)
def _start(tmp_path, monkeypatch, platform):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.setattr(mig, "_attempted", set())
monkeypatch.setattr("pm.install.lazy_installs_allowed", lambda: False)
agent = StartupAgent(platform)
monkeypatch.setattr(agent, "_warning_presentation_enabled", lambda: True)
_init_memory(agent, {"memory": {"provider": "scout_missing_provider"}}, False, platform)
assert agent._memory_manager is None and not (tmp_path / "plugins").exists()
return agent
def test_cli_start_prints_the_refusal(tmp_path, monkeypatch):
agent = _start(tmp_path, monkeypatch, "cli")
assert any("hermes plugins install scout_missing_provider" in line for line in agent.printed)
def test_gateway_start_replays_the_refusal_once_on_first_turn(tmp_path, monkeypatch):
agent = _start(tmp_path, monkeypatch, "telegram")
notices = []
agent.notice_callback = notices.append # the gateway wires this per turn, after __init__
agent._replay_startup_warnings()
agent._replay_startup_warnings()
assert len(notices) == 1 and notices[0].level == "warn"
assert "hermes plugins install scout_missing_provider" in notices[0].text