Files
hermes-agent/tests/agent/test_relay_runtime_plugins.py
ethernet 92686159d1 fix(pm): integrate audited runtime and lifecycle repairs
Prepare dependency generations before selecting them. Keep shipped tool
bytes separate from writable additions, and store facts beside their entries.
Validate proposed plugin sets before config publication. Restore the previous
config if the facts write fails.

Consolidate duplicate updater, backup, setup, and voice helpers. Repair
launcher selection, dependency consumers, download ownership, update feeds,
and native Windows process and file handling.

Verification: 206 changed/prior-failing Python files reported 4630 passed,
one failed, and 330 skipped. Fix the remaining Hindsight fixture boundary.
The final targeted rerun reported 234 passed and two skipped. The store
review regression batch reported 83 passed and one skipped. Desktop
TypeScript checks, 56 selected Electron tests, 24 release tests, and the
removed-import/compatibility guards passed.

This is an integration checkpoint, not full audit acceptance. The complete
Python suite has not run on this fixed tree. Crash-atomic plugin publication,
generation cleanup, receipt correlation, and packaged lifecycle acceptance
remain open in docs/pm-audit-status.md.
2026-09-05 22:36:48 -04:00

1278 lines
40 KiB
Python

"""Tests for native NeMo Relay plugin configuration ownership."""
from __future__ import annotations
import asyncio
import contextvars
import json
import threading
from types import SimpleNamespace
from typing import Any
import pytest
from agent import relay_runtime
class _FakeRelay:
def __init__(
self,
*,
initialize_error: Exception | None = None,
dynamic_initialize_error: Exception | None = None,
activation_close_error: Exception | None = None,
active_report: Any = None,
report_error: Exception | None = None,
) -> None:
self.events: list[tuple[Any, ...]] = []
self.initialize_error = initialize_error
self.dynamic_initialize_error = dynamic_initialize_error
self.activation_close_error = activation_close_error
self.active_report = active_report
self.report_error = report_error
self.dynamic_plugin_specs: list[dict[str, Any]] = []
self.ScopeType = SimpleNamespace(Agent="agent")
self.plugin = SimpleNamespace(
initialize=self._initialize_plugins,
initialize_with_dynamic_plugins=self._initialize_dynamic_plugins,
load_dynamic_plugin_activation_specs=self._load_dynamic_plugin_specs,
clear_async=self._clear_plugins_async,
report=self._report_plugins,
)
self.scope = SimpleNamespace(
push=self._scope_push,
pop=self._scope_pop,
)
self.subscribers = SimpleNamespace(flush_async=self._flush_async)
def get_scope_stack(self) -> None:
return None
async def _initialize_plugins(self, config: dict[str, Any]) -> dict[str, Any]:
self.events.append(("plugin.initialize", config))
if self.initialize_error is not None:
raise self.initialize_error
return {"diagnostics": []}
async def _initialize_dynamic_plugins(
self,
config: dict[str, Any],
dynamic_plugins: list[dict[str, Any]],
) -> Any:
self.events.append(("plugin.initialize_dynamic", config, dynamic_plugins))
if self.dynamic_initialize_error is not None:
raise self.dynamic_initialize_error
relay = self
class _Activation:
async def close(self) -> None:
relay.events.append(("plugin.activation.close",))
if relay.activation_close_error is not None:
raise relay.activation_close_error
return _Activation()
def _load_dynamic_plugin_specs(self, config_path: Any) -> list[dict[str, Any]]:
self.events.append(("plugin.load_dynamic_specs", str(config_path)))
return self.dynamic_plugin_specs
async def _clear_plugins_async(self) -> None:
self.events.append(("plugin.clear_async",))
def _report_plugins(self) -> Any:
if self.report_error is not None:
raise self.report_error
return self.active_report
def _scope_push(self, name: str, scope_type: Any, **kwargs: Any) -> Any:
handle = ("scope", name, len(self.events))
self.events.append(("scope.push", name, scope_type, kwargs))
return handle
def _scope_pop(self, handle: Any, **kwargs: Any) -> None:
self.events.append(("scope.pop", handle, kwargs))
async def _flush_async(self) -> None:
self.events.append(("subscribers.flush_async",))
class _ConcurrentPublicationRelay(_FakeRelay):
def __init__(self) -> None:
super().__init__()
self.publication_finished = threading.Event()
async def _flush_async(self) -> None:
self.events.append(("subscribers.flush_async",))
assert await asyncio.to_thread(self.publication_finished.wait, 5)
class _BehavioralFakeRelay(_FakeRelay):
"""Record plugin interception together with the active session stack."""
def __init__(self) -> None:
super().__init__()
self._scope_stack = contextvars.ContextVar(
"behavioral_fake_relay_scope_stack",
default=None,
)
self._plugin_source: str | None = None
self.tools = SimpleNamespace(request_intercepts=self._request_intercepts)
def get_scope_stack(self) -> Any:
return self._scope_stack.get()
async def _initialize_plugins(self, config: dict[str, Any]) -> dict[str, Any]:
report = await super()._initialize_plugins(config)
self._plugin_source = "static"
return report
async def _initialize_dynamic_plugins(
self,
config: dict[str, Any],
dynamic_plugins: list[dict[str, Any]],
) -> Any:
activation = await super()._initialize_dynamic_plugins(
config,
dynamic_plugins,
)
self._plugin_source = "dynamic"
return activation
def _scope_push(self, name: str, scope_type: Any, **kwargs: Any) -> Any:
handle = super()._scope_push(name, scope_type, **kwargs)
self._scope_stack.set(handle)
return handle
def _request_intercepts(
self,
tool_name: str,
args: dict[str, Any],
) -> dict[str, Any]:
scope_stack = self.get_scope_stack()
self.events.append(
(
"tools.request_intercepts",
tool_name,
args,
self._plugin_source,
scope_stack,
)
)
return {
**args,
"relay_plugin_source": self._plugin_source,
"relay_scope_stack": scope_stack,
}
@pytest.fixture(autouse=True)
def _reset_runtime():
relay_runtime._reset_for_tests()
yield
relay_runtime._reset_for_tests()
@pytest.fixture
def explicit_static_config(tmp_path, monkeypatch):
config = tmp_path / "plugins.toml"
config.write_text("", encoding="utf-8")
monkeypatch.setenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, str(config))
return config
def test_unset_config_disables_plugin_initialization(monkeypatch):
monkeypatch.delenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, raising=False)
relay = _FakeRelay()
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
try:
assert not host.managed_execution_enabled()
assert (
host._plugin_configuration_state
is relay_runtime._RelayPluginConfigurationState.DISABLED
)
host.ensure_session({"session_id": "session"})
assert relay.events[0][0:2] == ("scope.push", relay_runtime.SESSION_SCOPE)
assert not any(event[0].startswith("plugin.") for event in relay.events)
finally:
host.shutdown()
assert not any(event[0] == "subscribers.flush_async" for event in relay.events)
def test_first_profile_plugin_decision_applies_to_later_profile(
tmp_path,
monkeypatch,
):
monkeypatch.delenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, raising=False)
relay = _FakeRelay()
host_a = relay_runtime.RelayRuntime(relay=relay, profile_key="profile-a")
config = tmp_path / "plugins.toml"
config.write_text("", encoding="utf-8")
monkeypatch.setenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, str(config))
host_b = relay_runtime.RelayRuntime(relay=relay, profile_key="profile-b")
try:
assert not host_a.managed_execution_enabled()
assert not host_b.managed_execution_enabled()
assert (
host_a._plugin_configuration_state
is relay_runtime._RelayPluginConfigurationState.DISABLED
)
assert (
host_b._plugin_configuration_state
is relay_runtime._RelayPluginConfigurationState.DISABLED
)
assert not any(event[0].startswith("plugin.") for event in relay.events)
finally:
host_a.shutdown()
host_b.shutdown()
def test_relay_initializes_explicit_plugins_before_first_session_scope(
explicit_static_config,
):
relay = _FakeRelay()
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
try:
assert host.managed_execution_enabled()
host.ensure_session({"session_id": "session"})
assert relay.events[0] == ("plugin.initialize", {})
assert relay.events[1][0:2] == ("scope.push", relay_runtime.SESSION_SCOPE)
finally:
host.shutdown()
def test_foreign_active_plugin_configuration_is_left_unchanged(
explicit_static_config,
caplog,
):
foreign_report = {"diagnostics": [], "source": "embedding-host"}
relay = _FakeRelay(active_report=foreign_report)
with caplog.at_level("WARNING"):
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
try:
assert not host.managed_execution_enabled()
assert (
host._plugin_configuration_state
is relay_runtime._RelayPluginConfigurationState.FOREIGN
)
assert relay.active_report is foreign_report
assert relay.events == []
assert "already active outside Hermes native ownership" in caplog.text
assert "leaving it unchanged" in caplog.text
finally:
host.shutdown()
def test_unreadable_foreign_plugin_state_fails_safe(
explicit_static_config,
caplog,
):
relay = _FakeRelay(report_error=RuntimeError("report unavailable"))
with caplog.at_level("WARNING"):
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
try:
assert not host.managed_execution_enabled()
assert (
host._plugin_configuration_state
is relay_runtime._RelayPluginConfigurationState.FAILED
)
assert relay.events == []
assert "refusing to replace it" in caplog.text
finally:
host.shutdown()
def test_legacy_exporter_env_without_plugins_toml_warns_and_stays_disabled(
monkeypatch,
caplog,
):
monkeypatch.delenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, raising=False)
monkeypatch.setenv("HERMES_NEMO_RELAY_ATOF_ENABLED", "1")
monkeypatch.setenv("HERMES_NEMO_RELAY_ATIF_EXPORT_TIMEOUT_S", "30")
relay = _FakeRelay()
with caplog.at_level("WARNING"):
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
try:
assert not host.managed_execution_enabled()
assert (
host._plugin_configuration_state
is relay_runtime._RelayPluginConfigurationState.DISABLED
)
assert relay.events == []
assert "no HERMES_NEMO_RELAY_PLUGINS_TOML was provided" in caplog.text
assert "HERMES_NEMO_RELAY_ATOF_ENABLED" in caplog.text
assert "HERMES_NEMO_RELAY_ATIF_EXPORT_TIMEOUT_S" in caplog.text
finally:
host.shutdown()
def test_initialization_failure_is_fail_open(explicit_static_config, caplog):
relay = _FakeRelay(initialize_error=RuntimeError("rejected config"))
with caplog.at_level("WARNING"):
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
try:
assert not host.managed_execution_enabled()
assert (
host._plugin_configuration_state
is relay_runtime._RelayPluginConfigurationState.FAILED
)
assert "Hermes Relay plugin initialization failed" in caplog.text
finally:
host.shutdown()
def test_later_host_shares_initialization_failure(explicit_static_config):
relay = _FakeRelay(initialize_error=RuntimeError("transient failure"))
failed_host = relay_runtime.RelayRuntime(relay=relay, profile_key="failed")
assert not failed_host.managed_execution_enabled()
assert (
failed_host._plugin_configuration_state
is relay_runtime._RelayPluginConfigurationState.FAILED
)
relay.initialize_error = None
later_host = relay_runtime.RelayRuntime(relay=relay, profile_key="later")
try:
assert not later_host.managed_execution_enabled()
assert (
later_host._plugin_configuration_state
is relay_runtime._RelayPluginConfigurationState.FAILED
)
assert relay.events == [("plugin.initialize", {})]
finally:
failed_host.shutdown()
later_host.shutdown()
retry_host = relay_runtime.RelayRuntime(relay=relay, profile_key="retry")
try:
assert retry_host.managed_execution_enabled()
assert (
retry_host._plugin_configuration_state
is relay_runtime._RelayPluginConfigurationState.ACTIVE
)
assert relay.events.count(("plugin.initialize", {})) == 2
finally:
retry_host.shutdown()
def test_missing_explicit_config_is_failed_for_all_current_hosts(
tmp_path,
monkeypatch,
caplog,
):
missing_config = tmp_path / "missing" / "plugins.toml"
monkeypatch.setenv(
relay_runtime.RELAY_PLUGINS_CONFIG_ENV,
str(missing_config),
)
relay = _FakeRelay()
with caplog.at_level("WARNING"):
first_host = relay_runtime.RelayRuntime(relay=relay, profile_key="first")
missing_config.parent.mkdir()
missing_config.write_text("", encoding="utf-8")
later_host = relay_runtime.RelayRuntime(relay=relay, profile_key="later")
try:
for host in (first_host, later_host):
assert not host.managed_execution_enabled()
assert (
host._plugin_configuration_state
is relay_runtime._RelayPluginConfigurationState.FAILED
)
assert relay.events == []
assert "continuing without Relay plugins" in caplog.text
finally:
first_host.shutdown()
later_host.shutdown()
def test_malformed_explicit_config_does_not_fall_back_to_discovery(
tmp_path,
monkeypatch,
caplog,
):
config = tmp_path / "plugins.toml"
config.write_text("[[components]\nkind =", encoding="utf-8")
monkeypatch.setenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, str(config))
relay = _FakeRelay()
with caplog.at_level("WARNING"):
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
try:
assert not host.managed_execution_enabled()
assert (
host._plugin_configuration_state
is relay_runtime._RelayPluginConfigurationState.FAILED
)
assert relay.events == []
assert "continuing without Relay plugins" in caplog.text
finally:
host.shutdown()
def test_present_plugins_section_is_validated_even_when_falsey(
tmp_path,
monkeypatch,
caplog,
):
config = tmp_path / "plugins.toml"
config.write_text("plugins = []", encoding="utf-8")
monkeypatch.setenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, str(config))
relay = _FakeRelay()
def reject_invalid_plugins(_config_path):
raise ValueError("'plugins' must be a table")
relay.plugin.load_dynamic_plugin_activation_specs = reject_invalid_plugins
with caplog.at_level("WARNING"):
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
try:
assert not host.managed_execution_enabled()
assert (
host._plugin_configuration_state
is relay_runtime._RelayPluginConfigurationState.FAILED
)
assert relay.events == []
assert "'plugins' must be a table" in caplog.text
assert "continuing without Relay plugins" in caplog.text
finally:
host.shutdown()
def test_two_profile_hosts_initialize_once_and_clear_after_final_shutdown(
explicit_static_config,
caplog,
):
relay = _BehavioralFakeRelay()
with caplog.at_level("INFO"):
host_a = relay_runtime.RelayRuntime(relay=relay, profile_key="profile-a")
host_b = relay_runtime.RelayRuntime(relay=relay, profile_key="profile-b")
assert relay.events == [("plugin.initialize", {})]
assert host_a.managed_execution_enabled()
assert host_b.managed_execution_enabled()
assert (
caplog.text.count(
"Relay plugins are active process-wide and apply to all profiles "
"hosted by this Hermes process."
)
== 1
)
rewritten_a = host_a.apply_tool_request_intercepts(
session_id="profile-a-session",
tool_name="terminal",
args={"profile": "a"},
)
rewritten_b = host_b.apply_tool_request_intercepts(
session_id="profile-b-session",
tool_name="terminal",
args={"profile": "b"},
)
assert rewritten_a["relay_plugin_source"] == "static"
assert rewritten_b["relay_plugin_source"] == "static"
assert rewritten_a["relay_scope_stack"] != rewritten_b["relay_scope_stack"]
host_a.shutdown()
assert ("plugin.clear_async",) not in relay.events
host_b.shutdown()
assert relay.events[-2:] == [
("subscribers.flush_async",),
("plugin.clear_async",),
]
assert relay.events.count(("plugin.initialize", {})) == 1
assert relay.events.count(("plugin.clear_async",)) == 1
pop_index = next(
index for index, event in enumerate(relay.events) if event[0] == "scope.pop"
)
assert pop_index < relay.events.index(("plugin.clear_async",))
def test_plugin_initialization_inside_running_event_loop(explicit_static_config):
relay = _FakeRelay()
async def construct_host() -> relay_runtime.RelayRuntime:
return relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
host = asyncio.run(construct_host())
try:
assert relay.events == [("plugin.initialize", {})]
assert host.managed_execution_enabled()
finally:
host.shutdown()
def test_static_plugin_cleanup_uses_async_apis_inside_running_event_loop(
explicit_static_config,
):
relay = _FakeRelay()
async def run_lifecycle() -> None:
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
host.shutdown()
asyncio.run(run_lifecycle())
assert relay.events == [
("plugin.initialize", {}),
("subscribers.flush_async",),
("plugin.clear_async",),
]
def test_dynamic_plugins_share_owned_activation_until_final_host_shutdown(
tmp_path,
monkeypatch,
):
config = tmp_path / ".nemo-relay" / "plugins.toml"
config.parent.mkdir()
config.write_text(
"""
version = 1
[[components]]
kind = "observability"
enabled = true
[components.config]
version = 1
[[plugins.dynamic]]
manifest = "plugins/native/relay-plugin.toml"
[plugins.dynamic.config]
mode = "strict"
[[plugins.dynamic]]
manifest = "plugins/worker/relay-plugin.toml"
""".strip(),
encoding="utf-8",
)
monkeypatch.setenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, str(config))
relay = _BehavioralFakeRelay()
relay.dynamic_plugin_specs = [
{
"plugin_id": "native.policy",
"kind": "rust_dynamic",
"manifest_ref": str(
config.parent / "plugins/native/relay-plugin.toml"
),
"config": {"mode": "strict"},
},
{
"plugin_id": "worker.policy",
"kind": "worker",
"manifest_ref": str(
config.parent / "plugins/worker/relay-plugin.toml"
),
"environment_ref": str(config.parent / "environments/worker"),
"config": {},
},
]
host_a = relay_runtime.RelayRuntime(relay=relay, profile_key="profile-a")
host_b = relay_runtime.RelayRuntime(relay=relay, profile_key="profile-b")
assert host_a.managed_execution_enabled()
assert host_b.managed_execution_enabled()
assert relay.events == [
("plugin.load_dynamic_specs", str(config)),
(
"plugin.initialize_dynamic",
{
"version": 1,
"components": [
{
"kind": "observability",
"enabled": True,
"config": {"version": 1},
}
],
},
relay.dynamic_plugin_specs,
)
]
rewritten_a = host_a.apply_tool_request_intercepts(
session_id="profile-a-session",
tool_name="terminal",
args={"profile": "a"},
)
rewritten_b = host_b.apply_tool_request_intercepts(
session_id="profile-b-session",
tool_name="terminal",
args={"profile": "b"},
)
assert rewritten_a["relay_plugin_source"] == "dynamic"
assert rewritten_b["relay_plugin_source"] == "dynamic"
assert rewritten_a["relay_scope_stack"] != rewritten_b["relay_scope_stack"]
host_a.shutdown()
assert ("plugin.activation.close",) not in relay.events
host_b.shutdown()
assert relay.events[-2:] == [
("subscribers.flush_async",),
("plugin.activation.close",),
]
assert ("plugin.clear_async",) not in relay.events
assert relay.events.count(("plugin.activation.close",)) == 1
pop_index = next(
index for index, event in enumerate(relay.events) if event[0] == "scope.pop"
)
assert pop_index < relay.events.index(("plugin.activation.close",))
def test_dynamic_activation_failure_disables_plugins(
tmp_path,
monkeypatch,
caplog,
):
config = tmp_path / "plugins.toml"
config.write_text(
"""
[[plugins.dynamic]]
manifest = "relay-plugin.toml"
""".strip(),
encoding="utf-8",
)
monkeypatch.setenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, str(config))
relay = _FakeRelay(
dynamic_initialize_error=RuntimeError("worker rejected config")
)
relay.dynamic_plugin_specs = [{"plugin_id": "worker.policy"}]
with caplog.at_level("INFO"):
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
try:
assert not host.managed_execution_enabled()
assert (
host._plugin_configuration_state
is relay_runtime._RelayPluginConfigurationState.FAILED
)
assert [event[0] for event in relay.events] == [
"plugin.load_dynamic_specs",
"plugin.initialize_dynamic",
]
assert "dynamic plugin activation failed" in caplog.text
assert "Relay plugins are active process-wide" not in caplog.text
finally:
host.shutdown()
assert ("subscribers.flush_async",) not in relay.events
assert ("plugin.clear_async",) not in relay.events
def test_dynamic_activation_lifecycle_inside_running_event_loop(
tmp_path,
monkeypatch,
):
config = tmp_path / "plugins.toml"
config.write_text(
"""
[[plugins.dynamic]]
manifest = "relay-plugin.toml"
""".strip(),
encoding="utf-8",
)
monkeypatch.setenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, str(config))
relay = _FakeRelay()
relay.dynamic_plugin_specs = [{"plugin_id": "native.policy"}]
async def run_lifecycle() -> None:
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
assert host.managed_execution_enabled()
host.shutdown()
asyncio.run(run_lifecycle())
assert [event[0] for event in relay.events] == [
"plugin.load_dynamic_specs",
"plugin.initialize_dynamic",
"subscribers.flush_async",
"plugin.activation.close",
]
def test_shutdown_defers_dynamic_unload_until_async_operation_finishes(
tmp_path,
monkeypatch,
):
config = tmp_path / "plugins.toml"
config.write_text(
"""
[[plugins.dynamic]]
manifest = "relay-plugin.toml"
""".strip(),
encoding="utf-8",
)
monkeypatch.setenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, str(config))
relay = _FakeRelay()
relay.dynamic_plugin_specs = [{"plugin_id": "worker.policy"}]
async def run_lifecycle() -> None:
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
session = host.ensure_session({"session_id": "session"})
assert session is not None
started = asyncio.Event()
finish = asyncio.Event()
async def in_flight_call() -> None:
relay.events.append(("operation.start",))
started.set()
await finish.wait()
relay.events.append(("operation.end",))
operation = asyncio.create_task(
host.run_in_session_async(session, in_flight_call)
)
await started.wait()
host.shutdown()
assert host.ensure_session({"session_id": "late-session"}) is None
assert ("plugin.activation.close",) not in relay.events
finish.set()
await operation
assert await asyncio.to_thread(host._shutdown_complete.wait, 5)
asyncio.run(run_lifecycle())
assert relay.events.index(("operation.end",)) < relay.events.index(
("plugin.activation.close",)
)
def test_session_close_does_not_flush_during_concurrent_managed_publication(
explicit_static_config,
):
relay = _ConcurrentPublicationRelay()
completed = threading.Event()
errors: list[BaseException] = []
async def run_lifecycle() -> None:
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
closing_session = host.ensure_session({"session_id": "closing"})
active_session = host.ensure_session({"session_id": "active"})
assert closing_session is not None
assert active_session is not None
publication_started = asyncio.Event()
finish_publication = asyncio.Event()
async def managed_publication() -> None:
relay.events.append(("publication.start",))
publication_started.set()
await finish_publication.wait()
relay.events.append(("publication.end",))
relay.publication_finished.set()
publication = asyncio.create_task(
host.run_in_session_async(active_session, managed_publication)
)
await publication_started.wait()
host.close_session({"session_id": "closing"})
relay.events.append(("session.close.returned",))
finish_publication.set()
await publication
host.shutdown()
assert host._shutdown_complete.is_set()
def run_on_event_loop_thread() -> None:
try:
asyncio.run(run_lifecycle())
except BaseException as exc:
errors.append(exc)
finally:
completed.set()
event_loop_thread = threading.Thread(
target=run_on_event_loop_thread,
name="hermes-relay-session-close-regression",
daemon=True,
)
event_loop_thread.start()
if not completed.wait(3):
# Release a broken implementation so the test process can clean up
# after reporting the same deadlock guarded in production.
relay.publication_finished.set()
assert completed.wait(5)
pytest.fail("session close blocked the active asyncio event loop")
event_loop_thread.join()
assert errors == []
assert relay.events.count(("subscribers.flush_async",)) == 1
assert relay.events.index(("session.close.returned",)) < relay.events.index(
("publication.end",)
)
assert relay.events.index(("publication.end",)) < relay.events.index(
("subscribers.flush_async",)
)
assert relay.events[-1] == ("plugin.clear_async",)
def test_failed_dynamic_teardown_retains_activation_and_blocks_replacement(
tmp_path,
monkeypatch,
caplog,
):
config = tmp_path / "plugins.toml"
config.write_text(
"""
[[plugins.dynamic]]
manifest = "relay-plugin.toml"
""".strip(),
encoding="utf-8",
)
monkeypatch.setenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, str(config))
relay = _FakeRelay(activation_close_error=RuntimeError("worker still busy"))
relay.dynamic_plugin_specs = [
{
"plugin_id": "worker.policy",
"kind": "worker",
"manifest_ref": str(tmp_path / "relay-plugin.toml"),
"environment_ref": str(tmp_path / "environment"),
"config": {},
}
]
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
with caplog.at_level("WARNING"):
host.shutdown()
assert "plugin configuration cleanup failed" in caplog.text
activation = relay_runtime._PLUGIN_CONFIGURATION._activation
assert activation is not None
with caplog.at_level("WARNING"):
replacement = relay_runtime.RelayRuntime(
relay=relay,
profile_key="replacement",
)
try:
assert not replacement.managed_execution_enabled()
assert relay_runtime._PLUGIN_CONFIGURATION._activation is activation
assert relay.events.count(
("plugin.initialize_dynamic", {}, relay.dynamic_plugin_specs)
) == 1
assert relay.events.count(("plugin.activation.close",)) == 2
assert "refusing to replace" in caplog.text
finally:
replacement.shutdown()
# Relay treats a close failure as terminal; only reset the permissive
# fake so this process-global fixture cannot leak into later tests.
relay.activation_close_error = None
relay_runtime._PLUGIN_CONFIGURATION.reset_for_tests()
def test_standard_dynamic_records_use_relay_toml_loader(
tmp_path,
monkeypatch,
):
config = tmp_path / "plugins.toml"
config.write_text(
"""
[[plugins.dynamic]]
manifest = "relay-plugin.toml"
""".strip(),
encoding="utf-8",
)
monkeypatch.setenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, str(config))
relay = _FakeRelay()
relay.dynamic_plugin_specs = [
{
"plugin_id": "native.policy",
"kind": "rust_dynamic",
"manifest_ref": str(tmp_path / "relay-plugin.toml"),
"config": {},
}
]
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
try:
assert host.managed_execution_enabled()
assert relay.events == [
("plugin.load_dynamic_specs", str(config)),
("plugin.initialize_dynamic", {}, relay.dynamic_plugin_specs),
]
finally:
host.shutdown()
def test_legacy_dynamic_records_are_rejected(
tmp_path,
monkeypatch,
caplog,
):
config = tmp_path / "plugins.toml"
config.write_text(
"""
version = 1
[[dynamic_plugins]]
plugin_id = "native.policy"
kind = "rust_dynamic"
manifest_ref = "relay-plugin.toml"
""".strip(),
encoding="utf-8",
)
monkeypatch.setenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, str(config))
relay = _FakeRelay()
with caplog.at_level("WARNING"):
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
try:
assert not host.managed_execution_enabled()
assert (
host._plugin_configuration_state
is relay_runtime._RelayPluginConfigurationState.FAILED
)
assert relay.events == []
assert "Hermes [[dynamic_plugins]] records are unsupported" in caplog.text
assert "use Relay [[plugins.dynamic]] records" in caplog.text
assert "continuing without Relay plugins" in caplog.text
finally:
host.shutdown()
def test_real_binding_loads_standard_dynamic_specs_from_explicit_toml(
tmp_path,
monkeypatch,
):
relay = pytest.importorskip("nemo_relay")
manifest = tmp_path / "plugins" / "relay-plugin.toml"
manifest.parent.mkdir()
manifest.write_text(
"""
manifest_version = 1
[plugin]
id = "fixture.native"
kind = "rust_dynamic"
""".strip(),
encoding="utf-8",
)
config = tmp_path / "plugins.toml"
config.write_text(
"""
version = 1
[[plugins.dynamic]]
manifest = "plugins/relay-plugin.toml"
[plugins.dynamic.config]
mode = "strict"
""".strip(),
encoding="utf-8",
)
monkeypatch.setenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, str(config))
plugin_config, specs = relay_runtime._configured_plugin_inputs(relay)
assert plugin_config == {"version": 1}
assert [spec.to_dict() for spec in specs] == [
{
"plugin_id": "fixture.native",
"kind": "rust_dynamic",
"manifest_ref": str(manifest.resolve()),
"config": {"mode": "strict"},
}
]
def test_real_binding_ignores_project_config_without_explicit_opt_in(
tmp_path,
monkeypatch,
):
relay = pytest.importorskip("nemo_relay")
if getattr(relay, "_native", None) is None:
pytest.skip("NeMo Relay native binding is unavailable on this platform")
project_root = tmp_path / "project"
working_directory = project_root / "workspace"
config_directory = project_root / ".nemo-relay"
atof_dir = tmp_path / "atof"
working_directory.mkdir(parents=True)
config_directory.mkdir()
(config_directory / "plugins.toml").write_text(
f"""
version = 1
[[components]]
kind = "observability"
enabled = true
[components.config]
version = 3
[components.config.atof]
enabled = true
[[components.config.atof.sinks]]
type = "file"
output_directory = "{atof_dir.as_posix()}"
filename = "events.jsonl"
mode = "overwrite"
""".strip(),
encoding="utf-8",
)
xdg_config_home = tmp_path / "xdg"
xdg_config_home.mkdir()
monkeypatch.chdir(working_directory)
monkeypatch.setenv("XDG_CONFIG_HOME", str(xdg_config_home))
monkeypatch.delenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, raising=False)
relay.plugin.clear()
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
try:
assert not host.managed_execution_enabled()
host.ensure_session({"session_id": "native-no-plugins"})
finally:
host.shutdown()
relay_runtime._reset_for_tests()
assert not (atof_dir / "events.jsonl").exists()
def test_real_binding_ignores_project_config_with_explicit_opt_in(
tmp_path,
monkeypatch,
):
relay = pytest.importorskip("nemo_relay")
if getattr(relay, "_native", None) is None:
pytest.skip("NeMo Relay native binding is unavailable on this platform")
project_root = tmp_path / "project"
working_directory = project_root / "workspace"
config_directory = project_root / ".nemo-relay"
selected_directory = tmp_path / "selected-config"
project_atof_dir = tmp_path / "project-atof"
selected_atof_dir = tmp_path / "selected-atof"
working_directory.mkdir(parents=True)
config_directory.mkdir()
selected_directory.mkdir()
(config_directory / "plugins.toml").write_text(
f"""
version = 1
[[components]]
kind = "observability"
enabled = true
[components.config]
version = 3
[components.config.atof]
enabled = true
[[components.config.atof.sinks]]
type = "file"
output_directory = "{project_atof_dir.as_posix()}"
filename = "events.jsonl"
mode = "overwrite"
""".strip(),
encoding="utf-8",
)
selected_config = selected_directory / "plugins.toml"
selected_config.write_text(
f"""
version = 1
[[components]]
kind = "observability"
enabled = true
[components.config]
version = 4
[components.config.atof]
enabled = true
[[components.config.atof.sinks]]
type = "file"
output_directory = "{selected_atof_dir.as_posix()}"
filename = "events.jsonl"
mode = "overwrite"
""".strip(),
encoding="utf-8",
)
xdg_config_home = tmp_path / "xdg"
xdg_config_home.mkdir()
monkeypatch.chdir(working_directory)
monkeypatch.setenv("XDG_CONFIG_HOME", str(xdg_config_home))
monkeypatch.setenv(
relay_runtime.RELAY_PLUGINS_CONFIG_ENV,
str(selected_config),
)
relay.plugin.clear()
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
try:
assert host.managed_execution_enabled()
host.ensure_session({"session_id": "native-explicit-plugins"})
finally:
host.shutdown()
relay_runtime._reset_for_tests()
assert (selected_atof_dir / "events.jsonl").is_file()
assert not (project_atof_dir / "events.jsonl").exists()
def test_real_binding_keeps_two_profile_trajectories_separate_in_shared_exporters(
tmp_path,
monkeypatch,
):
relay = pytest.importorskip("nemo_relay")
if getattr(relay, "_native", None) is None:
pytest.skip("NeMo Relay native binding is unavailable on this platform")
from agent import relay_llm, relay_tools
working_directory = tmp_path / "project" / "workspace"
config_directory = tmp_path / "selected-config"
atof_dir = tmp_path / "atof"
atif_dir = tmp_path / "atif"
working_directory.mkdir(parents=True)
config_directory.mkdir()
config_path = config_directory / "plugins.toml"
config_path.write_text(
f"""
version = 1
[[components]]
kind = "observability"
enabled = true
[components.config]
version = 3
[components.config.atof]
enabled = true
[[components.config.atof.sinks]]
type = "file"
output_directory = "{atof_dir.as_posix()}"
filename = "events.jsonl"
mode = "overwrite"
[components.config.atif]
enabled = true
output_directory = "{atif_dir.as_posix()}"
filename_template = "trajectory-{{session_id}}.json"
agent_name = "Hermes Native Test"
agent_version = "test"
""".strip(),
encoding="utf-8",
)
xdg_config_home = tmp_path / "xdg"
xdg_config_home.mkdir()
monkeypatch.chdir(working_directory)
monkeypatch.setenv("XDG_CONFIG_HOME", str(xdg_config_home))
monkeypatch.setenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, str(config_path))
monkeypatch.setattr(relay_runtime, "_load_nemo_relay", lambda: relay)
relay.plugin.clear()
runtime_ids: dict[str, str] = {}
try:
for profile in ("profile-a", "profile-b"):
session_id = f"native-export-{profile}"
monkeypatch.setenv("HERMES_HOME", str(tmp_path / profile))
profile_key = relay_runtime.current_profile_key()
lease = relay_runtime.SESSION_COORDINATOR.acquire_conversation(
profile_key=profile_key,
session_id=session_id,
platform="cli",
model="test-model",
)
assert isinstance(lease.host, relay_runtime.RelayRuntime)
runtime_ids[profile] = lease.host.runtime_id
turn = relay_runtime.SESSION_COORDINATOR.begin_turn(
lease,
turn_id=f"turn-{profile}",
task_id=f"task-{profile}",
)
try:
assert lease.host.managed_execution_enabled()
relay_llm.execute(
{"model": "test-model", "messages": []},
lambda _request, profile=profile: {
"id": f"response-{profile}",
"model": "test-model",
"choices": [
{
"message": {
"role": "assistant",
"content": "ok",
},
"finish_reason": "stop",
}
],
},
session_id=session_id,
name="test-provider",
model_name="test-model",
metadata={
"api_mode": "chat_completions",
"api_request_id": f"request-{profile}",
},
)
relay_tools.execute(
"terminal",
{"command": "true"},
lambda _args: {"output": "ok"},
session_id=session_id,
metadata={"tool_call_id": f"tool-{profile}"},
)
finally:
relay_runtime.SESSION_COORDINATOR.end_turn(
turn,
outcome="success",
)
relay_runtime.SESSION_COORDINATOR.release_conversation(lease)
relay_runtime.SESSION_COORDINATOR.finalize_conversation(
profile_key=profile_key,
session_id=session_id,
)
finally:
relay_runtime._reset_for_tests()
assert (atof_dir / "events.jsonl").is_file()
atof_payload = (atof_dir / "events.jsonl").read_text(encoding="utf-8")
assert all(runtime_id in atof_payload for runtime_id in runtime_ids.values())
trajectories = list(atif_dir.glob("trajectory-*.json"))
assert len(trajectories) == 2
observed_runtime_ids: set[str] = set()
for trajectory_path in trajectories:
trajectory = json.loads(trajectory_path.read_text(encoding="utf-8"))
trajectory_payload = json.dumps(trajectory)
matching_runtime_ids = {
runtime_id
for runtime_id in runtime_ids.values()
if runtime_id in trajectory_payload
}
assert len(matching_runtime_ids) == 1
observed_runtime_ids.update(matching_runtime_ids)
observed_categories = {
event["category"]
for event in trajectory["extra"]["observed_events"]
if event["kind"] == "scope"
}
assert {"agent", "llm", "tool"} <= observed_categories
assert observed_runtime_ids == set(runtime_ids.values())