fix(relay): clarify opt-in plugin layering

Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
This commit is contained in:
Bryan Bednarski
2026-08-11 11:29:43 -06:00
parent 2ce4f228fe
commit ad9fb060c5
3 changed files with 98 additions and 6 deletions
+2 -2
View File
@@ -1905,9 +1905,8 @@ def _configured_plugin_inputs(
"Hermes [[dynamic_plugins]] records are unsupported; use Relay "
"[[plugins.dynamic]] records"
)
plugins_section = config.get("plugins")
dynamic_plugins: list[Any] = []
if plugins_section:
if "plugins" in config:
dynamic_plugins = relay.plugin.load_dynamic_plugin_activation_specs(
config_path
)
@@ -1923,6 +1922,7 @@ def _configured_plugin_inputs(
)
return None
def _flush_relay_subscribers(relay: Any) -> None:
"""Flush Relay without blocking an asyncio event-loop thread."""
_resolve_plugin_awaitable(relay.subscribers.flush_async())
+9 -4
View File
@@ -43,10 +43,15 @@ behalf.
Relay plugin activation is owned by the native runtime and remains explicitly
opt-in. Set `HERMES_NEMO_RELAY_PLUGINS_TOML` to a selected `plugins.toml` to
activate configured middleware, exporters, or dynamic plugins. When it is
unset, Hermes does not invoke Relay's plugin initializer or trigger Relay
plugin configuration discovery. Invalid explicit configuration is reported
and Hermes continues without native plugin activation.
activate configured middleware, exporters, or dynamic plugins. When the
variable is unset, Hermes does not invoke Relay's plugin initializer, so Relay
does not perform plugin configuration discovery or layering. When it is set
and the selected file loads successfully, Relay performs its normal static
`plugins.toml` discovery and layers the selected static configuration over the
discovered configuration. Dynamic `[[plugins.dynamic]]` records are loaded
from the selected file only. If the selected file cannot be loaded, Hermes
reports the error and does not invoke Relay initialization or fall back to
ambient discovery.
Hermes core owns one Relay host and one isolated Relay session scope per Hermes
session. Core lifecycle producers use
+87
View File
@@ -215,6 +215,32 @@ def test_malformed_explicit_config_does_not_fall_back_to_discovery(
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 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,
):
@@ -767,6 +793,67 @@ mode = "overwrite"
assert not (atof_dir / "events.jsonl").exists()
def test_real_binding_layers_project_config_after_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"
atof_dir = tmp_path / "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 = "{atof_dir}"
filename = "events.jsonl"
mode = "overwrite"
""".strip(),
encoding="utf-8",
)
selected_config = selected_directory / "plugins.toml"
selected_config.write_text("version = 1", 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-layered-plugins"})
finally:
host.shutdown()
relay_runtime._reset_for_tests()
assert (atof_dir / "events.jsonl").is_file()
def test_real_binding_loads_explicit_config_and_exports_native_activity(
tmp_path,
monkeypatch,